New functionality for BFSK

pull/16/head
boblark 2 years ago
parent aea942d855
commit da88765282
  1. 3
      OpenAudio_ArduinoLibrary.h
  2. 2
      RadioFMDetector_F32.cpp
  3. 155
      RadioFMDiscriminator_F32.cpp
  4. 304
      RadioFMDiscriminator_F32.h
  5. 154
      UART_F32.cpp
  6. 216
      UART_F32.h
  7. 289
      docs/index.html
  8. 158
      examples/BFSK/BFSK.ino
  9. 160
      examples/BFSK_random/BFSK_random.ino
  10. BIN
      examples/BFSK_random/BFSKrandom.gnumeric
  11. 148
      examples/BFSK_snr/BFSK_snr.ino
  12. BIN
      gui/DesignTool_F32.zip
  13. 101
      radioBFSKmodulator_F32.cpp
  14. 269
      radioBFSKmodulator_F32.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

@ -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

@ -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; //<<<<<<<<FIX <<<<<<
// blockA->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; k<blockIn->length; 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; i<block_size; i++)
sumNoise += fabsf(blockIn->data[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
}

@ -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

@ -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; i<block_size; i++)
{
sClock++; // Endless 32 bit count of audio sample periods (27 hours at 44.1 kHz)
dataIn = blockIn->data[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);
}

@ -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

@ -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
</div>
</script>
<div>
<script type="text/x-red" data-help-name="RadioFMDiscriminator_F32">
<!-- ============ RadioFMDiscriminator_F32 ========= -->
<h3>Summary</h3>
<div class=tooltipinfo>
<p>An FM Discriminator suitable for work at an low frequency, such
as 15 kHz. The DSP functionality is the same as that of a classic
2-tuned circuit analog discrimiator. The center frequenct of the tuned circuits
and their Q is programmable. An output low-pass filter is included. A squelch
allows for silencing noise when signals are not present.
For deviations in the 10 kHz range or less. Not for wideband
broadcast FM. No bandpass filtering is supplied for the input signals and noise.
That filtering is often desireable and may be added using AudioFilterFIRGeneral_F32.
</p>
</div>
<h3>Boards Supported</h3>
<ul>
<li>Teensy 3.2
<li>Teensy 3.5
<li>Teensy 3.6
<li>Teensy 4.0
<li>Teensy 4.1
</ul>
<h3>Audio Connections</h3>
<table class=doc align=center cellpadding=3>
<tr class=top><th>Port</th><th>Purpose</th></tr>
<tr class=odd><td align=center>In 0</td><td>Input Signal</td></tr>
<tr class=odd><td align=center>Out 0</td><td>De-modulated Output Signal</td></tr>
<tr class=odd><td align=center>Out 1</td><td>De-modulated Output Signal, squelched</td></tr>
</table>
<h3>Functions</h3>
<p class=func><span class=keyword>initializeFMDiscriminator</span>
(<strong>float</strong> f1,<strong> float</strong> f2,<strong> float</strong> q1,<strong> float</strong> q2);</p>
<p class=desc></p>
<p>Designs the discriminator "circuit" where f1 and f2
are the resonant frequencies of the two resonators and q1 and q2 are the associated Q factors.
The Q factors control the width of the peaks at frequencies f1 and f2.</p>
<p class=func><span class=keyword>filterOut
</span>(<strong>float32_t</strong> *firCoeffs, <strong>uint</strong> nFIR,
<strong>float32_t</strong> *firCoeffs,<strong>float32_t</strong> Kdem);</p>
<p>This sets output filtering where:
<pre class="desc">
float32_t* firCoeffs pointer to array of coefficients
uint nFIR is the number of coefficients
float32_t* fir_State_Out pointer to float32_t array
float32_t Kdem is the de-emphasis frequency factor
Kdem = 1/(0.5+(tau*fsample))
tau is the de-emphasis time constant,
typically 0.0005 second and fsample
the sample frequency, typically 44117.
</pre>
<p>Calling this function enables the use of a FIR output filter
and will cancel any IIR output filter. firCoeffs points to an INO supplied array
of FIR coefficients. fir_State_Out points to a float32_t storage area, again
supplied by the INO. The size of this array is 128+nFIR. If the block size
in settings has been changed from 128, then that new size should replace 128.</p>
<p class=func><span class=keyword>filterOutIIR
</span>(<strong>float</strong> frequency, <strong>float32_t</strong> q, <strong>float32_t</strong> kdem);</p>
<p>This sets the detector output filtering where:
<pre class="desc">
float32_t frequency is the LPF cutoff in Hz.
float32_t q is the loss factor for the LPF, typically 0.7.
</pre>
<p>Calling this function enables the use of a IIR output filter
and will cancel any FIR output filter.</p>
<p class=func><span class=keyword>setSquelchThreshold</span>(<strong>float</strong> sqThresh);</p>
<p class=desc></p>
<p>Sets the squelch threshold ranging 0.0 to 1.0 where
0.0 always lets audio through.</p>
<p class=func><span class=keyword>setSquelchDecay </span>(<strong>float</strong> sqDecay);</p>
<p class=desc></p>
<p>Sets the decay rate of the output of the squelch detector. This produces the "squelch tail."
The range is 0.9 (no real tail) to 0.9999 (very slow decay and a long squelch tail.)
The default value is 0.99.</p>
<p class=func><span class=keyword>getSquelchLevel</span>();</p>
<p class=desc></p>
<p>Returns the current measured squelch level as a <strong>float. </strong>
Higher levels, towards 1.0, are no signal. A low return value indicates presence of
a signal.</p>
<p class=func><span class=keyword>setSquelchFilter</span>(<strong>float* </strong>Coefficients);</p>
<p class=desc></p>
<p>This allows changing the 2-section (4-pole) bandpass BiQuad filter used before the
squelch detector. This passes high-frequency noise and attenuates lower frequency voice. The
parameter "Coefficients" points to an array of 10 floating point numbers. A Coefficient value
of NULL restores the default 3 to 5 kHz filter. See the example ReceiverFM.ino for using this.</p>
<h3>Examples</h3>
<p class=exam>File &gt; Examples &gt; OpenAudio_ArduinoLibrary &gt; BFSK
</p>
<p class=exam>File &gt; Examples &gt; OpenAudio_ArduinoLibrary &gt; BFSK_random
</p>
<p class=exam>File &gt; Examples &gt; OpenAudio_ArduinoLibrary &gt; BFSK_snr
</p>
<h3>Notes</h3>
<p>No input bandpass filtering is included. This may be desireable and should
be provided as a separate FIR filter block. The input level is non-critical
as a limiter is provided at the discriminator input. Limiting is centered on
a 0.0 input level.</p>
<p>This uses 45 microseconds for an 128 point update with Teensy 4.x.</p>
<P>The output can be FIR filtered using default parameters,
or using coefficients from an array. A separate single pole de-emphasis filter
is included that again can be programmed.</P>
<p>Two forms of object creation are possible.
RadioFMDiscriminator_F32() uses default sample rate and block size.
Non-standard values are possible with
RadioFMDiscriminator_F32(AudioSettings_F32 &settings).</p>
<p>The RadioFMDiscriminator_F32.h file has more information.</p>
</script>
<script type="text/x-red" data-template-name="RadioFMDiscriminator_F32">>
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>
</script>
<script type="text/x-red" data-help-name="UART_F32">
<h3>Summary</h3>
<div class=tooltipinfo>
<p>Receive base-band digital data as audio samples and convert these to digital
0,1 data. Using Universal Asynchronous Receive Transmit (UART) receive
rules, generate data words. These data words are available to the INO
via a 16-word FIFO buffer</p>
</div>
<h3>Audio Connections</h3>
<table class=doc align=center cellpadding=3>
<tr class=top><th>Port</th><th>Purpose</th></tr>
<tr class=odd><td align=center>In 0</td><td>Baseband signal to be digitized</td></tr>
</table>
<h3>Functions</h3>
<p class=func><span class=keyword>setUART</span>
(<strong>uint32_t</strong> cTauI, <strong>uint32_t</strong> cTauHalfI,
<strong>uint16_t</strong> nBits, <strong>uint16_t</strong> nParity,
<strong>uint16_t</strong> nStop);</p>
<p class=desc>This sets the UART parameters. The cTauI is the number of
audio samples for each bit period. This is normally found
as the bit sample rate divided by the UART bit rate. cTauHalfI
is half that number. Both are quantized to integer values.</p>
<p class=func><span class=keyword>getNDataBuffer</span>();</p>
<p class=desc>Returns the number of unread data words as uint32_t. </p>
<p class=func><span class=keyword>readUartData</span>();</p>
<p class=desc>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. The structure is as follows:
<pre class="desc">
struct uartData {
uint32_t data;
uint8_t status;
int32_t timeCrossings;
};
</pre>
</p>
<p class=func><span class=keyword>setInputOffset</span>(<strong>float32_t</strong> inputOffset);</p>
<p class=desc>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.
</p>
<p class=func><span class=keyword>setSampleRate_Hz</span>
(<strong>float32_t</strong> sampleRate_Hz)</p>
<p class=desc>Enter sample rate, for this class only, as float32_t.
</p>
<h3>Examples</h3>
<p class=exam>File &gt; Examples &gt; OpenAudio_ArduinoLibrary &gt; BFSK
</p>
<p class=exam>File &gt; Examples &gt; OpenAudio_ArduinoLibrary &gt; BFSK_random
</p>
<p class=exam>File &gt; Examples &gt; OpenAudio_ArduinoLibrary &gt; BFSK_snr
</p>
<h3>Notes</h3>
<p>This is only the receive portion of the UART. The transmit function is often
best integrated with the transmit modulator. This UART parallels the receive
functions of common devices going back to fully hardware implementations. To support
play with the function, the data words can be any length up to 32 bits.
</p>
<p>Parity is not yet implemented.</p>
</script>
<script type="text/x-red" data-template-name="UART_F32">
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>
</script>
<script type="text/x-red" data-help-name="radioBFSKModulator_F32">
<h3>Summary</h3>
<div class=tooltipinfo>
<p>Transmits Binary (2-frequency) Frequency Shift Keyed signals (BFSK).
The data words are supplied by the INO via a 64-word FIFO transmit buffer.
Optional FIR filtering of the input to the modulator is provided.
</p>
</div>
<h3>Audio Connections</h3>
<table class=doc align=center cellpadding=3>
<tr class=top><th>Port</th><th>Purpose</th></tr>
<tr class=odd><td align=center>Out 0</td><td>BFSK transmit audio or I-F</td></tr>
</table>
<h3>Functions</h3>
<p class=func><span class=keyword>setBFSK</span>
(<strong>float32_t</strong> bitRate, <strong>uint16_t</strong> numBits,
<strong>float32_t</strong> f0, <strong>float32_t</strong> f1);</p>
<p class=desc>This sets the BFSK modulator parameters. The bitRate is
obvious, but numBits includes start and stop bits so that to
transmit 8N1 numBits should be 10.
The tone frequencies are in Hz and can be any values up to fs/2.
IMPORTANT: Before calling this initialize function, call any functions
that set filters.</p>
<p class=func><span class=keyword>bufferHasSpace</span>();</p>
<p class=desc>Returns a bool value true if another data word can be sent. </p>
<p class=func><span class=keyword>sendData</span>(<strong>uint32_t</strong> data);</p>
<p class=desc>Empties the FIFO transmit buffer.</p>
<p class=func><span class=keyword>clearBuffer</span>();</p>
<p class=desc>Returns a bool value true if another data word can be sent. </p>
<p class=func><span class=keyword>amplitude</span>(<strong>float32_t</strong> a);</p>
<p class=desc>Sets a, the zero-to-peak amplitude of the transmit signal.
No return value, i.e., void. </p>
<p class=func><span class=keyword>setLPF
</span>(<strong>float32_t*</strong> FIRdata, <strong>float32_t*</strong> FIRcoeff, <strong>uint16_t</strong> numCoeffs);</p>
<p>This sets output filtering where:
<pre class="desc">
float32_t* FIRdata is a pointer to array data storage
float32_t* firCoeffs is a pointer to array of coefficients
uint numCoeffs is the number of FIR filter coefficients
</pre>
To omit the FIR filter, enter the pointer to the coefficients as NULL.
<p class=func><span class=keyword>setSampleRate_Hz</span>(<strong>float32_t</strong> sampleRate_Hz)</p>
<p class=desc>Enter sample rate, as float32_t.
It applies to this class only.
</p>
<h3>Examples</h3>
<p class=exam>File &gt; Examples &gt; OpenAudio_ArduinoLibrary &gt; BFSK
</p>
<p class=exam>File &gt; Examples &gt; OpenAudio_ArduinoLibrary &gt; BFSK_random
</p>
<p class=exam>File &gt; Examples &gt; OpenAudio_ArduinoLibrary &gt; BFSK_snr
</p>
<h3>Notes</h3>
<p>This modulator includes a lowpass filter on the input bit data.
This can be very effective in restricting the bandwidth of the BFSK output.
This filter can be None (default) or an arbitrary FIR filter.
</p>
<p>Parity is not yet implemented.</p>
</script>
<script type="text/x-red" data-template-name="radioBFSKModulator_F32">
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>
</script>
<div>
<script type="text/x-red" data-help-name="radioModulatedGenerator_F32">
<!-- ============ radioModulatedGenerator_F32 ========= -->

@ -0,0 +1,158 @@
/*
* BFSK.ino Test the BFSK at 1200 baud with repeated data.
* The Serial Monitor should print:
OpenAudio_ArduinoLibrary - Test BFSK
Resulting audio samples per data bit = 40
0The quick brown fox jumped...
1The quick brown fox jumped...
2The quick brown fox jumped...
3The quick brown fox jumped...
4The quick brown fox jumped...
Data send and receive complete
*
* F32 library
* Bob Larkin 5 June 2022
* Public Domain
*/
#include "OpenAudio_ArduinoLibrary.h"
#include "AudioStream_F32.h"
#include <Audio.h>
float* pDat = NULL;
float32_t fa, fb, delf, dAve; // For sweep
struct uartData* pData;
float32_t inFIRCoef[200];
float32_t inFIRadb[100];
float32_t inFIRData[528];
float32_t inFIRrdb[500];
// LPF FIR for 1200 baud
static float32_t LPF_FIR_Sinc[40] = {
0.025f, 0.025f, 0.025f, 0.025f, 0.025f, 0.025f, 0.025f, 0.025f,
0.025f, 0.025f, 0.025f, 0.025f, 0.025f, 0.025f, 0.025f, 0.025f,
0.025f, 0.025f, 0.025f, 0.025f, 0.025f, 0.025f, 0.025f, 0.025f,
0.025f, 0.025f, 0.025f, 0.025f, 0.025f, 0.025f, 0.025f, 0.025f,
0.025f, 0.025f, 0.025f, 0.025f, 0.025f, 0.025f, 0.025f, 0.025f};
// The next needs to be 128 + the size of the FIR coefficient array
float32_t FIRbuffer[128+40];
// T3.x supported sample rates: 2000, 8000, 11025, 16000, 22050, 24000, 32000, 44100, 44117, 48000,
// 88200, 88235 (44117*2), 95680, 96000, 176400, 176470, 192000
// T4.x supports any sample rate the codec will handle.
const float sample_rate_Hz = 48000.0f ; // 24000, 44117, or other frequencies listed above (untested)
const int audio_block_samples = 128; // Others untested
AudioSettings_F32 audio_settings(sample_rate_Hz, audio_block_samples); // Not used
RadioBFSKModulator_F32 modulator1(audio_settings);
AudioSynthGaussian_F32 gwn1;
AudioMixer4_F32 mixer4_1;
AudioFilterFIRGeneral_F32 inputFIR;
RadioFMDiscriminator_F32 fmDet1(audio_settings);
UART_F32 uart1(audio_settings);
AudioAnalyzeRMS_F32 rms1;
AudioOutputI2S_F32 audioOutI2S1(audio_settings);
AudioConnection_F32 patchCord1(modulator1, 0, mixer4_1, 0);
AudioConnection_F32 patchCord2(gwn1, 0, mixer4_1, 1);
AudioConnection_F32 patchCord4(mixer4_1, 0, inputFIR, 0);
AudioConnection_F32 patchCord5(inputFIR, 0, rms1, 0);
AudioConnection_F32 patchCord7(inputFIR, 0, fmDet1, 0);
AudioConnection_F32 patchcord8(fmDet1, 0, uart1, 0);
AudioControlSGTL5000 sgtl5000_1;
void setup() {
uint32_t spdb;
static float32_t snrDB = 15.0f;
static uint16_t dm0;
static uint32_t nn, ii;
static char ch[32];
static uint32_t t = 0UL;
Serial.begin(300); // Any value, it is not used
delay(1000);
Serial.println("OpenAudio_ArduinoLibrary - Test BFSK");
AudioMemory_F32(30, audio_settings);
// Enable the audio shield, select input, and enable output
sgtl5000_1.enable(); //start the audio board
spdb = modulator1.setBFSK(1200.0f, 10, 1200.0f, 2200.0f);
modulator1.setLPF(NULL, NULL, 0); // No LPF
modulator1.amplitude(1.00f);
Serial.print("Resulting audio samples per data bit = ");
Serial.println(spdb);
mixer4_1.gain(0, 1.0f); // Modulator in
mixer4_1.gain(1, 1.0f); // Gaussian noise in
// Design a bandpass filter to limit the input to the FM discriminator
for(int jj=0; jj<12; jj++) inFIRadb[jj] = -100.0f;
for(int jj=3; jj<=11; jj++) inFIRadb[jj] = 0.0f;
for(int jj=12; jj<100; jj++) inFIRadb[jj] = -100.0f;
inputFIR.FIRGeneralNew(inFIRadb, 200, inFIRCoef, 40.0f, inFIRData);
fmDet1.filterOutFIR(LPF_FIR_Sinc, 40, FIRbuffer, 0.99f); // Precede initialize
fmDet1.initializeFMDiscriminator(1100.0f, 2350.0f, 2.0f, 3.0f);
uart1.setUART(40, 20, 8, PARITY_NONE, 1);
// See BFSK_random.ino for details of S/N measurement
// Signal power = 470.831299, 5625
// Noise power = 471.335632, 5625
// S/N in dB for S set to 0.414476 and N set to 1.0f: -0.0046
// S/N=7 dB marginal but S/N=14 dB is solid
snrDB = 14.0f;
modulator1.amplitude(pow(10.0, 0.05f*(snrDB-7.65f)));
gwn1.amplitude(1.0f);
// Send a little data, five of these:
// index ii 0 10 20 30
// v v v v
strcpy(ch, "0The quick brown fox jumped...\n"); // 32 char including ending 0
ii = 0; nn = 0;
modulator1.bufferClear();
delay(40);
// Get UART synced up
if( modulator1.bufferHasSpace() )
modulator1.sendData(0X200); // 0X00
if( modulator1.bufferHasSpace() )
modulator1.sendData(0X200);
while(nn < 5)
{
if( modulator1.bufferHasSpace() )
{
// Serial.print("Send"); Serial.println((char)ch[ii]);
dm0 = (uint16_t)ch[ii++];
if(ii>30) // Sends all including \n, but not the string zero.
{
ii=0;
nn++;
ch[0]++; // Left hand character, 0 to 4
}
modulator1.sendData(0X200 | (dm0 << 1)); // Format ASCII to 8N1 serial
}
if(uart1.getNDataBuffer() > 0L)
{
pData = uart1.readUartData();
Serial.print((char)pData->data);
}
}
// Receive takes longer than transmit, so wait for the rest.
t = millis();
while((millis() - t) < 2000UL) // Wait few seconds...
{
if(uart1.getNDataBuffer() > 0L)
{
pData = uart1.readUartData();
Serial.print((char)pData->data);
}
}
Serial.println("Data send and receive complete");
}
void loop() {
}

@ -0,0 +1,160 @@
/*
* BFSK_random.ino Test the BFSK at 1200 baud with random data
* to determine byte error rate. Vary S/N. A slow process.
* F32 Teensy Audio Librarylibrary
* Bob Larkin 8 June 2022, Rev 15 June 2022
* Public Domain
*/
#include "OpenAudio_ArduinoLibrary.h"
#include "AudioStream_F32.h"
#include <Audio.h>
// Uncomment to see frequency response of input BPF:
// #define PRINT_BPF_FREQ_RESPONSE
int numberSamples = 0;
float* pDat = NULL;
float32_t fa, fb, delf, dAve; // For sweep
struct uartData* pData;
uint32_t errorCount, errorCountFrame;
float32_t inFIRCoef[200];
float32_t inFIRadb[100];
float32_t inFIRData[528];
float32_t inFIRrdb[500];
// A data storage FIFO for send data
float32_t xmitData[128];
int64_t indexIn = 0ULL;
// Correlation data
float32_t xcor[128];
// LPF FIR for 1200 baud
static float32_t LPF_FIR_Sinc[40] = {
0.025f, 0.025f, 0.025f, 0.025f, 0.025f, 0.025f, 0.025f, 0.025f,
0.025f, 0.025f, 0.025f, 0.025f, 0.025f, 0.025f, 0.025f, 0.025f,
0.025f, 0.025f, 0.025f, 0.025f, 0.025f, 0.025f, 0.025f, 0.025f,
0.025f, 0.025f, 0.025f, 0.025f, 0.025f, 0.025f, 0.025f, 0.025f,
0.025f, 0.025f, 0.025f, 0.025f, 0.025f, 0.025f, 0.025f, 0.025f};
float32_t LPF_FIR_State[128 + 40];
// T3.x supported sample rates: 2000, 8000, 11025, 16000, 22050, 24000, 32000, 44100, 44117, 48000,
// 88200, 88235 (44117*2), 95680, 96000, 176400, 176470, 192000
// T4.x supports any sample rate the codec will handle.
const float sample_rate_Hz = 48000.0f ; // 24000, 44117, or other frequencies listed above (untested)
const int audio_block_samples = 128; // Others untested
AudioSettings_F32 audio_settings(sample_rate_Hz, audio_block_samples); // Not used
RadioBFSKModulator_F32 modulator1(audio_settings);
AudioSynthGaussian_F32 gwn1;
AudioMixer4_F32 mixer4_1;
AudioFilterFIRGeneral_F32 inputFIR;
RadioFMDiscriminator_F32 fmDet1(audio_settings);
UART_F32 uart1(audio_settings);
AudioAnalyzeRMS_F32 rms1;
AudioOutputI2S_F32 audioOutI2S1(audio_settings);
AudioConnection_F32 patchCord1(modulator1, 0, mixer4_1, 0);
AudioConnection_F32 patchCord2(gwn1, 0, mixer4_1, 1);
AudioConnection_F32 patchCord4(mixer4_1, 0, inputFIR, 0);
AudioConnection_F32 patchCord5(inputFIR, 0, rms1, 0);
AudioConnection_F32 patchCord7(inputFIR, 0, fmDet1, 0);
AudioConnection_F32 patchcord8(fmDet1, 0, uart1, 0);
AudioControlSGTL5000 sgtl5000_1;
void setup() {
uint32_t spdb;
static uint16_t dm0;
static uint32_t nn;
Serial.begin(300); // Any value, it is not used
delay(1000);
Serial.println("OpenAudio_ArduinoLibrary - Test BFSK");
Serial.println("Byte error statistics with a random bit pattern.");
delay(1000);
AudioMemory_F32(30, audio_settings);
// Enable the audio shield, select input, and enable output
sgtl5000_1.enable(); //start the audio board
sgtl5000_1.inputSelect(AUDIO_INPUT_LINEIN); // or AUDIO_INPUT_MIC
modulator1.setLPF(NULL, NULL, 0); // No LPF
spdb = modulator1.setBFSK(1200.0f, 10, 1200.0f, 2200.0f);
modulator1.amplitude(1.00f);
Serial.print("Resulting audio samples per data bit = ");
Serial.println(spdb);
gwn1.amplitude(0.5f); // Set S/N
mixer4_1.gain(0, 1.0f); // Modulator in
mixer4_1.gain(1, 1.0f); // Gaussian noise in
// Design a bandpass filter to limit the input to the FM discriminator
for(int jj=0; jj<12; jj++) inFIRadb[jj] = -100.0f;
for(int jj=3; jj<=11; jj++) inFIRadb[jj] = 0.0f;
for(int jj=12; jj<100; jj++) inFIRadb[jj] = -100.0f;
inputFIR.FIRGeneralNew(inFIRadb, 200, inFIRCoef, 40.0f, inFIRData);
#ifdef PRINT_BPF_FREQ_RESPONSE
// Gather the data for a plot of the response. Output goes to Serial Monitor.
// I use highlighting and Ctrl-C to get the data for plotting.
Serial.println("\nResponse of Bandpass Filter ahead of the Discriminator in dB:");
inputFIR.getResponse(500, inFIRrdb);
for(int jj =0; jj<500; jj++)
{
Serial.print(48.0f * (int)jj); Serial.print(","); // Frequency, Hz
Serial.println(inFIRrdb[jj]); // Respnse in dB
}
Serial.println("----------------------------");
#endif
fmDet1.filterOutFIR(LPF_FIR_Sinc, 40, LPF_FIR_State, 0.99f);
fmDet1.initializeFMDiscriminator(1100.0f, 2350.0f, 2.0f, 3.0f);
uart1.setUART(40, 20, 8, PARITY_NONE, 1);
// Next we set the signal and noise
// amplitudes. The pow() equation allows us to enter the S/N directly.
// S/N in dB --v
modulator1.amplitude(pow(10.0, 0.05f*(0.00f-7.65f)));
gwn1.amplitude(1.0f); // Noise fixed, vary signal level
// See BFSKsnr.ino for details
// We can now evaluate the performance of the transmitter and receiver
// by varying the S/N and counting the number of data errors. The data
// will be set randomly over all 8 data bits.
// Thus we can compute error levels vs S/N in dB
for(float32_t snrDB=4.0f; snrDB<=11.0f; snrDB+=0.5f)
//for(float32_t snrDB=11.0f; snrDB<=13.5f; snrDB+=0.5f) // Use with nn=100000
{
modulator1.amplitude(pow(10.0f, 0.05f*(snrDB-7.65f)));
nn = 0;
errorCount = 0;
//while(nn<100000 && errorCount<1000) // Use for S/N > 11 dB
while(nn<10000 && errorCount<1000)
{
if( modulator1.bufferHasSpace() )
{
dm0 = random(255); // Serial.println(dm0);
// Save a copy of sent data in circular buffer
xmitData[indexIn & 0X7F] = (float32_t)dm0;
indexIn++;
modulator1.sendData(0X200 | (dm0 << 1));
nn++;
}
if(uart1.getNDataBuffer() > 0)
{
pData = uart1.readUartData(); // Pointer to data structure
if( pData->data!=xmitData[(indexIn-65LL) & 0X7F] &&
pData->data!=xmitData[(indexIn-66LL) & 0X7F] )
{
errorCount++;
}
}
} // End, waiting for enough data
Serial.print("S/N= "); Serial.print(snrDB, 3);
Serial.print(", number= "); Serial.print(nn);
Serial.print(", errors= "); Serial.println(errorCount);
}
}
void loop() {
}

@ -0,0 +1,148 @@
/*
* BFSK_snr.ino Measure the BFSK at 1200 baud to determine
* power signal/noise ratio after band-pass filtering .
* F32 library
* Bob Larkin 8 June 2022
* Public Domain -
*/
#include "OpenAudio_ArduinoLibrary.h"
#include "AudioStream_F32.h"
#include <Audio.h>
int numberSamples = 0;
float* pDat = NULL;
float32_t fa, fb, delf, dAve; // For sweep
struct uartData* pData;
uint32_t nn, errorCount, errorCountFrame;
// Storage for the input BPF FIR filter
float32_t inFIRCoef[200];
float32_t inFIRadb[100];
float32_t inFIRData[528];
float32_t inFIRrdb[500]; // To calculate response
// T3.x supported sample rates: 2000, 8000, 11025, 16000, 22050, 24000, 32000, 44100, 44117, 48000,
// 88200, 88235 (44117*2), 95680, 96000, 176400, 176470, 192000
// T4.x supports any sample rate the codec will handle.
const float sample_rate_Hz = 48000.0f ; // 24000, 44117, or other frequencies listed above (untested)
const int audio_block_samples = 128; // Others untested
AudioSettings_F32 audio_settings(sample_rate_Hz, audio_block_samples); // Not used
RadioBFSKModulator_F32 modulator1(audio_settings);
AudioSynthGaussian_F32 gwn1;
AudioMixer4_F32 mixer4_1;
AudioFilterFIRGeneral_F32 inputFIR;
AudioAnalyzeRMS_F32 rms1;
AudioOutputI2S_F32 audioOutI2S1(audio_settings);
AudioConnection_F32 patchCord1(modulator1, 0, mixer4_1, 0);
AudioConnection_F32 patchCord2(gwn1, 0, mixer4_1, 1);
AudioConnection_F32 patchCord4(mixer4_1, 0, inputFIR, 0);
AudioConnection_F32 patchCord5(inputFIR, 0, rms1, 0);
AudioControlSGTL5000 sgtl5000_1;
void setup() {
uint32_t spdb;
float32_t pSig, pNoise;
uint32_t tt, nMeas;
Serial.begin(300); // Any value, it is not used
delay(1000);
Serial.println("OpenAudio_ArduinoLibrary - Test BFSK");
Serial.println("Statistics with a Fixed Bit Pattern");
AudioMemory_F32(30, audio_settings);
sgtl5000_1.enable(); //start the audio board (NU)
modulator1.setLPF(NULL, NULL, 0); // No LPF
modulator1.amplitude(1.00f);
spdb = modulator1.setBFSK(1200.0f, 10, 1200.0f, 2200.0f);
Serial.print("Audio samples per data bit = ");
Serial.println(spdb);
gwn1.amplitude(0.5f);
mixer4_1.gain(0, 1.0f); // Modulator in
mixer4_1.gain(1, 1.0f); // Gaussian noise in
// Design a bandpass filter to limit the input to the FM discriminator
for(int jj=0; jj<12; jj++) inFIRadb[jj] = -100.0f;
for(int jj=3; jj<=11; jj++) inFIRadb[jj] = 0.0f;
for(int jj=12; jj<100; jj++) inFIRadb[jj] = -100.0f;
inputFIR.FIRGeneralNew(inFIRadb, 200, inFIRCoef, 40.0f, inFIRData);
// This measures S/N. Set amplitudes and turn on either signal
// or noise using mixer gain. Next we set the signal and noise
// amplitudes. The pow() equation allows us to enter the S/N directly.
// S/N in dB --v
modulator1.amplitude(pow(10.0, 0.05f*(0.00f-7.65f)));
gwn1.amplitude(1.0f);
// Reversing the process, we directly measure the S/N at
// the output of the BPF, going to the discriminator input.
mixer4_1.gain(0, 1.0f); // Modulator in
mixer4_1.gain(1, 0.0f); // No Gaussian noise in
pingTransmit(); pingTransmit(); pingTransmit();
getPower();
nMeas=0;
tt = millis();
pSig = 0.0f;
pNoise = 0.0f;
while(millis()-tt < 15000) // 15 second average
{
pingTransmit(); // Send random data if there is room
float32_t pp = getPower();
if(pp >= 0.0)
{
pSig += pp;
nMeas++;
}
}
Serial.print("Signal power = ");
Serial.print(pSig, 6); Serial.print(", "); Serial.println(nMeas);
// Now repeat using noise, but no signal:
mixer4_1.gain(0, 0.0f); // No Modulator in
mixer4_1.gain(1, 1.0f); // Gaussian noise in
getPower();
nMeas=0;
tt = millis();
while(millis()-tt < 15000) // 15 second average
{
float32_t pp = getPower();
if(pp >= 0.0)
{
pNoise += pp;
nMeas++;
}
}
Serial.print("Noise power = ");
Serial.print(pNoise, 6); Serial.print(", "); Serial.println(nMeas);
Serial.print("S/N in dB for S set to 0.414476 and N set to 1.0f: ");
Serial.println(10.0*log10f(pSig/pNoise), 4);
// This code is omitted by #if 0, as it need not normally run.
// Results from running this code:
// Signal power = 470.831299, 5625
// Noise power = 471.335632, 5625
// S/N in dB for S set to 0.414476 and N set to 1.0f: -0.0046
}
void loop() {
}
// Used to calibrate S/N
float32_t getPower(void) {
float32_t p;
if(rms1.available())
{
p = rms1.read();
p *= p;
return p;
}
else
return -1.0f;
}
// The transmitter has a buffer and we can keep the tranmission
// running continuosly
void pingTransmit(void) {
if( modulator1.bufferHasSpace() )
modulator1.sendData(0X200 | (random(255) << 1));
}

Binary file not shown.

@ -0,0 +1,101 @@
/*
* radioBFSKmodulator_F32
*
* Created: Bob Larkin 17 March 2022
*
* License: MIT License. Use at your own risk. See corresponding .h file.
*
*/
#include "radioBFSKmodulator_F32.h"
// 513 values of the sine wave in a float array:
#include "sinTable512_f32.h"
void RadioBFSKModulator_F32::update(void) {
audio_block_f32_t *blockFSK;
uint16_t index, i;
float32_t vca;
float32_t a, b;
// uint32_t tt=micros();
blockFSK = AudioStream_F32::allocate_f32(); // Get the output block
if (!blockFSK) return;
// Note - If buffer is dry, there is a delay of up to 3 mSec. Should be OK
// Note: indexIn and indexOut will not change during this update(),
// as we have the interrupt.
if(atIdle && (indexIn - indexOut) > 0) // At idle and new buffer entry has arrived
{
atIdle = false;
bitSendCount = numBits + 1; // Force new start
samplePerBitCount = samplesPerDataBit + 1;
}
for (i=0; i < blockFSK->length; i++)
{
// Each data bit is an integer number of sample periods
if(samplePerBitCount >= samplesPerDataBit) // Time for either idle or new bit/word
{
if(bitSendCount < numBits) // Still sending a word, get next bit
{
vc = (uint16_t)(currentWord & 1);
currentWord = currentWord >> 1;
bitSendCount++;
samplePerBitCount = 0;
}
else if((indexIn - indexOut) > 0) // Is there another word ready?
{
atIdle = false;
indexOut++; // Just keeps on going, not circular
currentWord = dataBuffer[indexOut&indexMask];
vc = (uint16_t)(currentWord & 1); // Bit to send next
bitSendCount = 1U; // Count how many bits have been sent
currentWord = currentWord >> 1;
samplePerBitCount = 0;
}
else // No bits are available
{
atIdle = true;
vc = 1; //Idle at logic 1 (Make programmable?)
samplePerBitCount = samplesPerDataBit + 1; // Force revisit
}
}
if(FIRcoeff==NULL) // No LPF being used
vca = (float32_t)vc;
else
{
// Put latest data point intoFIR LPF buffer
indexFIRlatest++;
FIRdata[indexFIRlatest & indexFIRMask] = (float32_t)vc; // Add to buffer
// Optional FIR LPF
vca = 0.0f;
int16_t iiBase16 = (int16_t)(indexFIRlatest & indexFIRMask);
int16_t iiSize = (int16_t)(indexFIRMask + 1ULL);
for(int16_t k=0; k<numCoeffs; k++)
{
int16_t ii = iiBase16 - k;
if(ii < 0)
ii += iiSize;
vca += FIRcoeff[k]*FIRdata[ii];
}
}
// pre-multiply phaseIncrement[0]=kp*freq[0] and deltaPhase=kp*deltaFreq
currentPhase += (phaseIncrement[0] + vca*deltaPhase);
if (currentPhase > 512.0f) currentPhase -= 512.0f;
index = (uint16_t) currentPhase;
float32_t deltaPhase = currentPhase - (float32_t)index;
/* Read two nearest values of input value from the sin table */
a = sinTable512_f32[index];
b = sinTable512_f32[index+1];
blockFSK->data[i] = magnitude*(a+(b-a)*deltaPhase); // Linear interpolation
samplePerBitCount++;
}
AudioStream_F32::transmit(blockFSK);
AudioStream_F32::release (blockFSK);
// Serial.print(" "); Serial.println(micros()-tt);
}

@ -0,0 +1,269 @@
/*
* 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
Loading…
Cancel
Save