|
|
|
/*
|
|
|
|
* radioFT8Demodulator_F32.h Assembled by Bob Larkin 11 Sept 2022
|
|
|
|
*
|
|
|
|
* Note: Teensy 4.x Only, 3.x not supported
|
|
|
|
*
|
|
|
|
* (c) 2021 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.
|
|
|
|
*/
|
|
|
|
|
|
|
|
/* *** CREDITS ***
|
|
|
|
* There have been many contributors to this FT8 project. I will not
|
|
|
|
* catch them all, but a try is neeeded. I picked from
|
|
|
|
* multiple sources in making the transmission process consistent with the
|
|
|
|
* 128-block Teensy Audio Library. But, everything I did was a translation
|
|
|
|
* of existing software. The "specification" for FT8 is the paper, "The FT4
|
|
|
|
* and FT8 Communications Protocols," by Steve Franke, K9AN, Bill Somerville,
|
|
|
|
* G4WJS and Joe Taylor, K1JT, QEX July/August 2020, pp7-17. I was guided
|
|
|
|
* in the right direction for this implementation by Farhan, VU2ESE
|
|
|
|
* and Charley, W5BAA. This led to a pair of excellent Raspberry Pi sources,
|
|
|
|
* https://github.com/kgoba/ft8_lib and
|
|
|
|
* https://github.com/WB2CBA/W5BAA-FT8-POCKET-TERMINAL
|
|
|
|
* An FT8 derivative of the first web site, using the Raspberry Pi,
|
|
|
|
* is Farhan's sBitx:
|
|
|
|
* https://github.com/afarhan/sbitx
|
|
|
|
* For those not familiar with FT8, it is a widely used amateur radio
|
|
|
|
* communications format. A search of FT8 will lead to much information.
|
|
|
|
*
|
|
|
|
* Notes:
|
|
|
|
* 1. Sampling rate: The entire FT8 Demodulator runs at a decimated
|
|
|
|
* 6400 samples/sec. At this time, only 48 and 96 kHz sample rates are
|
|
|
|
* supported. For 48 kHz there is
|
|
|
|
* an interpolate to 96 kHz followed by decimation to 19.2 and 6.4 kHz.
|
|
|
|
* For 96 kHz there is no interpolation.
|
|
|
|
*
|
|
|
|
* 2. In order to process the decoding of FT8 as rapidly as possibly,
|
|
|
|
* after the 0.16 seconds of data are acquired (2048 data points),
|
|
|
|
* this library class only does the process that must be done in real
|
|
|
|
* time, either because of data un-availability or because the full data
|
|
|
|
* set for 0.16 sec is needed. Thus this class does the two stages
|
|
|
|
* of decimation in time needed to reduce the data rate and does the
|
|
|
|
* saving of signal samples to an array.
|
|
|
|
*
|
|
|
|
* 3. To allow two FFT's, offset in time by 0.16sec / 2, we need to gather
|
|
|
|
* 2048 data points every 0.08 seconds with 50% overlap in data.
|
|
|
|
* These are put into an array that is supplied by the calling
|
|
|
|
* INO/CPP program. It is arrange as signalData[2][1024]
|
|
|
|
* for convenience. The indices of the first of two is returned
|
|
|
|
* when data is available. In order to not need dual large storage, an
|
|
|
|
* 18 float auxiliary storage is provided to store data acquired during
|
|
|
|
* the "first two" update period (5.3 or 2.7 mSec for 48 or
|
|
|
|
* 96 kHz sample rate). This gives time for an FFT
|
|
|
|
* before altering the signal data.
|
|
|
|
*
|
|
|
|
* 4. This class does not provide true (absolute) clock timing.
|
|
|
|
* The startReceive() function should be called when it is time
|
|
|
|
* for a new reception period. This class will start that at the
|
|
|
|
* next 128 audio sample period.
|
|
|
|
*
|
|
|
|
* 5. Update time required for a T4.x is uSec.
|
|
|
|
*/
|
|
|
|
|
|
|
|
// The Charley Hill W5BAA_INTERFACE is from the Pocket-FT9 project and
|
|
|
|
// transfers data as 128 int's. It is included as a compile time
|
|
|
|
// option to allow testing with the Pocket-FT8 Teensy software.
|
|
|
|
// The default interface transfers the collected data as a pointer to
|
|
|
|
// 2048 F32 float's ready for windowing and FFT's. Both
|
|
|
|
// interfaces includes 50% overlap of the data to correct for window losses.
|
|
|
|
|
|
|
|
// Rev 16 Jan 2023 Corrected position of endif for T4.x only Bob
|
|
|
|
|
|
|
|
// #define W5BAA_INTERFACE
|
|
|
|
|
|
|
|
#ifndef radioFT8Demodulator_h_
|
|
|
|
#define radioFT8Demodulator_h_
|
|
|
|
|
|
|
|
#define SR_NONE 0
|
|
|
|
#define SR48000 1
|
|
|
|
#define SR96000 2
|
|
|
|
|
|
|
|
// *************** TEENSY 4.X ONLY ****************
|
|
|
|
#if defined(__IMXRT1062__)
|
|
|
|
|
|
|
|
#include "Arduino.h"
|
|
|
|
#include "AudioStream_F32.h"
|
|
|
|
#include "arm_math.h"
|
|
|
|
|
|
|
|
// NOTE Changed class name to start with capital "R" RSL 7 Nov 2022
|
|
|
|
|
|
|
|
class RadioFT8Demodulator_F32 : public AudioStream_F32 {
|
|
|
|
//GUI: inputs:2, outputs:4 //this line used for automatic generation of GUI node
|
|
|
|
//GUI: shortName:FFT2048IQ
|
|
|
|
public:
|
|
|
|
RadioFT8Demodulator_F32() : AudioStream_F32(1, inputQueueArray_f32) {
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
// There is no varient for "settings," as blocks other than 128 are
|
|
|
|
// not supported FIX. <<<<<<<<<<<<<<<<<<<<<
|
|
|
|
|
|
|
|
void initialize(void) {
|
|
|
|
// Initialize 2 FIR instances (ARM DSP Math Library)
|
|
|
|
//arm_fir_init_f32 (arm_fir_instance_f32 *S, uint16_t numTaps,
|
|
|
|
// const float32_t *pCoeffs, float32_t *pState, uint32_t blockSize)
|
|
|
|
arm_fir_init_f32(&fir_inst1, 55, &firDecimate1[0], &dec1FIRWork[0], 128UL);
|
|
|
|
arm_fir_init_f32(&fir_inst2, 167, &firDecimate2[0], &dec2FIRWork[0], 128UL);
|
|
|
|
// CHECK SAMPLE RATE AND SET index <<<<<<<<<<<<<
|
|
|
|
|
|
|
|
FT8DemodInit = true;
|
|
|
|
#ifdef W5BAA_INTERFACE
|
|
|
|
gettingData = true;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
// Following reflects that there are only 2 supported sample rates.
|
|
|
|
// IMPORTANT: This changes constants for FT8 Receive, only. It does not
|
|
|
|
// change system-wide audio sample rate. Use AudioSettings_F32.
|
|
|
|
void setSampleRate_Hz(const float32_t &fs_Hz) {
|
|
|
|
sampleRateHz = fs_Hz;
|
|
|
|
if(sampleRateHz>47900.0f && sampleRateHz<48100.0f)
|
|
|
|
{
|
|
|
|
sampleRateHz = 48000.0f;
|
|
|
|
srIndex = SR48000;
|
|
|
|
}
|
|
|
|
else if(sampleRateHz>95900.0f && sampleRateHz<96100.0f)
|
|
|
|
{
|
|
|
|
sampleRateHz = 96000.0f;
|
|
|
|
srIndex = SR96000;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
Serial.println("Unsupported sample rate, FT8 will not receive.");\
|
|
|
|
srIndex = SR_NONE;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void decimate15(void);
|
|
|
|
|
|
|
|
bool powerAvailable(void) {
|
|
|
|
if(powAvail) return true;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Read the dB power level *once* per 128 samples at 6.4kHz
|
|
|
|
float32_t powerRead(void) {
|
|
|
|
if(powAvail)
|
|
|
|
{
|
|
|
|
powAvail = false;
|
|
|
|
return powerOut;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
return -200.0f;
|
|
|
|
}
|
|
|
|
|
|
|
|
#ifdef W5BAA_INTERFACE
|
|
|
|
int queueAvailable(void) { // W5BAA_INTERFACE only
|
|
|
|
uint32_t h, t;
|
|
|
|
h = head;
|
|
|
|
t = tail; //Serial.print("@queueAvailable h, t= "); Serial.print(h); Serial.print(", "); Serial.println(t);
|
|
|
|
if (h >= t)
|
|
|
|
return h - t;
|
|
|
|
return max_buffers + h - t;
|
|
|
|
}
|
|
|
|
|
|
|
|
void queueClear(void) { // W5BAA_INTERFACE only
|
|
|
|
uint32_t t;
|
|
|
|
if (userblock)
|
|
|
|
{
|
|
|
|
AudioStream::release(userblock);
|
|
|
|
userblock = NULL;
|
|
|
|
}
|
|
|
|
t = tail;
|
|
|
|
while (t != head)
|
|
|
|
{
|
|
|
|
if (++t >= max_buffers)
|
|
|
|
t = 0;
|
|
|
|
AudioStream::release(queue[t]);
|
|
|
|
}
|
|
|
|
tail = t;
|
|
|
|
}
|
|
|
|
|
|
|
|
int16_t* queueReadBuffer(void) { // W5BAA_INTERFACE only
|
|
|
|
uint32_t t;
|
|
|
|
if (userblock)
|
|
|
|
return NULL;
|
|
|
|
t = tail;
|
|
|
|
if (t == head)
|
|
|
|
return NULL;
|
|
|
|
if (++t >= max_buffers)
|
|
|
|
t = 0;
|
|
|
|
userblock = queue[t];
|
|
|
|
tail = t;
|
|
|
|
return userblock->data; // Pointer to 128 int16_t of data
|
|
|
|
}
|
|
|
|
|
|
|
|
void queueFreeBuffer(void) { // W5BAA_INTERFACE only
|
|
|
|
if (userblock == NULL)
|
|
|
|
return;
|
|
|
|
AudioStream::release(userblock);
|
|
|
|
userblock = NULL;
|
|
|
|
}
|
|
|
|
#else
|
|
|
|
// Regular 512/2048 float interface
|
|
|
|
|
|
|
|
// Returns true when output data is available.
|
|
|
|
bool available() {
|
|
|
|
if (outputFlag == true)
|
|
|
|
{
|
|
|
|
outputFlag = false; // No double returns
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Start a new 14.7 sec data gather
|
|
|
|
void startDataCollect(void) {
|
|
|
|
gettingData = true;
|
|
|
|
FFTCount = 0;
|
|
|
|
dec1Count = 0;
|
|
|
|
dec2Count = 0;
|
|
|
|
kOffset1 = 0;
|
|
|
|
kOffset2 = 0;
|
|
|
|
outputFlag = false;
|
|
|
|
current128Used1 = false;
|
|
|
|
current128Used2 = false;
|
|
|
|
FFTCount = 0;
|
|
|
|
FFTOld = 0;
|
|
|
|
block128Count = 0;
|
|
|
|
index2 = 0; // Runs 0,511 on outputs
|
|
|
|
}
|
|
|
|
|
|
|
|
// Cancel the data gather
|
|
|
|
void cancelDataCollect(void) {
|
|
|
|
gettingData = false;
|
|
|
|
// Getting started again from here is by startDataCollect()
|
|
|
|
}
|
|
|
|
|
|
|
|
bool receivingData(void) {
|
|
|
|
return gettingData;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Return FFT Count, 0 = No data
|
|
|
|
// 1 to 184 = current 1024 words last returned
|
|
|
|
// The number is incremented every 80 mSec during receive
|
|
|
|
// Reset to 0 is by startDataCollect()
|
|
|
|
int getFFTCount(void) {
|
|
|
|
return FFTCount;
|
|
|
|
}
|
|
|
|
|
|
|
|
float32_t* getDataPtr(void) { // Location of input for FFT
|
|
|
|
return &data2K[0];
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
virtual void update(void);
|
|
|
|
|
|
|
|
private:
|
|
|
|
audio_block_f32_t *inputQueueArray_f32[1];
|
|
|
|
float sampleRateHz = AUDIO_SAMPLE_RATE;
|
|
|
|
|
|
|
|
int16_t srIndex = SR_NONE;
|
|
|
|
//uint16_t block_size = 128;
|
|
|
|
int16_t fcurrentArray = -1;
|
|
|
|
float32_t *p0 = NULL; // Pointers to 1024 storage
|
|
|
|
float32_t *p1 = NULL;
|
|
|
|
|
|
|
|
uint32_t ttt;
|
|
|
|
int32_t kkk;
|
|
|
|
|
|
|
|
bool FT8DemodInit = false;
|
|
|
|
|
|
|
|
bool outputFlag = false;
|
|
|
|
bool gettingData = false;
|
|
|
|
bool current128Used1 = false; // Decimation by 5
|
|
|
|
bool current128Used2 = false; // Decimation by 3
|
|
|
|
audio_block_f32_t *inputQueueArray[1];
|
|
|
|
int32_t dec1Count = 0;
|
|
|
|
int32_t dec2Count = 0;
|
|
|
|
float32_t dataIn[128];
|
|
|
|
|
|
|
|
float32_t inFIR1[128]; // Inputs for FIR1
|
|
|
|
// Working space for 55-tap FIR, 128 data points
|
|
|
|
float32_t dec1FIRWork[183];
|
|
|
|
float32_t outFIR1[128];
|
|
|
|
int kOffset1 = 0;
|
|
|
|
|
|
|
|
float32_t inFIR2[128];
|
|
|
|
// Working space for 167-tap FIR
|
|
|
|
float32_t dec2FIRWork[295];
|
|
|
|
float32_t outFIR2[128];
|
|
|
|
int kOffset2 = 0;
|
|
|
|
arm_fir_instance_f32 fir_inst1, fir_inst2;
|
|
|
|
|
|
|
|
bool powAvail = false;
|
|
|
|
float32_t powerOut = 0.0f;
|
|
|
|
float32_t outData[128]; // Storage after decimate by 3
|
|
|
|
#ifdef W5BAA_INTERFACE
|
|
|
|
static const int max_buffers = 48; // Enough for 3 FFT, i.e., plenty
|
|
|
|
audio_block_t* volatile queue[max_buffers];
|
|
|
|
audio_block_t* userblock;
|
|
|
|
volatile uint8_t head = 0;
|
|
|
|
volatile uint8_t tail = 0;
|
|
|
|
volatile uint8_t enabled = 0;
|
|
|
|
|
|
|
|
audio_block_t *block;
|
|
|
|
uint32_t h;
|
|
|
|
#else
|
|
|
|
int16_t FFTCount = 0;
|
|
|
|
int FFTOld = 0;
|
|
|
|
int16_t block128Count = 0;
|
|
|
|
int16_t index2 = 0; // Runs 0,511 on outputs
|
|
|
|
float32_t data2K[2048]; // Output time array to FFT 2048+512
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
// int64_t sampleCount = 0LL;
|
|
|
|
// bool sampleCountOdd = false;
|
|
|
|
|
|
|
|
/* FOR INFO ONLY
|
|
|
|
// Blackman-Harris produces a first sidelobe more than 90 dB down.
|
|
|
|
// The price is a bandwidth of about 2 bins. Very useful at times.
|
|
|
|
void useBHWindow(void) {
|
|
|
|
for (int i=0; i < 2048; i++) {
|
|
|
|
float kx = 0.00306946f; // 2*PI/2047
|
|
|
|
int ix = (float) i;
|
|
|
|
window[i] = 0.35875 -
|
|
|
|
0.48829*cosf( kx*ix) +
|
|
|
|
0.14128*cosf(2.0f*kx*ix) -
|
|
|
|
0.01168*cosf(3.0f*kx*ix);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
*/
|
|
|
|
|
|
|
|
/* FIR filter designed with http://t-filter.appspot.com
|
|
|
|
* Sampling frequency = 96000 Hz
|
|
|
|
* 0 Hz - 3200 Hz ripple = 0.065 dB
|
|
|
|
* 9600 Hz - 48000 Hz att = -81.1 dB */
|
|
|
|
float32_t firDecimate1[55] = { // constexpr static
|
|
|
|
0.000037640f, 0.000248621f, 0.000535682f, 0.001017563f, 0.001647298,
|
|
|
|
0.002359693f, 0.003009942f, 0.003388980f, 0.003245855f, 0.002335195,
|
|
|
|
0.000482309f, -0.002343975f, -0.005965316f, -0.009953093f, -0.013627779f,
|
|
|
|
-0.016110493f, -0.016426909f, -0.013654288f, -0.007090645f, 0.003582981f,
|
|
|
|
0.018178902f, 0.035947300f, 0.055606527f, 0.075464728f, 0.093619230f,
|
|
|
|
0.108205411f, 0.117656340f, 0.120928651f, 0.117656340f, 0.108205411f,
|
|
|
|
0.093619230f, 0.075464728f, 0.055606527f, 0.035947300f, 0.018178902f,
|
|
|
|
0.003582981f, -0.007090645f, -0.013654288f, -0.016426909f, -0.016110493f,
|
|
|
|
-0.013627779f, -0.009953093f, -0.005965316f, -0.002343975f, 0.000482309f,
|
|
|
|
0.002335195f, 0.003245855f, 0.003388980f, 0.003009942f, 0.002359693f,
|
|
|
|
0.001647298f, 0.001017563f, 0.000535682f, 0.000248621f, 0.000037640f};
|
|
|
|
|
|
|
|
/* FIR filter designed with http://t-filter.appspot.com
|
|
|
|
* Sampling frequency = 19200 Hz
|
|
|
|
* 0 Hz - 2800 Hz ripple = 0.073 dB
|
|
|
|
* 3200 Hz - 9600 Hz att = -80.0 dB */
|
|
|
|
float32_t firDecimate2[167] = {
|
|
|
|
0.000200074f, 0.000438821f, 0.000648425f, 0.000636175f, 0.000315069f,
|
|
|
|
-0.000193500f, -0.000591064f, -0.000600896f, -0.000202482f, 0.000301473f,
|
|
|
|
0.000486276f, 0.000152104f, -0.000466930f, -0.000846593f, -0.000601871f,
|
|
|
|
0.000140985f, 0.000780434f, 0.000717692f, -0.000092419f, -0.001015260f,
|
|
|
|
-0.001218073f, -0.000407595f, 0.000820258f, 0.001400419f, 0.000712814f,
|
|
|
|
-0.000783775f, -0.001824384f, -0.001392096f, 0.000312681f, 0.001894843f,
|
|
|
|
0.001890948f, 0.000105692f, -0.002046807f, -0.002639347f, -0.000937478f,
|
|
|
|
0.001779635f, 0.003148244f, 0.001757989f, -0.001441273f, -0.003731905f,
|
|
|
|
-0.002912877f, 0.000623820f, 0.003953823f, 0.004009904f, 0.000373714f,
|
|
|
|
-0.004041709f, -0.005284541f, -0.001864153f, 0.003610036f, 0.006359221f,
|
|
|
|
0.003566059f, -0.002829631f, -0.007374880f, -0.005699305f, 0.001364774f,
|
|
|
|
0.007956768f, 0.007979137f, 0.000650460f, -0.008163693f, -0.010542154f,
|
|
|
|
-0.003518210f, 0.007604450f, 0.013094981f, 0.007160556f, -0.006233941f,
|
|
|
|
-0.015716023f, -0.011940068f, 0.003541776f, 0.018116367f, 0.018029298f,
|
|
|
|
0.000861510f, -0.020344029f, -0.026351929f, -0.008277630f, 0.022137232f,
|
|
|
|
0.038780109f, 0.021608602f, -0.023552791f, -0.062590967f, -0.053225138f,
|
|
|
|
0.024375124f, 0.148263262f, 0.262405223f, 0.308632386f, 0.262405223f,
|
|
|
|
0.148263262f, 0.024375124f, -0.053225138f, -0.062590967f, -0.023552791f,
|
|
|
|
0.021608602f, 0.038780109f, 0.022137232f, -0.008277630f, -0.026351929f,
|
|
|
|
-0.020344029f, 0.000861510f, 0.018029298f, 0.018116367f, 0.003541776f,
|
|
|
|
-0.011940068f, -0.015716023f, -0.006233941f, 0.007160556f, 0.013094981f,
|
|
|
|
0.007604450f, -0.003518210f, -0.010542154f, -0.008163693f, 0.000650460f,
|
|
|
|
0.007979137f, 0.007956768f, 0.001364774f, -0.005699305f, -0.007374880f,
|
|
|
|
-0.002829631f, 0.003566059f, 0.006359221f, 0.003610036f, -0.001864153f,
|
|
|
|
-0.005284541f, -0.004041709f, 0.000373714f, 0.004009904f, 0.003953823f,
|
|
|
|
0.000623820f, -0.002912877f, -0.003731905f, -0.001441273f, 0.001757989f,
|
|
|
|
0.003148244f, 0.001779635f, -0.000937478f, -0.002639347f, -0.002046807f,
|
|
|
|
0.000105692f, 0.001890948f, 0.001894843f, 0.000312681f, -0.001392096f,
|
|
|
|
-0.001824384f, -0.000783775f, 0.000712814f, 0.001400419f, 0.000820258f,
|
|
|
|
-0.000407595f, -0.001218073f, -0.001015260f, -0.000092419f, 0.000717692f,
|
|
|
|
0.000780434f, 0.000140985f, -0.000601871f, -0.000846593f, -0.000466930f,
|
|
|
|
0.000152104f, 0.000486276f, 0.000301473f, -0.000202482f, -0.000600896f,
|
|
|
|
-0.000591064f, -0.000193500f, 0.000315069f, 0.000636175f, 0.000648425f,
|
|
|
|
0.000438821f, 0.000200074f};
|
|
|
|
|
|
|
|
}; // End class def
|
|
|
|
|
|
|
|
// endif for Teensy 4.x only:
|
|
|
|
#endif
|
|
|
|
|
|
|
|
// endif for single read of .h file:
|
|
|
|
#endif
|