/* * 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. * * 2 April 2023 -Corrected to handle outputs from full buffer. RSL * Added int16_t getBufferSpace(). RSL */ /* 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 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; } #define DATA_BUFFER_EMPTY 0 #define DATA_BUFFER_PART 1 #define DATA_BUFFER_FULL 2 // As viewed from the INO, does the bufffer have space? bool bufferHasSpace(void) { checkBuffer(); if(stateBuffer == DATA_BUFFER_FULL) return false; else return true; } int16_t getBufferSpace(void) { return 64-checkBuffer(); } // As viewed from the INO, put a data word into the buffer to send. // Returns true if successful. bool sendData(uint32_t data) { checkBuffer(); if(stateBuffer == DATA_BUFFER_FULL) return false; else { indexIn++; dataBuffer[indexIn & indexMask] = data; checkBuffer(); 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); } int16_t checkBuffer(void); virtual void update(void); #define DATA_BUFFER_EMPTY 0 #define DATA_BUFFER_PART 1 #define DATA_BUFFER_FULL 2 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; 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 stateBuffer = DATA_BUFFER_EMPTY; 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