You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
OpenAudio_ArduinoLibrary/examples/FT8Receive/FT8Receive.ino

339 lines
11 KiB

// FT8Receive.ino 13 Oct 2022 Bob Larkin, W7PUA
// Simple command-line reception of WSJT FT8 signals for
// amateur radio.
/*
2 years ago
* 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
}
2 years ago
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);
}