You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
OpenAudio_ArduinoLibrary/radioBFSKmodulator_F32.h

269 lines
9.1 KiB

/*
* radioBFSKmodulator_F32.h
*
* BFSK Modulator including control line low-pass filtering
* By Bob Larkin W7PUA 17 March 2022
*
* Thanks to PJRC and Pau Stoffregen for the Teensy processor, Teensyduino
* and Teensy Audio. Thanks to Chip Audette for the F32 extension
* work. Alll of that makes this possible. Bob
*
* Copyright (c) 2020 Bob Larkin
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice, development funding notice, and this permission
* notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
Notes:
Modem type: V.23 forward channel
Data rates: 600-/1200-bps half duplex
Symbol rate: 600/1200 baud (1 bit/symbol)
Sample rate: 9.6 kHz
Mean frequency: Mode 1 (600 bps) 1500 Hz 1 = 1300 Hz 0 = 1700 Hz
Mode 2 (1200 bps) 1700 Hz 1 = 1300 Hz 0 = 2100 Hz
Frequency deviation: Mode 1 ±200 Hz
Mode 2 ±400 Hz
Tx power spectrum: As per V.2 not exceeding -13 dBm
Receiver: FSK receiver structure based on quadrature baseband time-delay
multiply
Rx performance: 600 bps Flat channel: 11-dB SNR for 1e-5 block error rate
(1000 bits/block)
1200 bps Flat channel: 16.5-dB SNR for 1e-5 block error rate
(1000 bits/block)
Modem type: V.23 backward channel
Data rate: 75-bps half duplex
Symbol rate: 75 baud (1 bit/symbol)
Sample rate: 9.6 kHz
Mean frequency: 420 Hz 1 = 390 Hz 0 = 450 Hz
Frequency deviation: ±30 Hz
*/
/*
* Binary Frequency Shift Keying modulator
* Very general as this can be used to send 1 to 32 bit words at any
* rate and frequencies only constrained by fs/2 limitations. The bit
* meanings are just 0 or 1. So, start bits, parity and ending bits
* are all just part of the transmitted data. For 8N1 select
* 10 bits here and construct a 0 start bit and a 1 ending bit.
* For instance: If ch8 is an 8-bit character, we transmit
* 0X200 | (ch8 << 1).
*
* After a bit is determined, there is an optional low-pass FIR filter to limit
* the transmit bandwidth. This produces a non-binary frequency that
* is set by a phase increment.
*
* The update() uses about 13 microseconds for 128 points with no LPF
* on the control signal. The LPF adds roughly 3 icroseconds per
* FIR coefficient being used.
*/
/* Here is an Octave/Matlab .m file to design the Gaussian LP FIR filter
% FIR Gaussian Pulse-Shaping Filter Design - Based roughly on
% https://www.mathworks.com/help/signal/ug/fir-gaussian-pulse-shaping-filter-design.html
%
symbolRate = 1200.0
Ts = 1.0/symbolRate; % Symbol time (sec)
span = 4; % Filter span in symbols
sampleRate = 48000.0
overSampleFactor = sampleRate/symbolRate % For printing info
% nFIR is constrained. Check smallest coefficient and if too big, adjust span
nFIR = span*sampleRate/symbolRate;
if rem(nFIR, 2) == 0
nFIR = nFIR + 1;
endif
nFIR
%
% Important BT parameter:
BT = 0.4
a = Ts*sqrt(0.5*log(2))/BT;
B = sqrt(log(2)/2)./(a);
B3dB = B % For printing info
t = linspace(-span*Ts/2, span*Ts/2, nFIR)';
cf32f = zeros(nFIR); % FIR coefficients
cf32f = sqrt(pi)/a*exp(-(pi*t/a).^2);
cf32f = (cf32f./sum(cf32f)) % Column vector, normalized for DC gain of 1
*/
/* Gaussian FSK LPF Filter A Sample filter per AIS radios.
* Paste from here into INO program, or design a different LPF.
* This filter corresponds to:
* symbolRate = 9600
* sampleRate = 48000
* overSampleFactor = 5
* nFIR = 21
* BT = 0.40000
* B3dB = 3840.0
* Atten at 9600 Hz = 18.8 dB
float32_t gaussFIR9600_48K[21] = {
0.0000000029f,
0.0000000934f,
0.0000020700f,
0.0000318610f,
0.0003406053f,
0.0025289299f,
0.0130411453f,
0.0467076746f,
0.1161861408f,
0.2007307811f,
0.2408613913f,
0.2007307811f,
0.1161861408f,
0.0467076746f,
0.0130411453f,
0.0025289299f,
0.0003406053f,
0.0000318610f,
0.0000020700f,
0.0000000934f,
0.0000000029f}; */
#ifndef _radioBFSKmodulator_f32_h
#define _radioBFSKmodulator_f32_h
#include "AudioStream_F32.h"
#include "arm_math.h"
#include "mathDSP_F32.h"
#include <math.h>
class RadioBFSKModulator_F32 : public AudioStream_F32 {
//GUI: inputs:0, outputs:1 //this line used for automatic generation of GUI node
//GUI: shortName: BFSKmodulator
public:
// Option of AudioSettings_F32 change to block size or sample rate:
RadioBFSKModulator_F32(void) : AudioStream_F32(0, NULL) {
// Defaults
}
RadioBFSKModulator_F32(const AudioSettings_F32 &settings) : AudioStream_F32(0, NULL) {
setSampleRate_Hz(settings.sample_rate_Hz);
block_size = settings.audio_block_samples;
}
// As viewed from the INO, does the bufffer have space?
bool bufferHasSpace(void) {
if((64LL - indexIn + indexOut) <= 0LL) { //Serial.print(indexIn);
//Serial.print(" NR1 ");Serial.println(indexOut); delay(4);
return false; } // No room for more
return true;
}
// As viewed from the INO, put a data word into the buffer to send.
// Returns true if successful.
bool sendData(uint32_t data) {
int64_t space = 64LL - indexIn + indexOut;
if(space <= 0LL) { // No room
return false;
}
indexIn++;
dataBuffer[indexIn & indexMask] = data;
return true;
}
void bufferClear(void) {
indexIn = 0LL;
indexOut = 0LL;
}
// Sets all parameters for BFSK modulation
// Returns number of audio samplesPerDataBit
uint32_t setBFSK(float32_t _bitRate, uint16_t _numBits, float32_t _f0, float32_t _f1) {
bitRate = _bitRate;
numBits = _numBits;
// Transmission rates are quantized by sample rate. On this transmit side,
// it seems best to error on the side of sending too fast. Thus ceilf()
samplesPerDataBit = (uint16_t)(ceilf((sample_rate_Hz/bitRate) - 0.0000001));
freq[0] = _f0;
freq[1] = _f1;
phaseIncrement[0] = kp*freq[0];
phaseIncrement[1] = kp*freq[1];
deltaPhase = kp*(freq[1] - freq[0]);
return samplesPerDataBit;
}
// The amplitude, a, is the peak, as in zero-to-peak. This produces outputs
// ranging from -a to +a.
void amplitude(float32_t a) {
if (a < 0.0f) a = 0.0f;
magnitude = a;
}
// Low pass filter on frequency control line. Set to NULL to omitfilter.
void setLPF(float32_t* _FIRdata, float32_t* _FIRcoeff, uint16_t _numCoeffs) {
FIRdata = _FIRdata;
if(_FIRcoeff == NULL || _numCoeffs == 0)
{
FIRcoeff = NULL;
numCoeffs = 0;
return;
}
FIRcoeff = _FIRcoeff;
numCoeffs = _numCoeffs;
if(numCoeffs != 0)
indexFIRMask = (uint64_t)(powf(2, (ceilf(logf(numCoeffs)/logf(2.0f)))) - 1.000001f);
}
void setSampleRate_Hz(const float &fs_Hz) {
sample_rate_Hz = fs_Hz;
kp = 512.0f/sample_rate_Hz;
phaseIncrement[0] = kp*freq[0];
phaseIncrement[1] = kp*freq[1];
deltaPhase = kp*(freq[1] - freq[0]);
samplesPerDataBit = (uint32_t)(0.5 + sample_rate_Hz/bitRate);
}
virtual void update(void);
private:
float32_t sample_rate_Hz = AUDIO_SAMPLE_RATE_EXACT;
uint16_t block_size = 128;
float32_t freq[2] = {1300.000f, 2100.000f};
float32_t kp = 512.0f/sample_rate_Hz;
float32_t phaseIncrement[2] = {kp*freq[0], kp*freq[1]};
float32_t deltaPhase = kp*(freq[1] - freq[0]);
float32_t bitRate = 1200.0f;
float32_t currentPhase = 0.0f;
float32_t magnitude = 1.0f;
uint32_t currentWord = 0UL;
uint16_t numBits = 10;
uint16_t bitSendCount = 0;
// atIdle means no data is available. But, keep on sending a sine wave
// at the logic 1 frequency.
bool atIdle = true;
uint32_t dataBuffer[64]; // Make 64 a define??
// By using a 64-bit index we never need to wrap around.
// We create a circular 64-word buffer with the mask.
int64_t indexIn = 0ULL; // Next word to be entered to buffer
int64_t indexOut = 0ULL; // Next word to be sent
int64_t indexMask = 0X003F; // Goes with 64
uint16_t vc = 0;
uint32_t samplePerBitCount = 0;
// The next for 1200 bit/sec and 44100 sample rate is 37 samples,
// or actually 1192 bits/sec.
// For 9600 bit/sec and 96000 sample rate is 10 samples.
uint32_t samplesPerDataBit = (uint32_t)(0.5 + sample_rate_Hz/bitRate);
float32_t* FIRdata = NULL;
float32_t* FIRcoeff = NULL;
uint16_t numCoeffs = 0;
uint64_t indexFIRlatest = 0; // Next word to be entered to buffer
uint64_t indexFIRMask = 0X001F; // Goes with 32
};
#endif