From da887652823052041e99685841d9a8ca998cad4d Mon Sep 17 00:00:00 2001 From: boblark Date: Tue, 5 Jul 2022 14:27:26 -0700 Subject: [PATCH] New functionality for BFSK --- OpenAudio_ArduinoLibrary.h | 3 + RadioFMDetector_F32.cpp | 2 +- RadioFMDiscriminator_F32.cpp | 155 ++++++++++++ RadioFMDiscriminator_F32.h | 304 +++++++++++++++++++++++ UART_F32.cpp | 154 ++++++++++++ UART_F32.h | 216 ++++++++++++++++ docs/index.html | 289 ++++++++++++++++++++- examples/BFSK/BFSK.ino | 158 ++++++++++++ examples/BFSK_random/BFSK_random.ino | 160 ++++++++++++ examples/BFSK_random/BFSKrandom.gnumeric | Bin 0 -> 10802 bytes examples/BFSK_snr/BFSK_snr.ino | 148 +++++++++++ gui/DesignTool_F32.zip | Bin 1714256 -> 1716428 bytes radioBFSKmodulator_F32.cpp | 101 ++++++++ radioBFSKmodulator_F32.h | 269 ++++++++++++++++++++ 14 files changed, 1951 insertions(+), 8 deletions(-) create mode 100644 RadioFMDiscriminator_F32.cpp create mode 100644 RadioFMDiscriminator_F32.h create mode 100644 UART_F32.cpp create mode 100644 UART_F32.h create mode 100644 examples/BFSK/BFSK.ino create mode 100644 examples/BFSK_random/BFSK_random.ino create mode 100644 examples/BFSK_random/BFSKrandom.gnumeric create mode 100644 examples/BFSK_snr/BFSK_snr.ino create mode 100644 radioBFSKmodulator_F32.cpp create mode 100644 radioBFSKmodulator_F32.h diff --git a/OpenAudio_ArduinoLibrary.h b/OpenAudio_ArduinoLibrary.h index e5487cc..a2c772d 100644 --- a/OpenAudio_ArduinoLibrary.h +++ b/OpenAudio_ArduinoLibrary.h @@ -56,6 +56,9 @@ #include "AudioFilterEqualizer_F32.h" #include "AudioFilterFIRGeneral_F32.h" #include "RadioFMDetector_F32.h" +#include "radioBFSKmodulator_F32.h" +#include "RadioFMDiscriminator_F32.h" #include "radioNoiseBlanker_F32.h" #include "synth_sin_cos_f32.h" +#include "UART_F32.h" // #include "USB_Audio_F32.h" Include this separately if needed. Then in IDE Tools>USB Type>Audio diff --git a/RadioFMDetector_F32.cpp b/RadioFMDetector_F32.cpp index cbfc7f8..8f4cf1a 100644 --- a/RadioFMDetector_F32.cpp +++ b/RadioFMDetector_F32.cpp @@ -6,7 +6,7 @@ * Chip Audette, OpenAudio, Apr 2017 * ------------------- * - * Copyright (c) 2020 Bob Larkin + * Copyright (c) 2022 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 diff --git a/RadioFMDiscriminator_F32.cpp b/RadioFMDiscriminator_F32.cpp new file mode 100644 index 0000000..df95d63 --- /dev/null +++ b/RadioFMDiscriminator_F32.cpp @@ -0,0 +1,155 @@ +/* + * RadioFMDetector_F32.cpp + * + * 25 April 2022 + * Bob Larkin, in support of the library: + * Chip Audette, OpenAudio, Apr 2017 + * ------------------- + * + * Copyright (c) 2022 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 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. + * + * See RadioFMDiscriminator_F32.h for usage details +*/ +#include "RadioFMDiscriminator_F32.h" + +// ==== UPDATE ==== +void RadioFMDiscriminator_F32::update(void) { + audio_block_f32_t *blockIn, *blockA=NULL, *blockB=NULL; + int i; + static float32_t discr0, disc1, disc2; + static float32_t sumNoise; + static float saveIn = 0.0f; // for squelch + static float saveOut = 0.0f; + +#if TEST_TIME_FM + if (iitt++ >1000000) iitt = -10; + uint32_t t1, t2; + t1 = tElapse; +#endif + + // Get input to FM Discriminator block + blockIn = AudioStream_F32::receiveWritable_f32(0); + if (!blockIn) { + return; + } + + if (fir_Out_Coeffs == NULL) { + //if(errorPrintFM) Serial.println("FMDET-ERR: No Out FIR Coefficients"); + AudioStream_F32::release(blockIn); + return; + } + + // Try to get two blocks for both intermediate data and outputs + blockA = AudioStream_F32::allocate_f32(); + if (!blockA){ // Didn't have any + //if(errorPrintFM) Serial.println("FMDET-ERR: No Output Memory"); + AudioStream_F32::release(blockIn); + return; + } + + blockB = AudioStream_F32::allocate_f32(); + if (!blockB) // Didn't have any + { + //if(errorPrintFM) Serial.println("FMDET-ERR: No Output Memory"); + AudioStream_F32::release(blockIn); + AudioStream_F32::release(blockA); + return; + } + + // Limiter + for(int k=0; k<128; k++) + blockIn->data[k] = (blockIn->data[k]>0.0f) ? 1.0f : -1.0f; + + // Two BPF at f1 and f2 + arm_biquad_cascade_df1_f32(&f1BPF_inst, blockIn->data, + blockA->data, blockIn->length); + arm_biquad_cascade_df1_f32(&f2BPF_inst, blockIn->data, + blockB->data, blockIn->length); + + if ( isnan(discrOut) ) discrOut=0.0f; + + // Find difference in responses and average + for (i=0; i < block_size; i++) + { + // Find maximum absolute amplitudes (full-wave rectifiers) + disc1 = blockA->data[i]; + disc1 = disc1>0.0 ? disc1 : -disc1; + disc2 = blockB->data[i]; + disc2 = disc2>0.0 ? disc2 : -disc2; + discr0 = disc2 - disc1; // Sample-by-sample discriminator output + + // Low pass filtering, RC type + discrOut = 0.999f*discr0 + 0.001f*discrOut; // REMOVE????? + + // Data point is now discrOut. Apply single pole de-emphasis LPF, in place + // Set kDem = 1 to stop filtering + // dLast = Kdem * discrOut + OneMinusKdem * dLast; //<<<<<<<data[i] = dLast; // and save to an array + blockA->data[i] = discrOut; + + //Serial.print(disc1, 6); Serial.print(" dLow dHigh "); Serial.println(disc2, 6); + //Serial.println(discrOut, 6); + } + + // Do output FIR filter. Data now in blockA. Filter out goes to blockB. + if(outputFilterType == LPF_FIR) + arm_fir_f32(&FMDet_Out_inst, blockA->data, blockB->data, (uint32_t)blockIn->length); + else if(outputFilterType == LPF_IIR) + arm_biquad_cascade_df1_f32(&outLPF_inst, blockA->data, blockB->data, blockIn->length); + else + for(int k=0; klength; k++) + blockB->data[k] = blockA->data[k]; + + // The un-squelch controlled audio for tone decoding, etc., always goes to output 0 + AudioStream_F32::transmit(blockB, 0); + + // Squelch picks the audio from before the output filter and does a 4-pole BiQuad BPF + // blockA->data still has the data we need. Borrow blockIn for squelch filter out. + arm_biquad_cascade_df1_f32(&iirSqIn_inst, blockA->data, blockIn->data, blockIn->length); + + // Update the Squelch full-wave envelope detector and single pole LPF + sumNoise = 0.0f; + for(i=0; idata[i]); // Ave of rectified noise + squelchLevel = alpha*(sumNoise + saveIn) + gamma*saveOut; // 1 pole + saveIn = sumNoise; + saveOut = squelchLevel; + // Add hysteresis here <<<< + + // Squelch gate sends audio to output 1 (right) if squelch is open + if(squelchLevel > squelchThreshold) + AudioStream_F32::transmit(blockB, 1); + else + { + for(int kk=0; kk<128; kk++) + blockA->data[kk] = 0.0f; + AudioStream_F32::transmit(blockA, 1); + } + + AudioStream_F32::release(blockA); // Give back the blocks + AudioStream_F32::release(blockB); + AudioStream_F32::release(blockIn); +#if TEST_TIME_FM + t2 = tElapse; + if(iitt++ < 0) {Serial.print("At end of FM Det "); Serial.println (t2 - t1); } + t1 = tElapse; +#endif +} diff --git a/RadioFMDiscriminator_F32.h b/RadioFMDiscriminator_F32.h new file mode 100644 index 0000000..63a504e --- /dev/null +++ b/RadioFMDiscriminator_F32.h @@ -0,0 +1,304 @@ +/* + * RadioFMDiscriminator_F32 + * 25 April 2022 Bob Larkin + * With much credit to: + * Chip Audette (OpenAudio) Feb 2017 + * Building from AudioFilterFIR from Teensy Audio Library + * (AudioFilterFIR credited to Pete (El Supremo)) + * and of course, to PJRC for the Teensy and Teensy Audio Library + * + * Copyright (c) 2022 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 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. + */ + +/* This consists of a single input at some frequency, such as 10 to 20 kHz and + * an output, such as 0 to 5 kHz. The output level is linearly dependent on the + * frequency of the input sine wave frequency, i.e., it is an FM discriminator. + * + * NOTE: Due to the sample frequencies we are working with, like 44.1 kHz, this + * discriminator cannot handle full FM broadcast bandwidths. It is suitable for + * NBFM as used in communications, marine radio, ham radio, etc. + * + * The output can be FIR filtered using default parameters, + * or using coefficients from an array. A separate single pole de-emphasis filer + * is included that again can be programmed. + * + * Internally, the discriminator uses a pair of single pole BPF that + * + * Status: + * + * + * Functions: + * frequency(float fCenter ) sets the center frequency in Hz, default 15000. + * + * filterOut(float *firCoeffs, uint nFIR, float Kdem) sets output filtering where: + * float32_t* firCoeffs is an array of coefficients + * uint nFIR is the number of coefficients + * float32_t Kdem is the de-emphasis frequency factor, where + * Kdem = 1/(0.5+(tau*fsample)) and tau is the de-emphasis + * time constant, typically 0.0005 second and fsample is + * the sample frequency, typically 44117. + * + * filterIQ(float *fir_IQ_Coeffs, uint nFIR_IQ) sets output filtering where: + * float32_t* fir_IQ_Coeffs is an array of coefficients + * uint nFIR_IQ is the number of coefficients, max 60 + * + * setSampleRate_Hz(float32_t _sampleRate_Hz) allows dynamic changing of + * the sample rate (experimental as of May 2020). + * + * Time: For T4.0, 45 microseconds for a block of 128 data points. + * + */ + +#ifndef _radioFMDiscriminator_f32_h +#define _radioFMDiscriminator_f32_h + +//#include "mathDSP_F32.h" +#include "AudioStream_F32.h" +//#include "arm_math.h" + +#define LPF_NONE 0 +#define LPF_FIR 1 +#define LPF_IIR 2 + +#define TEST_TIME_FM 0 + +class RadioFMDiscriminator_F32 : public AudioStream_F32 { +//GUI: inputs:1, outputs:2 //this line used for automatic generation of GUI node +//GUI: shortName: FMDiscriminator +public: + // Default block size and sample rate: + RadioFMDiscriminator_F32(void) : AudioStream_F32(1, inputQueueArray_f32) { + } + // Option of AudioSettings_F32 change to block size and/or sample rate: + RadioFMDiscriminator_F32(const AudioSettings_F32 &settings) : AudioStream_F32(1, inputQueueArray_f32) { + sampleRate_Hz = settings.sample_rate_Hz; + block_size = settings.audio_block_samples; + } + + // This sets the parameters of the discriminator. The output LPF, if any, + // must precede this function. + void initializeFMDiscriminator(float32_t _f1, float32_t _f2, float32_t _q1, float32_t _q2) { + f1 = _f1; f2 = _f2; + q1 = _q1; q2 = _q2; + + // Design the 2 single pole filters: + setBandpass(coeff_f1BPF, f1, q1); + setBandpass(coeff_f2BPF, f2, q2); + // Initialize BiQuad instances for BPF's (ARM DSP Math Library) + // https://www.keil.com/pack/doc/CMSIS/DSP/html/group__BiquadCascadeDF1.html + // arm_biquad_cascade_df1_init_f32(&biquad_inst, numStagesUsed, &coeff32[0], &StateF32[0]) + arm_biquad_cascade_df1_init_f32(&f1BPF_inst, 1, &coeff_f1BPF[0], &state_f1BPF[0]); + arm_biquad_cascade_df1_init_f32(&f2BPF_inst, 1, &coeff_f2BPF[0], &state_f2BPF[0]); + + /* The FIR instance setup call + * void arm_fir_init_f32( + * arm_fir_instance_f32* S, points to instance of floating-point FIR filter struct + * uint16_t numTaps, Number of filter coefficients in the filter. + * float32_t* pCoeffs, points to the filter coefficients buffer. + * float32_t* pState, points to the state buffer. + * uint32_t blockSize) Number of samples that are processed per call. + */ + if (fir_Out_Coeffs && outputFilterType == LPF_FIR) + { + arm_fir_init_f32(&FMDet_Out_inst, nFIR_Out, &fir_Out_Coeffs[0], + &State_FIR_Out[0], (uint32_t)block_size); + } + else + { + ; + } + + // Initialize squelch Input BPF BiQuad instance + arm_biquad_cascade_df1_init_f32(&iirSqIn_inst, 2, pCfSq, &stateSqIn[0]); + } + + // Provide for changing to user FIR for discriminator output, (and user de-emphasis) + // This should precede setting discriminator parameters + void filterOutFIR(float32_t *_fir_Out_Coeffs, int _nFIR_Out, float32_t *_State_FIR_Out, float32_t _Kdem) { + if(_fir_Out_Coeffs==NULL) + { + outputFilterType = LPF_NONE; + return; + } + if( _Kdem<0.0001 || _Kdem>1.0 ) { + return; + } + outputFilterType = LPF_FIR; + fir_Out_Coeffs = _fir_Out_Coeffs; + nFIR_Out = _nFIR_Out; + State_FIR_Out = _State_FIR_Out; + Kdem = _Kdem; + OneMinusKdem = 1.0f - Kdem; + } + + // This should precede setting discriminator parameters, if used + void filterOutIIR(float32_t _frequency, float32_t _q, float32_t _Kdem) { + if( _frequency < 0.0001f) + { + outputFilterType = LPF_NONE; + return; + } + outputFilterType = LPF_IIR; + setLowpass(coeff_outLPF, _frequency, _q); + arm_biquad_cascade_df1_init_f32(&outLPF_inst, 1, &coeff_outLPF[0], &state_outLPF[0]); + + if( _Kdem<0.0001 || _Kdem>1.0 ) { + return; + } + Kdem = _Kdem; + OneMinusKdem = 1.0f - Kdem; + } + + // Provide for changing to user supplied BiQuad for Squelch input. + // This should precede setting discriminator parameters, if used + void setSquelchFilter(float* _sqCoeffs) { + if( _sqCoeffs==NULL) + pCfSq = coeffSqIn; // Default filter + else + pCfSq = _sqCoeffs; + } + + // The squelch level reads nominally 0.0 to 1.0 where + float getSquelchLevel (void) { + return squelchLevel; + } + + // The squelch threshold is nominally 0.7 where + // 0.0 always lets audio through. + void setSquelchThreshold (float _sqTh) { + squelchThreshold = _sqTh; + } + + void setSquelchDecay (float _sqDcy) { + gamma = _sqDcy; + alpha = 0.5f*(1.0f - gamma); + } + + // This should precede setting discriminator parameters, if used + void setSampleRate_Hz(float32_t _sampleRate_Hz) { + sampleRate_Hz = _sampleRate_Hz; + } + + virtual void update(void); + +private: + // One input data pointer + audio_block_f32_t *inputQueueArray_f32[1]; + float32_t sampleRate_Hz = AUDIO_SAMPLE_RATE_EXACT; + uint16_t block_size = AUDIO_BLOCK_SAMPLES; + + /* A pair of single pole BPF for the discriminator: + * Info - The structure from arm_biquad_casd_df1_inst_f32 consists of + * uint32_t numStages; + * const float32_t *pCoeffs; //Points to the array of coefficients, length 5*numStages. + * float32_t *pState; //Points to the array of state variables, length 4*numStages. + */ + float f1, q1, f2, q2; + arm_biquad_casd_df1_inst_f32 f1BPF_inst; + float coeff_f1BPF[5]; + float state_f1BPF[4]; + arm_biquad_casd_df1_inst_f32 f2BPF_inst; + float coeff_f2BPF[5]; + float state_f2BPF[4]; + + // De-emphasis constant + float32_t Kdem = 0.045334f; + float32_t OneMinusKdem = 0.954666f; + // Save last data point for next update of de-emphasis filter + float32_t dLast = -1.0f; + + // The output FIR LPF (optional) + int outputFilterType = LPF_NONE; + // ARM CMSIS FIR filter instances and State vectors + arm_fir_instance_f32 FMDet_Out_inst; + float32_t *State_FIR_Out; // 128+nFIR_Out + uint16_t nFIR_Out; + float32_t* fir_Out_Coeffs = NULL; + float32_t discrOut = 0.0f; + // Output IIR Biquad alternative + arm_biquad_casd_df1_inst_f32 outLPF_inst; + float coeff_outLPF[5]; + float state_outLPF[4]; + + arm_biquad_casd_df1_inst_f32 iirSqIn_inst; + // Default 2 stage Squelch input BiQuad filter, 3000 Hz, 4000 Hz both Q=5 + // The -6 dB points are 2680 and 4420 Hz + // The -20 dB points are 2300 and 5300 Hz + float coeffSqIn[10] = { + 0.0398031529f, 0.0f, -0.0398031529f, 1.74762569f, -0.92039369f, + 0.0511929547f, 0.0f, -0.0511929547f, 1.59770204f, -0.89761409f}; + float* pCfSq = coeffSqIn; + float stateSqIn[8]; + float squelchThreshold = 0.7f; + float squelchLevel = 1.0f; + float gamma = 0.99; + float alpha = 0.5f*(1.0f - gamma); + +#if TEST_TIME_FM +elapsedMicros tElapse; +int32_t iitt = 999000; // count up to a million during startup +#endif + +#if 0 + /* Info Only, an example FIR filter, include this in INO to use. + * FIR filter designed with http://t-filter.appspot.com + * fs = 44100 Hz, < 3kHz ripple 0.36 dB, >6 kHz, -60 dB, 39 taps + * Corrected to give DC gain = 1.00 + */ + float32_t fir_Out39[39] = { + -0.0008908477f, -0.0008401274f, -0.0001837353f, 0.0017556005f, + 0.0049353322f, 0.0084952916f, 0.0107668722f, 0.0097441685f, + 0.0039877576f, -0.0063455016f, -0.0188069300f, -0.0287453055f, + -0.0303831521f, -0.0186809770f, 0.0085931270f, 0.0493875744f, + 0.0971742012f, 0.1423015880f, 0.1745838382f, 0.1863024485f, + 0.1745838382f, 0.1423015880f, 0.0971742012f, 0.0493875744f, + 0.0085931270f, -0.0186809770f, -0.0303831521f, -0.0287453055f, + -0.0188069300f, -0.0063455016f, 0.0039877576f, 0.0097441685f, + 0.0107668722f, 0.0084952916f, 0.0049353322f, 0.0017556005f, + -0.0001837353f, -0.0008401274f, -0.0008908477f }; +#endif + + // Unity gain BPF Biquad, CMSIS format (not Matlab) + void setBandpass(float32_t* pCoeff, float32_t frequency, float32_t q) { + float32_t w0 = 2.0f*3.141592654f*frequency/sampleRate_Hz; + float32_t alpha = sin(w0)/(2.0f*q); + float32_t scale = 1.0f/(1.0f + alpha); + /* b0 */ *(pCoeff+0) = alpha*scale; + /* b1 */ *(pCoeff+1) = 0.0f; + /* b2 */ *(pCoeff+2) = (-alpha)*scale; + /* a1 */ *(pCoeff+3) = -(-2.0f*cos(w0))*scale; + /* a2 */ *(pCoeff+4) = -(1.0f - alpha)*scale; + } + + // Unity gain LPF, CMSIS format + void setLowpass(float32_t* pCoeff, float32_t frequency, float32_t q) { + float32_t w0 = frequency*(2.0f*3.141592654f / sampleRate_Hz); + float32_t alpha = sin(w0) / ((double)q*2.0f); + float32_t cosW0 = cos(w0); + float32_t scale = 1.0f/(1.0f+alpha); // which is equal to 1.0f / a0 + /* b0 */ *(pCoeff+0) = ((1.0f - cosW0) / 2.0f)*scale; + /* b1 */ *(pCoeff+1) = (1.0f - cosW0)*scale; + /* b2 */ *(pCoeff+2) = *(pCoeff+0); + /* a1 */ *(pCoeff+3) = -(-2.0f*cosW0)*scale; + /* a2 */ *(pCoeff+4) = -(1.0f - alpha)*scale; + } + }; +#endif diff --git a/UART_F32.cpp b/UART_F32.cpp new file mode 100644 index 0000000..04d2edd --- /dev/null +++ b/UART_F32.cpp @@ -0,0 +1,154 @@ +/* + * UART_F32.cpp + * + * 10 JUne 2022 - Separated from FM Discriminator + * Bob Larkin, in support of the library: + * Chip Audette, OpenAudio, Apr 2017 + * ------------------- + * + * Copyright (c) 2022 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 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. + * + * See UART_F32.h for usage details +*/ +#include "UART_F32.h" + +void UART_F32::update(void) { + audio_block_f32_t *blockIn; + int i; + bool downTransition; + float32_t dataIn; + + // For measuring timing + // uint32_t t1 = micros(); + + // Get input to UART block + blockIn = AudioStream_F32::receiveWritable_f32(0); + if (!blockIn) { + return; + } + + /* Simple UART - Start idle state. Wait for Start Bit, wait 0.5 bit periods + * and read Start bit. Read next data bits every cTauI samples. After last data bit + * is read, go back to idle state. This gives a half bit period to allow for fast + * sending clocks. + */ + for (i=0; idata[i] + inputOffset; // Programmable offset, if needed + downTransition = (dataIn<=0.0f && yLast>0.0f); + yLast = dataIn; + + if(serialState==SERIAL_DATA_FINISHED) // Have data bits, waiting to read stop bit + { //Serial.print(sClock); Serial.print("sc next"); Serial.println(cSampleNext); + if(sClock>=cSampleNext) + { // Serial.print(" @"); Serial.print(serialState); Serial.println("@"); + if(dataIn > 0.0f) // Valid Stop Bit + { + // Serial.println("P"); // Stop bit is correct + writeUartData(dataWord, saveStatus, elapsedTime); + saveStatus &= ERR_OVERRUN; // Error indicator has been sent, don't clear overrun + } + else // Wrong logic level + { + // Many low S/N frame errors are just bit-read errors and not timing errors. + // No way to tell which, and best to just accept data. + // Serial.println("F"); // Data has been read with Framing error + saveStatus |= ERR_FRAME; + writeUartData(dataWord, saveStatus, elapsedTime); + saveStatus &= ERR_OVERRUN; // Error indicator has been sent, don't clear overrun + } + serialState = SERIAL_IDLE; // Return to waiting for start bit + bitCount = 0; + bitMask = 1UL; + dataWord = 0UL; + elapsedTime = 0; + } + } + else if(serialState==SERIAL_DATA) + { + if(sClock>=cSampleNext) + { + if(bitCount==0) // Going to read start bit + { + if(dataIn<=0.0f) // Valid start bit + { + //Serial.print("T"); + // Logic is low at data read time for valid start bit + bitCount = 1; + bitMask = 1UL; + dataWord = 0UL; + cSampleNext += cTauI; + } + else // Not low, must be noise, cancel out + { + serialState = SERIAL_IDLE; + bitCount = 0; + cSampleNext = 0; + } + } + else // bitCount>0 so reading data bits + { + if(dataIn >= 0.0f) + { + // Serial.print("1"); // Data bit is a 1 + dataWord |= bitMask; + } + else + { + // Serial.print("0"); // Data bit + // Data bit is already zero + } + bitMask = (bitMask << 1); + cSampleNext += cTauI; + bitCount++; + if(bitCount>=nTotal) // Last data bit + { + serialState = SERIAL_DATA_FINISHED; // Look for valid stop bit + bitCount = 0; + } + } + } + } + else if(serialState==SERIAL_IDLE) // Waiting for a Start Bit + { + if(downTransition) // Down going, potential start bit + { + serialState = SERIAL_DATA; + timeStartBit = sClock; + cSampleNext = sClock + cTauHalfI; // This will sample start bit + bitCount = 0; + } + } + + // UART done, now gather data for checking clock accuracy + // Use timeStartBit and time of last down transitions. + // Up transitions may have a bias and are not used. + if(downTransition) + { + elapsedTime = (int32_t)(sClock - timeStartBit); + } + } + + AudioStream_F32::release(blockIn); + +// Serial.print("At end of UART "); Serial.println (micros() - t1); +} diff --git a/UART_F32.h b/UART_F32.h new file mode 100644 index 0000000..f9d49ce --- /dev/null +++ b/UART_F32.h @@ -0,0 +1,216 @@ +/* + * UART_F32 + * 10 JUne 2022 - Separated from FM Discriminator + * + * Copyright (c) 2022 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 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. + */ + +/* This object takes a single audio input representing the baseband output + * of a suitable detector such as frequency or phase. The polarity of the + * input is >0.0 for logic 1 as well as the the idle state and <0.0 for logic 0. + * The timing of the UART is specified by the + * setUART(cTauI, cTauHalfI, nBits, nParity, nStop) function. The first two + * parameters are the data bit period measured in audio sample periods, + * and half of that (roughly) that sets the number of audio sample periods between + * between the 1-to-0 transition of the start bit and the middle of the start bit. + * nBits can be between 1 and 32 (default 8). nParity can be PARITY_NONE, + * PARITY_ODD or PARITY_EVEN. nStop is currently restricted to 1, but + * this could change. + * + * Data is read using the functions + * int32_t getNDataBuffer() that returns the number of words available + * and readUartData() that returns a pointer to the oldest UART data structure: + * struct uartData { + * uint32_t data; + * uint8_t error; // Parity=01, Overrun=02, Underrun=04 + * int32_t timeCrossings;} + * Up to 16 of these structures can be buffered before creating + * an ERR_OVERRUN. + * + * NOTE: Parity checking does nothing now. Needs to be added using + * the in-place function. June 2022 + * + * Time: For T4.0, 7 microseconds for 128 data points. + * + */ + +#ifndef _uart_f32_h +#define _uart_f32_h + +#include "AudioStream_F32.h" +#include "OpenAudio_ArduinoLibrary.h" + +#define SERIAL_IDLE 0 +#define SERIAL_DATA 1 +#define SERIAL_DATA_FINISHED 2 + +#define PARITY_NONE 0 +#define PARITY_ODD 1 +#define PARITY_EVEN 2 + +#define ERR_FRAME 1; +#define ERR_OVERRUN 2 +#define ERR_PARITY 4 + +struct uartData { + uint32_t data; + uint8_t status; + int32_t timeCrossings; + }; + +class UART_F32 : public AudioStream_F32 { +//GUI: inputs:1, outputs:0 //this line used for automatic generation of GUI node +//GUI: shortName: uart +public: + // Default block size and sample rate: + UART_F32(void) : AudioStream_F32(1, inputQueueArray_f32) { + } + // Option of AudioSettings_F32 change to block size and/or sample rate: + UART_F32(const AudioSettings_F32 &settings) : AudioStream_F32(1, inputQueueArray_f32) { + sampleRate_Hz = settings.sample_rate_Hz; + block_size = settings.audio_block_samples; + } + + void setUART(uint32_t _cTauI, uint32_t _cTauHalfI, + uint16_t _nBits, uint16_t _nParity, uint16_t _nStop) { + cTauI = _cTauI; + cTauHalfI = _cTauHalfI; + nBits = _nBits; + nStop = _nStop; + nTotal = 1 + nBits; + if(nParity > 0) nTotal++; + } + + // Returns the number of unread data words + int32_t getNDataBuffer(void) { + delay(1); // Why needed?? <<<<<<<<<<<<<< + return (uartWriteIndex - uartReadIndex); + } + + // Read UART data returns a pointer to the uartData structure. + // This increments the index and thus can only be called once per + // successful UART output word. If no data is available, a NULL + // pointer is returned. + struct uartData* readUartData(void) { + if(uartReadIndex >= uartWriteIndex) // Never should be greater + return NULL; + int32_t uri = uartReadIndex & uartDMask; + // Circular increment of the read index + uartReadIndex++; + bufferSpace = 16 - uartWriteIndex + uartReadIndex; + return &uartD[uri]; + } + + // The input is a floating point value, centered on 0.0. One source of this + // could be a discriminator base-band output. If the input signal is offset + // from zero, it can be corrected with this function. This inputOffset is + // added to the input and can be + or - in value. + void setInputOffset(float32_t _inputOffset) { + inputOffset = _inputOffset; + } + + void setSampleRate_Hz(float32_t _sampleRate_Hz) { + sampleRate_Hz = _sampleRate_Hz; + } + + virtual void update(void); + +private: + // One input data pointer + audio_block_f32_t *inputQueueArray_f32[1]; + float32_t sampleRate_Hz = AUDIO_SAMPLE_RATE_EXACT; + uint16_t block_size = AUDIO_BLOCK_SAMPLES; + + // BFSK decode clock variables + // Always 1 start bit. + uint16_t nBits = 8; // Data bits + uint16_t nParity = PARITY_NONE; + uint16_t nStop = 1; // Fixed for now + uint16_t nTotal = 9; // 1+nBits+Parity + uint32_t cTauI = 40UL; + uint32_t cTauHalfI = 20UL; + + struct uartData uartD[16]; // Circular buffer of UART data + + float32_t inputOffset = 0.0f; // Offset of input data + + // These next 2 indices are the index, mod 16, where we wil write + // data to next, or read data from next (if available). + // If the two are equal, no data is available to read. + int32_t uartReadIndex = 0L; // Both indices continue to grow, unbounded + int32_t uartWriteIndex = 0L; + + const int32_t uartDMask = 0B1111; // Mask for 16 word buffer + int32_t bufferSpace = 16L; // 16 - uartWriteIndex + uartReadIndex + uint16_t serialState = SERIAL_IDLE; + uint8_t saveStatus; + float32_t yData = 0.0f; + float32_t yLast = -0.01f; + float32_t yClock = -1.0f; + float32_t errorClock = 0.0f; + uint32_t sClock = 0UL; // Counted in audio sample units + uint32_t lastTime = 0UL; // Last zero crossing + uint32_t timeStartBit= 0UL; + uint32_t timeLastTransition = 0UL; + uint32_t cSampleNext = 0UL; + uint8_t bitCount = 0UL; // Where we are at in receiving word + uint32_t bitMask = 1; // Values 1, 2, 4, 8, 16,... tracks bitCount + uint32_t dataWord = 0UL; + uint32_t elapsedTime = 0UL; + +void initUartData(uint n) { + uartD[n].data = 0UL; + uartD[n].status = 0; // including data ready, overrun, parity and framing errors + uartD[n].timeCrossings = 0; + } + +// Write an output word for Serial BFSK modem +void writeUartData(uint32_t _data, uint8_t _status, int32_t _timeCrossings) { + bufferSpace = 16L - uartWriteIndex + uartReadIndex; //0 to 16 amount of space available + if(bufferSpace > 0) + { + uartD[uartWriteIndex&uartDMask].data = _data; + uartD[uartWriteIndex&uartDMask].status = _status; + uartD[uartWriteIndex&uartDMask].timeCrossings = _timeCrossings; + uartWriteIndex++; // Bump write index + saveStatus &= ~ERR_OVERRUN; // Clear overrun bit, it has finally been sent + } + else // No room in buffer + { + saveStatus |= ERR_OVERRUN; // Error gets sent when overrun is cleared + } + bufferSpace = 16L - uartWriteIndex + uartReadIndex; + } + +// Thanks svicent. NEEDS TO BE ADDED TO writeUartData() <<<---- +uint8_t oddParity(uint32_t ino) { + uint8_t n8 = 0; + while(ino != 0) + { + n8++; + ino &= (ino-1); // the loop will execute once for each bit of ino set + } + /* if n8 is odd, least significant bit will be 1 */ + return (n8 & 1); + } + +}; // End class UART_F32 +#endif diff --git a/docs/index.html b/docs/index.html index 079dbd5..55bd2f5 100644 --- a/docs/index.html +++ b/docs/index.html @@ -380,11 +380,7 @@ span.mainfunction {color: #993300; font-weight: bolder} {"type":"AudioFilter90Deg_F32","data":{"defaults":{"name":{"value":"new"}},"shortName":"filter90deg","inputs":"2","output":"2","category":"filter-function","color":"#E6E0F8","icon":"arrow-in.png","outputs":"2"}}, {"type":"AudioFilterBiquad_F32","data":{"defaults":{"name":{"value":"new"}},"shortName":"biquad","inputs":"1","output":"0","category":"filter-function","color":"#E6E0F8","icon":"arrow-in.png","outputs":"1"}}, {"type":"AudioFilterFIR_F32","data":{"defaults":{"name":{"value":"new"}},"shortName":"fir","inputs":"1","output":"0","category":"filter-function","color":"#E6E0F8","icon":"arrow-in.png","outputs":"1"}}, - - {"type":"AudioFilterConvolution_F32","data":{"defaults":{"name":{"value":"new"}},"shortName":"convFilt","inputs":"1","output":"0","category":"filter-function","color":"#E6E0F8","icon":"arrow-in.png","outputs":"1"}}, - - {"type":"AudioLMSDenoiseNotch_F32","data":{"defaults":{"name":{"value":"new"}},"shortName":"LMS","inputs":"1","output":"0","category":"filter-function","color":"#E6E0F8","icon":"arrow-in.png","outputs":"1"}}, {"type":"AudioSpectralDenoise_F32","data":{"defaults":{"name":{"value":"new"}},"shortName":"Spectral","inputs":"1","output":"0","category":"filter-function","color":"#E6E0F8","icon":"arrow-in.png","outputs":"1"}}, {"type":"AudioFilterFreqWeighting_F32","data":{"defaults":{"name":{"value":"new"}},"shortName":"freqWeight","inputs":"NaN","output":"0","category":"filter-function","color":"#E6E0F8","icon":"arrow-in.png","outputs":"NaN"}}, @@ -407,7 +403,6 @@ span.mainfunction {color: #993300; font-weight: bolder} {"type":"AudioInputUSB_F32","data":{"defaults":{"name":{"value":"new"}},"shortName":"audioInUSB","inputs":"0","output":"0","category":"input-function","color":"#E6E0F8","icon":"arrow-in.png","outputs":"2"}}, {"type":"AudioOutputUSB_F32","data":{"defaults":{"name":{"value":"new"}},"shortName":"audioOutUSB","inputs":"2","output":"0","category":"output-function","color":"#E6E0F8","icon":"arrow-in.png","outputs":"0"}}, {"type":"AudioOutputSPDIF3_F32","data":{"defaults":{"name":{"value":"new"}},"shortName":"spdif3Out","inputs":2,"outputs":0,"category":"output-function","color":"#E6E0F8","icon":"arrow-in.png"}}, - {"type":"AudioPlayQueue_F32","data":{"defaults":{"name":{"value":"new"}},"shortName":"playQueue","inputs":"0","output":"0","category":"play-function","color":"#E6E0F8","icon":"arrow-in.png","outputs":"1"}}, {"type":"AudioRecordQueue_F32","data":{"defaults":{"name":{"value":"new"}},"shortName":"recordQueue","inputs":"1","output":"0","category":"record-function","color":"#E6E0F8","icon":"arrow-in.png","outputs":"0"}}, {"type":"AudioSynthNoisePink_F32","data":{"defaults":{"name":{"value":"new"}},"shortName":"noisePink","inputs":"0","output":"0","category":"synth-function","color":"#E6E0F8","icon":"arrow-in.png","outputs":"1"}}, @@ -416,12 +411,18 @@ span.mainfunction {color: #993300; font-weight: bolder} {"type":"AudioSynthWaveform_F32","data":{"defaults":{"name":{"value":"new"}},"shortName":"waveform","inputs":"0","output":"0","category":"synth-function","color":"#E6E0F8","icon":"arrow-in.png","outputs":"1"}}, {"type":"AudioSynthNoiseWhite_F32","data":{"defaults":{"name":{"value":"new"}},"shortName":"noiseWhite","inputs":"0","output":"0","category":"synth-function","color":"#E6E0F8","icon":"arrow-in.png","outputs":"1"}}, {"type":"AudioSynthGaussian_F32","data":{"defaults":{"name":{"value":"new"}},"shortName":"GaussianWhiteNoise","inputs":"0","output":"0","category":"synth-function","color":"#E6E0F8","icon":"arrow-in.png","outputs":"1"}}, - {"type":"AudioAlignLR_F32","data":{"defaults":{"name":{"value":"new"}},"shortName":"alignLR","inputs":"2","output":"0","category":"input-function","color":"#E6E0F8","icon":"arrow-in.png","outputs":"3"}}, - {"type":"RadioFMDetector_F32","data":{"defaults":{"name":{"value":"new"}},"shortName":"FMDetector","inputs":"1","output":"0","category":"radio-function","color":"#E6E0F8","icon":"arrow-in.png","outputs":"2"}}, {"type":"radioModulatedGenerator_F32","data":{"defaults":{"name":{"value":"new"}},"shortName":"Modulator","inputs":"2","output":"0","category":"radio-function","color":"#E6E0F8","icon":"arrow-in.png","outputs":"2"}}, {"type":"radioNoiseBlanker_F32","data":{"defaults":{"name":{"value":"new"}},"shortName":"NoiseBlank","inputs":"2","output":"0","category":"radio-function","color":"#E6E0F8","icon":"arrow-in.png","outputs":"2"}}, + + {"type":"RadioFMDiscriminator_F32","data":{"defaults":{"name":{"value":"new"}},"shortName":"FMDiscrim","inputs":"1","output":"0","category":"radio-function","color":"#E6E0F8","icon":"arrow-in.png","outputs":"2"}}, + {"type":"radioBFSKModulator_F32","data":{"defaults":{"name":{"value":"new"}},"shortName":"BFSKMod","inputs":"0","output":"0","category":"radio-function","color":"#E6E0F8","icon":"arrow-in.png","outputs":"1"}}, + {"type":"UART_F32","data":{"defaults":{"name":{"value":"new"}},"shortName":"UART","inputs":"1","output":"0","category":"radio-function","color":"#E6E0F8","icon":"arrow-in.png","outputs":"0"}}, + + + + {"type":"RadioIQMixer_F32","data":{"defaults":{"name":{"value":"new"}},"shortName":"I-QMixer","inputs":"2","output":"0","category":"radio-function","color":"#E6E0F8","icon":"arrow-in.png","outputs":"2"}}, {"type":"AudioControlSGTL5000","data":{"defaults":{"name":{"value":"new"}},"shortName":"sgtl5000","inputs":0,"outputs":0,"category":"control-function","color":"#E6E0F8","icon":"arrow-in.png"}}, {"type":"AudioControlAK4558","data":{"defaults":{"name":{"value":"new"}},"shortName":"ak4558","inputs":0,"outputs":0,"category":"control-function","color":"#E6E0F8","icon":"arrow-in.png"}}, @@ -3131,6 +3132,280 @@ The actual packets are taken + + + + +
+ + + + + + + + + + + +