|
|
|
// 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 <TimeLib.h>
|
|
|
|
#include <OpenAudio_ArduinoLibrary.h> // Added for F32 Teensy library RSL
|
|
|
|
#include <Audio.h>
|
|
|
|
#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
|
|
|
|
|
|
|
|
// 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
|
|
|
|
|
|
|
|
// 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);
|
|
|
|
}
|