// FT8Receive.ino 13 Oct 2022 Bob Larkin, W7PUA // Simple command-line reception of WSJT FT8 signals for // amateur radio. /* * Huge thanks to Charley Hill, W5BAA, for his Pocket FT8 work, much of which * is the basis for this INO: https://github.com/Rotron/Pocket-FT8 * That work started from the "FT8 Decoding Library" by * Karlis Goba: https://github.com/kgoba/ft8_lib and thank you to both * contributors. */ /* * See Examples/Teensy/TimeTeensy3.ino for * example code illustrating Time library with Real Time Clock. */ /* This INO uses the Teensy F32 Audio Library interface to test * the radioFT8Demodulator_F32 data collector. This should * not be used with "#define W5BAA_INTERFACE" in the file * radioFT8Demodulator_F32.h, as that interface is different. */ /* The F32 Audio library class, radioFT8Demodulator_F32, performs the * data collection and organization for FT8 reception. This occurs * after every 128 audio samples, automatically. The sync and decode * functions are time intensive and therefore not done with the audio * interrupts. A complete set of Karlis Goba FT8 reception files is * included with this INO and have been slightly renamed with an "R" * at the end. The Goba functions have stayed the same and minimal * changes are made to the functions. * * IMPORTANT -When one wants to build a full transmit and receive * FT8 INO, maybe with waterfall, they should go back to the Goba and * Hill files referenced above. This INO is intended as a minimal demonstration * of FT8 reception with emphasis on testing the radioFT8Demodulator_F32 * class. That said, be aware that these receive files include * snr estimation not available from the other sets. */ #include "Arduino.h" #include #include // Added for F32 Teensy library RSL #include #include "AudioStream.h" #include "arm_math.h" // The following debugging print dumps are available: // DEBUG1 - Main INO and overall control // DEBUG_N - Noise measurement data for snr estimates // DEBUG_D - Decode measurements // Uncomment the following for debugging: // #define DEBUG1 // #define DEBUG_N // #define DEBUG_D #define FFT_SIZE 2048 #define block_size 128 #define input_gulp_size 1024 #define HIGH_FREQ_INDEX 368 #define LOW_FREQ_INDEX 48 #define FT8_FREQ_SPACING 6.25 // ?? #define ft8_min_freq FT8_FREQ_SPACING * LOW_FREQ_INDEX #define ft8_msg_samples 92 // Alternate interface format. // #define W5BAA // Only 48 and 96 kHz audio sample rates are currently supported. const float32_t sample_rate_Hz = 48000.0f; const int audio_block_samples = 128; AudioSettings_F32 audio_settings(sample_rate_Hz, audio_block_samples); AudioInputI2S_F32 audioInI2S1(audio_settings); //xy=100,150 AudioEffectGain_F32 gain1; //xy=250,150 AudioAnalyzePeak_F32 peak1; //xy=400,250 radioFT8Demodulator_F32 demod1; //xy=400,150 AudioConnection_F32 patchCord1(audioInI2S1, 0, gain1, 0); AudioConnection_F32 patchCord2(gain1, demod1); AudioConnection_F32 patchCord3(gain1, peak1); AudioControlSGTL5000 sgtl5000_1; //xy=100,250 // This is the big file of log powers uint8_t export_fft_power[ft8_msg_samples*HIGH_FREQ_INDEX*4] ; // Pointer to 2048 float data for FFT array in radioDemodulator_F32 float32_t* pData2K = NULL; float32_t noisePowerEstimateL = 0.0f; // Works when big signals are absent int16_t noisePwrDBIntL = 0; float32_t noisePowerEstimateH = 0.0f; // Works for big signals and QRMt int16_t noisePwrDBIntH = 0; float32_t noisePeakAveRatio = 0.0f; // > about 100 for big sigs // /char Station_Call[11]; //six character call sign + /0 // /char home_Locator[11];; // four character locator + /0 // /char Locator[11]; // four character locator + /0 char Station_Call[11]; // six character call sign + /0 char home_Locator[11]; // four character locator + /0 char Locator[11]; // four character locator + /0 uint16_t currentFrequency; // Next 3 lines were uint32_t Sept 22 change to allow tOffset int32_t current_time, start_time, ft8_time, ft8_mod_time, ft8_mod_time_last; int32_t days_fraction, hours_fraction, minute_fraction; int32_t tOffset = 0; // Added Sept 22 uint8_t ft8_hours, ft8_minutes, ft8_seconds; int ft8_flag, FT_8_counter, ft8_marker, decode_flag; int num_decoded_msg; int xmit_flag, ft8_xmit_counter, Transmit_Armned; int DSP_Flag; // =1 if new data is ready for FFT int master_decoded; // rcvFT8State #define FT8_RCV_IDLE 0 #define FT8_RCV_DATA_COLLECT 1 #define FT8_RCV_FIND_POWERS 2 #define FT8_RCV_DECODE 3 int rcvFT8State = FT8_RCV_IDLE; int master_offset, offset_step; int Target_Flag = 0; //From gen_ft8.cpp char Target_Call[7]; //six character call sign + /0 char Target_Locator[5]; // four character locator + /0 int Target_RSL; // four character RSL + /0 // Define FT8 symbol counts int ND = 58; int NS = 21; int NN = 79; // Define the LDPC sizes int N = 174; int K = 91; int M = 83; int K_BYTES = 12; // Define CRC parameters uint16_t CRC_POLYNOMIAL = 0X2757; // CRC-14 polynomial without the leading (MSB) 1 int CRC_WIDTH = 14; // Communicate amongst decode functions: typedef struct Candidate { int16_t score; int16_t time_offset; int16_t freq_offset; uint8_t time_sub; uint8_t freq_sub; uint8_t alt; // Added for convenience Teensy, RSL float32_t syncPower; // Added for Teensy, RSL } Candidate; uint8_t secLast = 0; const int ledPin = 13; bool showPower = false; uint32_t tp = 0; uint32_t tu; uint32_t ct=0; void setup(void) { // set the Time library to use Teensy 4.x's RTC to keep time setSyncProvider(getTeensy3Time); AudioMemory_F32(50, audio_settings); Serial.begin(9600); delay(1000); // Enable the audio shield, select input, and enable output sgtl5000_1.enable(); sgtl5000_1.inputSelect(AUDIO_INPUT_LINEIN); #ifndef W5BAA pData2K = demod1.getDataPtr(); // 2048 floats in radioFT8Demodulator_F32 #endif demod1.initialize(); demod1.setSampleRate_Hz(48000.0f); init_DSP(); gain1.setGain(1.0); update_synchronization(); Serial.println("FT8 Receive test"); if (timeStatus()!= timeSet) Serial.println("Unable to sync with the RTC"); else Serial.println("RTC has set the system time"); //demod1.startDataCollect(); NOT FOR W5BAA interface } void loop(void) { int16_t inCmd; if( Serial.available() ) { inCmd = Serial.read(); if(inCmd=='=') //Set minute clock to zero { } else if(inCmd=='p' || inCmd=='P') // Increase clock 0.1 sec { tOffset += 100; Serial.println("Increase clock 0.1 sec"); Serial.println(0.001*(float)tOffset); } else if(inCmd=='l' || inCmd=='L') // Decrease clock 0.1 sec { tOffset -= 100; Serial.println("Decrease clock 0.1 sec"); Serial.println(0.001*(float)tOffset); } else if(inCmd=='-') // Increase clock 1 sec { tOffset += 1000; Serial.println("Increase clock 1 sec"); Serial.println(0.001*(float)tOffset); } else if(inCmd==',') // Decrease clock 1 sec { tOffset -= 1000; Serial.println("Decrease clock 1 sec"); Serial.println(0.001*(float)tOffset); } else if(inCmd=='c' || inCmd=='C') // Clock display { Serial.print("Time Offset, millisecods = "); Serial.println(tOffset); } else if(inCmd=='e' || inCmd=='E') // Toggle power display { showPower = !showPower; Serial.print("Show Power = "); Serial.println(showPower); } else if(inCmd=='?') { //Serial.println("= Set local FT-8 clock to 0 (60 sec range)."); Serial.println("p, P Increase local FT8 clock by 0.1 sec"); Serial.println("l, L Decrease local FT8 clock by 0.1 sec"); Serial.println("- Increase local FT8 clock by 1 sec"); Serial.println(", Decrease local FT8 clock by 1 sec"); Serial.println("c, C Display offset"); Serial.println("e, E Display received power"); Serial.println("? Help Display (this message)"); } else if(inCmd>35) // Ignore anything below '#' Serial.println("Cmd ???"); } // End, if Serial Available // Print average power level to Serial Monitor // Useful for testing and synchronizing the FT8 clock. Shows total band power. if( showPower && demod1.powerAvailable() ) { float32_t pwr=demod1.powerRead(); float32_t fl; if(second()>secLast || (second()==0 && secLast==59)) { secLast = second(); tp = millis(); } fl = millis() + tOffset - start_time; while(fl>=15000.0f) fl -= 15000.0f; Serial.print(0.001f*fl); Serial.print(" "); // Serial.print(0.001*(millis() - tp)+(float)second()); Serial.print(" "); Serial.print(pwr); Serial.print(" "); for(int jj=0; jj<2*(30-(int)(-0.5*pwr)); jj++) Serial.print("*"); Serial.println(); } update_synchronization(); ft8_mod_time = ft8_time%15000; if( ft8_mod_time<100 && ft8_mod_time_last>14900 ) { ft8_mod_time_last = ft8_mod_time; rcvFT8State = FT8_RCV_DATA_COLLECT; demod1.startDataCollect(); // Turn on decimation and data #ifdef DEBUG1 Serial.println("= = = = = SYNC TIME 15 = = = = ="); #endif digitalWrite(ledPin, HIGH); // set the LED on delay(100); digitalWrite(ledPin, LOW); // set the LED on } else ft8_mod_time_last = ft8_mod_time; if(rcvFT8State==FT8_RCV_DATA_COLLECT && demod1.available()) { // Here every 80 mSec for FFT rcvFT8State = FT8_RCV_FIND_POWERS; // 1472 * 92 = 135424 //master_offset = offset_step * FT_8_counter; //offset_step=1472 master_offset = 736 * (demod1.getFFTCount() -1); #ifdef DEBUG1 Serial.print("master offset = "); Serial.println(master_offset); Serial.print("Extract Power, fft count = "); Serial.println( demod1.getFFTCount() ); #endif extract_power(master_offset); // Do FFT and log powers rcvFT8State = FT8_RCV_DATA_COLLECT; #ifdef DEBUG1 Serial.println("Power array updated"); #endif } if(rcvFT8State!=FT8_RCV_IDLE && demod1.getFFTCount() >= 184) { rcvFT8State = FT8_RCV_DECODE; #ifdef DEBUG1 Serial.println("FT8 Decode"); #endif tu = micros(); num_decoded_msg = ft8_decode(); rcvFT8State = FT8_RCV_IDLE; master_decoded = num_decoded_msg; #ifdef DEBUG1 Serial.print("ft8_decode Time, uSec = "); Serial.println(micros() - tu); #endif } delay(1); } // End loop() time_t getTeensy3Time() { return Teensy3Clock.get(); } // This starts the receiving data collection every 15 sec void update_synchronization() { current_time = millis() + tOffset; ft8_time = current_time - start_time; ft8_hours = (int8_t)(ft8_time/3600000); hours_fraction = ft8_time % 3600000; ft8_minutes = (int8_t) (hours_fraction/60000); ft8_seconds = (int8_t)((hours_fraction % 60000)/1000); }