// FT8Receive.ino 13 Oct 2022 Bob Larkin, W7PUA // Simple command-line reception of WSJT FT8 signals for // amateur radio. // Added azimuth calculation. Bob 14 Nov 2022 /* * 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 // Define FT8 symbol counts #define FT8_ND 58 #define FT8_NN 79 // Define the LDPC sizes #define FT8_N 174 #define FT8_K 91 #define FT8_M 83 #define FT8_K_BYTES 12 // A couple of prototypes to help the compile order void init_DSP(void); void extract_power(int); // 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); // NOTE Changed class name to start with capital "R" RSL 7 Nov 2022 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 // 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; // 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 FT8noisePowerEstimateL = 0.0f; // Works when big signals are absent int16_t FT8noisePwrDBIntL = 0; float32_t FT8noisePowerEstimateH = 0.0f; // Works for big signals and QRMt int16_t FT8noisePwrDBIntH = 0; float32_t FT8noisePeakAveRatio = 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 uint8_t ft8_hours, ft8_minutes, ft8_seconds; // rcvFT8State #define FT8_RCV_IDLE 0 #define FT8_RCV_DATA_COLLECT 1 #define FT8_RCV_FIND_POWERS 2 #define FT8_RCV_READY_DECODE 3 #define FT8_RCV_DECODE 4 int rcvFT8State = FT8_RCV_IDLE; int offset_step; //From gen_ft8.cpp, i.e., also used for transmit char Target_Call[7]; //six character call sign + /0 char Target_Locator[5]; // four character locator + /0 int Target_RSL; // four character RSL + /0 float32_t Station_Latitude, Station_Longitude; float32_t Target_Latitude, Target_Longitude; int32_t current_time, start_time; int32_t ft8_time, ft8_mod_time, ft8_mod_time_last; int32_t tOffset = 0; // Added Sept 22 uint8_t secLast = 0; const int ledPin = 13; bool showPower = false; uint32_t tp = 0; uint32_t tu = 0; void setup(void) { strcpy(Station_Call, "W7PUA"); strcpy(home_Locator, "CN84"); Station_Latitude = mh2latf(home_Locator); Station_Longitude = mh2lonf(home_Locator); // 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; int master_offset; 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 if(demod1.getFFTCount() >= 184) rcvFT8State = FT8_RCV_READY_DECODE; else rcvFT8State = FT8_RCV_DATA_COLLECT; #ifdef DEBUG1 Serial.println("Power array updated"); #endif } if(rcvFT8State==FT8_RCV_READY_DECODE ) { rcvFT8State = FT8_RCV_DECODE; #ifdef DEBUG1 Serial.println("FT8 Decode"); #endif tu = micros(); ft8_decode(); printFT8Received(); rcvFT8State = FT8_RCV_IDLE; #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() { int32_t hours_fraction; 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); }