/* * 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