parent
aea942d855
commit
da88765282
@ -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 |
@ -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() { |
||||||
|
} |
Binary file not shown.
@ -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…
Reference in new issue