qtsoundmodem/SoundInput.c

5254 lines
168 KiB
C
Raw Permalink Normal View History

2023-09-04 19:06:44 +01:00
// ARDOP Modem Decode Sound Samples
#include <math.h>
#include "ARDOPC.h"
#pragma warning(disable : 4244) // Code does lots of float to int
#ifndef TEENSY
#define MEMORYARQ
#endif
#undef PLOTWATERFALL
#ifdef PLOTWATERFALL
#define WHITE 0xffff
#define Tomato 0xffff
#define Orange 0xffff
#define Khaki 0xffff
#define Cyan 0xffff
#define DeepSkyBlue 0
#define RoyalBlue 0
#define Navy 0
#define Black 0
#endif
#ifdef TEENSY
#define PKTLED LED3 // flash when packet received
extern unsigned int PKTLEDTimer;
#endif
//#define max(x, y) ((x) > (y) ? (x) : (y))
//#define min(x, y) ((x) < (y) ? (x) : (y))
void SendFrametoHost(unsigned char *data, unsigned dlen);
void CheckandAdjustRXLevel(int maxlevel, int minlevel, BOOL Force);
void mySetPixel(unsigned char x, unsigned char y, unsigned int Colour);
void clearDisplay();
void updateDisplay();
VOID L2Routine(UCHAR * Packet, int Length, int FrameQuality, int totalRSErrors, int NumCar, int pktRXMode);
void RemoveProcessedOFDMData();
BOOL CheckCRC16(unsigned char * Data, int Length);
void DrawAxes(int Qual, const char * Frametype, char * Mode);
extern int lastmax, lastmin; // Sample Levels
char strRcvFrameTag[32];
BOOL blnLeaderFound = FALSE;
int intLeaderRcvdMs = 1000; // Leader length??
extern int intLastRcvdFrameQuality;
extern int intReceivedLeaderLen;
extern UCHAR bytLastReceivedDataFrameType;
extern int NErrors;
extern BOOL blnBREAKCmd;
extern UCHAR bytLastACKedDataFrameType;
extern int intARQDefaultDlyMs;
unsigned int tmrFinalID;
extern BOOL PKTCONNECTED;
extern int LastDemodType;
extern int pktRXMode;
extern int RXOFDMMode;
extern BOOL blnBusyStatus;
BOOL blnLastBusyStatus;
int BusyCount;
short intPriorMixedSamples[120]; // a buffer of 120 samples to hold the prior samples used in the filter
int intPriorMixedSamplesLength = 120; // size of Prior sample buffer
// While searching for leader we must save unprocessed samples
// We may have up to 720 left, so need 1920
short rawSamples[2400]; // Get Frame Type need 2400 and we may add 1200
extern int rawSamplesLength;
int maxrawSamplesLength;
short intFilteredMixedSamples[3500]; // Get Frame Type need 2400 and we may add 1200
int intFilteredMixedSamplesLength = 0;
int MaxFilteredMixedSamplesLength = 0;
int intFrameType= 0; // Type we are decoding
int LastDataFrameType = 0; // Last data frame processed (for Memory ARQ, etc)
char strDecodeCapture[256];
// Frame type parameters
int intCenterFreq = 1500;
float floatCarFreq; //(was int) // Are these the same ??
int intNumCar;
int intSampPerSym;
int intBaud;
int intDataLen;
int intRSLen;
int intSampleLen;
int DataRate = 0; // For SCS Reporting
int intDataPtr;
int intDataBytesPerCar;
BOOL blnOdd;
char strType[18] = "";
char strMod[16] = "";
UCHAR bytMinQualThresh;
int intPSKMode;
int intSymbolsPerByte = 4;
// ARDOP V2 has max 10 carriers and 160 (120 + 40RS) per carrier
#define MAX_RAW_LENGTH 163 // Len Byte + Data + RS + CRC I think!
#define MAX_RAW_LENGTH_FSK 43 // MAX FSK 32 data + 8 RS
// OFDM is MAXCAR * 100
// 10 carrier 16QAM id 10 * 160
#define MAX_DATA_LENGTH MAXCAR * 100 // I think! (OFDM 16QAM)
// intToneMags should be an array with one row per carrier.
// and 16 * max bytes data (2 bits per symbol, 4 samples per symbol in 4FSK.
// but as 600 Baud frames are very long (750 bytes), but only one carrier
// may be better to store as scalar and calculate offsets into it for each carrier
// treat 600 as 3 * 200, but scalar still may be better
// Needs 64K if ints + another 64 for MEM ARQ. (maybe able to store as shorts)
// 48K would do if we use a scalar (600 baud, 750 bytes)
// Max is 4 carrier, 83 bytes or 1 carrier 762 (or treat as 3 * 253)
// Could just about do this on Teensy 3.6 or Nucleo F7
// looks like we have 4 samples for each 2 bits, which means 16 samples per byte.
// ARDOP 2 only has one and two carrier FSK modes
// Teensy is rather short of RAM, but as we never receive FSK and PSK
// at the same time we can use same data area (saves about 20K)
int intToneMagsIndex[2];
// Same here
int intSumCounts[MAXCAR]; // number in above arrays
int intToneMagsLength;
unsigned char goodCarriers = 0; // Carriers we have already decoded
// We always collect all phases for PSK and QAM so we can do phase correction
// Max PSK frame is 83, 4 samples per byte = 332
// Max 16QAM frame is 163, 2 samples per byte = 326
// OFDM frames are shorter, so carriers 11 - 17 could have smaller sample buffers
// This is a bit complicated, but allows smaller buffers for the OFDM carriers (Needed for Teensy)
//short intPhases[MAXCAR][332] = {0};
short QAMPhases[10][332]; // 6640 bytes
short OFDMPhases[MAXCAR - 10][232]; // Need 232 = (PSK2 8 * 29); 15312
short * Phaseptrs[MAXCAR] =
{&QAMPhases[0][0], &QAMPhases[1][0], &QAMPhases[2][0], &QAMPhases[3][0], &QAMPhases[4][0],
&QAMPhases[5][0], &QAMPhases[6][0], &QAMPhases[7][0], &QAMPhases[8][0], &QAMPhases[9][0],
&OFDMPhases[0][0], &OFDMPhases[1][0], &OFDMPhases[2][0], &OFDMPhases[3][0], &OFDMPhases[4][0],
&OFDMPhases[5][0], &OFDMPhases[6][0], &OFDMPhases[7][0], &OFDMPhases[8][0], &OFDMPhases[9][0],
&OFDMPhases[10][0], &OFDMPhases[11][0], &OFDMPhases[12][0], &OFDMPhases[13][0], &OFDMPhases[14][0],
&OFDMPhases[15][0], &OFDMPhases[16][0], &OFDMPhases[17][0], &OFDMPhases[18][0], &OFDMPhases[19][0],
&OFDMPhases[20][0], &OFDMPhases[21][0], &OFDMPhases[22][0], &OFDMPhases[23][0], &OFDMPhases[24][0],
&OFDMPhases[25][0], &OFDMPhases[26][0], &OFDMPhases[27][0], &OFDMPhases[28][0], &OFDMPhases[29][0],
&OFDMPhases[30][0], &OFDMPhases[31][0], &OFDMPhases[32][0]};
short ** intPhases = &Phaseptrs[0];
short QAMMags[10][332];
short OFDMMags[MAXCAR - 10][232];
short * Magptrs[MAXCAR] =
{&QAMMags[0][0], &QAMMags[1][0], &QAMMags[2][0], &QAMMags[3][0], &QAMMags[4][0],
&QAMMags[5][0], &QAMMags[6][0], &QAMMags[7][0], &QAMMags[8][0], &QAMMags[9][0],
&OFDMMags[0][0], &OFDMMags[1][0], &OFDMMags[2][0], &OFDMMags[3][0], &OFDMMags[4][0],
&OFDMMags[5][0], &OFDMMags[6][0], &OFDMMags[7][0], &OFDMMags[8][0], &OFDMMags[9][0],
&OFDMMags[10][0], &OFDMMags[11][0], &OFDMMags[12][0], &OFDMMags[13][0], &OFDMMags[14][0],
&OFDMMags[15][0], &OFDMMags[16][0], &OFDMMags[17][0], &OFDMMags[18][0], &OFDMMags[19][0],
&OFDMMags[20][0], &OFDMMags[21][0], &OFDMMags[22][0], &OFDMMags[23][0], &OFDMMags[24][0],
&OFDMMags[25][0], &OFDMMags[26][0], &OFDMMags[27][0], &OFDMMags[28][0], &OFDMMags[29][0],
&OFDMMags[30][0], &OFDMMags[31][0], &OFDMMags[32][0]};
short ** intMags = &Magptrs[0];
//int Tones[2][16 * MAX_RAW_LENGTH_FSK];
int intToneMags[4][16 * MAX_RAW_LENGTH_FSK] = {0}; // Need one per carrier
// We need 5504 bytes for FSK but can overlay on PSK data areas
//int * Toneptrs[2] = {(int *)&Tones[0][0], (int *)&Tones[1][0]};
//int ** intToneMags = &Toneptrs[0];
#ifdef MEMORYARQ
// Enough RAM for memory ARQ so keep all samples for FSK and a copy of tones or phase/amplitude
int intToneMagsAvg[2][332]; //???? FSK Tone averages
short intCarPhaseAvg[MAXCAR][332]; // array to accumulate phases for averaging (Memory ARQ)
short intCarMagAvg[MAXCAR][332]; // array to accumulate mags for averaging (Memory ARQ)
#endif
//219 /3 * 8= 73 * 8 = 584
//163 * 4 = 652
// If we do Mem ARQ we will need a fair amount of RAM
int intPhasesLen;
// Received Frame
UCHAR bytData[128 * 80]; // Max OFDM Window
int frameLen;
int totalRSErrors;
// for comparing with CarrierOK
const char Good[MAXCAR] = {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}; // All Good
const char Bad[MAXCAR] = {0}; // All bad
// We need one raw buffer per carrier
// This can be optimized quite a bit to save space
// We can probably overlay on bytData
// If we still have 600 baud modes may need a lot more for first
// Note OFDM doesn't need one per carrier so only need 10
UCHAR bytFrameData[10][MAX_RAW_LENGTH + 10]; // Received chars
char CarrierOk[MAXCAR]; // RS OK Flags per carrier
int RepeatedFrame = 0; // set if this dats frame is a repeat
int charIndex = 0; // Index into received chars
int SymbolsLeft; // number still to decode
int DummyCarrier = 0; // pseudo carrier used for long 600 baud frames
UCHAR * Decode600Buffer;
BOOL PSKInitDone = FALSE;
BOOL blnSymbolSyncFound, blnFrameSyncFound;
extern UCHAR bytLastARQSessionID;
extern UCHAR bytCurrentFrameType;
extern int intShiftUpDn;
extern const char ARQSubStates[10][11];
extern int intLastARQDataFrameToHost;
// dont think I need it short intRcvdSamples[12000]; // 1 second. May need to optimise
float dblOffsetLastGoodDecode = 0;
int dttLastGoodFrameTypeDecode = -20000;
float dblOffsetHz = 0;;
int dttLastLeaderDetect;
extern int intRmtLeaderMeasure;
extern BOOL blnARQConnected;
extern BOOL blnPending;
extern UCHAR bytPendingSessionID;
extern UCHAR bytSessionID;
int dttLastGoodFrameTypeDecod;
int dttStartRmtLeaderMeasure;
char lastGoodID[11] = "";
int GotBitSyncTicks;
int intARQRTmeasuredMs;
float dbl2Pi = 2 * M_PI;
float dblSNdBPwr;
float dblNCOFreq = 3000; // nominal NC) frequency
float dblNCOPhase = 0;
float dblNCOPhaseInc = 2 * M_PI * 3000 / 12000; // was dblNCOFreq
float dblPwrSNPower_dBPrior = 0;
float dblPhaseDiff1_2Avg; // an initial value of -10 causes initialization in AcquireFrameSyncRSBAvg
int intMFSReadPtr = 0; // reset the MFSReadPtr offset 30 to accomodate the filter delay
int RcvdSamplesLen = 0; // Samples in RX buffer
float dblPhaseDiff1_2Avg;
int intPhaseError = 0;
BOOL Acquire2ToneLeaderSymbolFraming();
BOOL SearchFor2ToneLeader4(short * intNewSamples, int Length, float * dblOffsetHz, int * intSN);
BOOL AcquireFrameSyncRSB();
BOOL AcquireFrameSyncRSBAvg();
int Acquire4FSKFrameType();
void DemodulateFrame(int intFrameType);
void Demod1Car4FSKChar(int Start, UCHAR * Decoded, int Carrier);
VOID Track1Car4FSK(short * intSamples, int * intPtr, int intSampPerSymbol, float intSearchFreq, int intBaud, UCHAR * bytSymHistory);
VOID Decode1CarPSK(int Carrier, BOOL OFDM);
int EnvelopeCorrelator();
int EnvelopeCorrelatorNew();
2023-09-12 21:38:15 +01:00
BOOL DecodeFrame(int chan, int intFrameType, UCHAR * bytData);
2023-09-04 19:06:44 +01:00
void Update4FSKConstellation(int * intToneMags, int * intQuality);
void Update16FSKConstellation(int * intToneMags, int * intQuality);
void Update8FSKConstellation(int * intToneMags, int * intQuality);
void ProcessPingFrame(char * bytData);
int Compute4FSKSN();
void DemodPSK();
BOOL DemodQAM();
BOOL DemodOFDM();
BOOL Decode4FSKOFDMACK();
void PrintCarrierFlags()
{
char Msg[128];
if (intNumCar == 1)
Debugprintf("MEMARQ Flags %d", CarrierOk[0]);
else if (intNumCar == 2)
Debugprintf("MEMARQ Flags %d %d", CarrierOk[0], CarrierOk[1]);
else
{
sprintf(Msg, "MEMARQ Flags %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d",
CarrierOk[0], CarrierOk[1], CarrierOk[2], CarrierOk[3], CarrierOk[4], CarrierOk[5], CarrierOk[6], CarrierOk[7], CarrierOk[8], CarrierOk[9],
CarrierOk[10], CarrierOk[11], CarrierOk[12], CarrierOk[13], CarrierOk[14], CarrierOk[15], CarrierOk[16], CarrierOk[17], CarrierOk[18], CarrierOk[19],
CarrierOk[20], CarrierOk[21], CarrierOk[22], CarrierOk[23], CarrierOk[24], CarrierOk[25], CarrierOk[26], CarrierOk[27], CarrierOk[28], CarrierOk[29],
CarrierOk[30], CarrierOk[31], CarrierOk[32], CarrierOk[33], CarrierOk[34], CarrierOk[35], CarrierOk[36], CarrierOk[37], CarrierOk[38], CarrierOk[39],
CarrierOk[40], CarrierOk[41], CarrierOk[42]);
Msg[12 + 2 * intNumCar] = 0;
Debugprintf(Msg);
}
}
// Function to determine if frame type is short control frame
BOOL IsShortControlFrame(UCHAR bytType)
{
switch (intFrameType)
{
case DataNAK:
case DataNAKLoQ:
case ConRejBusy:
case ConRejBW:
case ConAck:
case DISCFRAME:
case BREAK:
case END:
case IDLEFRAME:
case DataACK:
case DataACKHiQ:
return TRUE;
}
return FALSE;
}
BOOL IsConReqFrame(UCHAR bytType)
{
switch (bytType)
{
case ConReq200:
case ConReq500:
case ConReq2500:
case OConReq500:
case OConReq2500:
return TRUE;
}
return FALSE;
}
// Function to determine if it is a data frame (Even OR Odd)
BOOL IsDataFrame(UCHAR intFrameType)
{
const char * String = Name(intFrameType);
if (intFrameType == PktFrameHeader)
return TRUE;
if (String == NULL || String[0] == 0)
return FALSE;
if (strstr(String, ".E") || strstr(String, ".O"))
return TRUE;
return FALSE;
}
// Subroutine to clear all mixed samples
void ClearAllMixedSamples()
{
intFilteredMixedSamplesLength = 0;
intMFSReadPtr = 0;
rawSamplesLength = 0; // Clear saved
}
// Subroutine to Initialize mixed samples
void InitializeMixedSamples()
{
// Measure the time from release of PTT to leader detection of reply.
intARQRTmeasuredMs = min(10000, Now - dttStartRTMeasure); //?????? needs work
intPriorMixedSamplesLength = 120; // zero out prior samples in Prior sample buffer
intFilteredMixedSamplesLength = 0; // zero out the FilteredMixedSamples array
intMFSReadPtr = 0; // reset the MFSReadPtr offset 30 to accomodate the filter delay
}
// Subroutine to discard all sampled prior to current intRcvdSamplesRPtr
void DiscardOldSamples()
{
// This restructures the intRcvdSamples array discarding all samples prior to intRcvdSamplesRPtr
//not sure why we need this !!
/*
if (RcvdSamplesLen - intRcvdSamplesRPtr <= 0)
RcvdSamplesLen = intRcvdSamplesRPtr = 0;
else
{
// This is rather slow. I'd prefer a cyclic buffer. Lets see....
memmove(intRcvdSamples, &intRcvdSamples[intRcvdSamplesRPtr], (RcvdSamplesLen - intRcvdSamplesRPtr)* 2);
RcvdSamplesLen -= intRcvdSamplesRPtr;
intRcvdSamplesRPtr = 0;
}
*/
}
// Subroutine to apply 2000 Hz filter to mixed samples
float xdblZin_1 = 0, xdblZin_2 = 0, xdblZComb= 0; // Used in the comb generator
// The resonators
float xdblZout_0[29] = {0.0f}; // resonator outputs
float xdblZout_1[29] = {0.0f}; // resonator outputs delayed one sample
float xdblZout_2[29] = {0.0f}; // resonator outputs delayed two samples
float xdblCoef[29] = {0.0}; // the coefficients
float xdblR = 0.9995f; // insures stability (must be < 1.0) (Value .9995 7/8/2013 gives good results)
int xintN = 120; //Length of filter 12000/100
void FSMixFilter2500Hz(short * intMixedSamples, int intMixedSamplesLength)
{
// assumes sample rate of 12000
// implements 27 100 Hz wide sections (~2500 Hz wide @ - 30dB centered on 1500 Hz)
// FSF (Frequency Selective Filter) variables
// This works on intMixedSamples, len intMixedSamplesLength;
// Filtered data is appended to intFilteredMixedSamples
float dblRn;
float dblR2;
float dblZin = 0;
int i, j;
float intFilteredSample = 0; // Filtered sample
if (intFilteredMixedSamplesLength < 0)
Debugprintf("Corrupt intFilteredMixedSamplesLength");
dblRn = powf(xdblR, xintN);
dblR2 = powf(xdblR, 2);
// Initialize the coefficients
if (xdblCoef[28] == 0)
{
for (i = 2; i <= 28; i++)
{
xdblCoef[i] = 2 * xdblR * cosf(2 * M_PI * i / xintN); // For Frequency = bin i
}
}
for (i = 0; i < intMixedSamplesLength; i++)
{
intFilteredSample = 0;
if (i < xintN)
dblZin = intMixedSamples[i] - dblRn * intPriorMixedSamples[i];
else
dblZin = intMixedSamples[i] - dblRn * intMixedSamples[i - xintN];
//Compute the Comb
xdblZComb = dblZin - xdblZin_2 * dblR2;
xdblZin_2 = xdblZin_1;
xdblZin_1 = dblZin;
// Now the resonators
for (j = 2; j <= 28; j++) // calculate output for 3 resonators
{
xdblZout_0[j] = xdblZComb + xdblCoef[j] * xdblZout_1[j] - dblR2 * xdblZout_2[j];
xdblZout_2[j] = xdblZout_1[j];
xdblZout_1[j] = xdblZout_0[j];
//' scale each by transition coeff and + (Even) or - (Odd)
//' Resonators 2 and 13 scaled by .389 get best shape and side lobe supression
//' Scaling also accomodates for the filter "gain" of approx 60.
if (j == 2 || j == 28)
intFilteredSample += 0.389f * xdblZout_0[j];
else if ((j & 1) == 0)
intFilteredSample += xdblZout_0[j];
else
intFilteredSample -= xdblZout_0[j];
}
intFilteredSample = intFilteredSample * 0.00833333333f;
intFilteredMixedSamples[intFilteredMixedSamplesLength++] = intFilteredSample; // rescales for gain of filter
}
// update the prior intPriorMixedSamples array for the next filter call
memmove(intPriorMixedSamples, &intMixedSamples[intMixedSamplesLength - xintN], intPriorMixedSamplesLength * 2);
if (intFilteredMixedSamplesLength > MaxFilteredMixedSamplesLength)
MaxFilteredMixedSamplesLength = intFilteredMixedSamplesLength;
if (intFilteredMixedSamplesLength > 3500)
Debugprintf("Corrupt intFilteredMixedSamplesLength %d", intFilteredMixedSamplesLength);
}
// Function to apply 150Hz filter used in Envelope correlator
void Filter150Hz(short * intFilterOut)
{
// assumes sample rate of 12000
// implements 3 100 Hz wide sections (~150 Hz wide @ - 30dB centered on 1500 Hz)
// FSF (Frequency Selective Filter) variables
static float dblR = 0.9995f; // insures stability (must be < 1.0) (Value .9995 7/8/2013 gives good results)
static int intN = 120; //Length of filter 12000/100
static float dblRn;
static float dblR2;
static float dblCoef[17] = {0.0}; // the coefficients
float dblZin = 0, dblZin_1 = 0, dblZin_2 = 0, dblZComb= 0; // Used in the comb generator
// The resonators
float dblZout_0[17] = {0.0}; // resonator outputs
float dblZout_1[17] = {0.0}; // resonator outputs delayed one sample
float dblZout_2[17] = {0.0}; // resonator outputs delayed two samples
int i, j;
float FilterOut = 0; // Filtered sample
float largest = 0;
dblRn = powf(dblR, intN);
dblR2 = powf(dblR, 2);
// Initialize the coefficients
if (dblCoef[17] == 0)
{
for (i = 14; i <= 16; i++)
{
dblCoef[i] = 2 * dblR * cosf(2 * M_PI * i / intN); // For Frequency = bin i
}
}
for (i = 0; i < 480; i++)
{
if (i < intN)
dblZin = intFilteredMixedSamples[intMFSReadPtr + i] - dblRn * 0; // no prior mixed samples
else
dblZin = intFilteredMixedSamples[intMFSReadPtr + i] - dblRn * intFilteredMixedSamples[intMFSReadPtr + i - intN];
// Compute the Comb
dblZComb = dblZin - dblZin_2 * dblR2;
dblZin_2 = dblZin_1;
dblZin_1 = dblZin;
// Now the resonators
for (j = 14; j <= 16; j++) // calculate output for 3 resonators
{
dblZout_0[j] = dblZComb + dblCoef[j] * dblZout_1[j] - dblR2 * dblZout_2[j];
dblZout_2[j] = dblZout_1[j];
dblZout_1[j] = dblZout_0[j];
// scale each by transition coeff and + (Even) or - (Odd)
// Scaling also accomodates for the filter "gain" of approx 120.
// These transition coefficients fairly close to optimum for WGN 0db PSK4, 100 baud (yield highest average quality) 5/24/2014
if (j == 14 || j == 16)
FilterOut = 0.2f * dblZout_0[j]; // this transisiton minimizes ringing and peaks
else
FilterOut -= dblZout_0[j];
}
intFilterOut[i] = (int)ceil(FilterOut * 0.00833333333); // rescales for gain of filter
}
}
// Function to apply 75Hz filter used in Envelope correlator
void Filter75Hz(short * intFilterOut, BOOL blnInitialise, int intSamplesToFilter)
{
// assumes sample rate of 12000
// implements 3 100 Hz wide sections (~150 Hz wide @ - 30dB centered on 1500 Hz)
// FSF (Frequency Selective Filter) variables
static float dblR = 0.9995f; // insures stability (must be < 1.0) (Value .9995 7/8/2013 gives good results)
static int intN = 240; //Length of filter 12000/50 - delays output 120 samples from input
static float dblRn;
static float dblR2;
static float dblCoef[3] = {0.0}; // the coefficients
float dblZin = 0, dblZin_1 = 0, dblZin_2 = 0, dblZComb= 0; // Used in the comb generator
// The resonators
float dblZout_0[3] = {0.0}; // resonator outputs
float dblZout_1[3] = {0.0}; // resonator outputs delayed one sample
float dblZout_2[3] = {0.0}; // resonator outputs delayed two samples
int i, j;
float FilterOut = 0; // Filtered sample
float largest = 0;
dblRn = powf(dblR, intN);
dblR2 = powf(dblR, 2);
// Initialize the coefficients
if (dblCoef[2] == 0)
{
for (i = 0; i <= 3; i++)
{
dblCoef[i] = 2 * dblR * cosf(2 * M_PI * (29 + i)/ intN); // For Frequency = bin 29, 30, 31
}
}
for (i = 0; i < intSamplesToFilter; i++)
{
if (i < intN)
dblZin = intFilteredMixedSamples[intMFSReadPtr + i] - dblRn * 0; // no prior mixed samples
else
dblZin = intFilteredMixedSamples[intMFSReadPtr + i] - dblRn * intFilteredMixedSamples[intMFSReadPtr + i - intN];
// Compute the Comb
dblZComb = dblZin - dblZin_2 * dblR2;
dblZin_2 = dblZin_1;
dblZin_1 = dblZin;
// Now the resonators
for (j = 0; j < 3; j++) // calculate output for 3 resonators
{
dblZout_0[j] = dblZComb + dblCoef[j] * dblZout_1[j] - dblR2 * dblZout_2[j];
dblZout_2[j] = dblZout_1[j];
dblZout_1[j] = dblZout_0[j];
// scale each by transition coeff and + (Even) or - (Odd)
// Scaling also accomodates for the filter "gain" of approx 120.
// These transition coefficients fairly close to optimum for WGN 0db PSK4, 100 baud (yield highest average quality) 5/24/2014
if (j == 0 || j == 2)
FilterOut -= 0.39811f * dblZout_0[j]; // this transisiton minimizes ringing and peaks
else
FilterOut += dblZout_0[j];
}
intFilterOut[i] = (int)ceil(FilterOut * 0.0041f); // rescales for gain of filter
}
}
// Subroutine to Mix new samples with NCO to tune to nominal 1500 Hz center with reversed sideband and filter.
void MixNCOFilter(short * intNewSamples, int Length, float dblOffsetHz)
{
// Correct the dimension of intPriorMixedSamples if needed (should only happen after a bandwidth setting change).
int i;
short intMixedSamples[2400]; // All we need at once ( I hope!) // may need to be int
int intMixedSamplesLength = 0; //size of intMixedSamples
if (Length == 0)
return;
// Nominal NCO freq is 3000 Hz to downmix intNewSamples (NCO - Fnew) to center of 1500 Hz (invertes the sideband too)
dblNCOFreq = 3000 + dblOffsetHz;
dblNCOPhaseInc = dblNCOFreq * dbl2Pi / 12000;
intMixedSamplesLength = Length;
for (i = 0; i < Length; i++)
{
intMixedSamples[i] = (int)ceilf(intNewSamples[i] * cosf(dblNCOPhase)); // later may want a lower "cost" implementation of "Cos"
dblNCOPhase += dblNCOPhaseInc;
if (dblNCOPhase > dbl2Pi)
dblNCOPhase -= dbl2Pi;
}
// showed no significant difference if the 2000 Hz filer used for all bandwidths.
// printtick("Start Filter");
FSMixFilter2500Hz(intMixedSamples, intMixedSamplesLength); // filter through the FS filter (required to reject image from Local oscillator)
// printtick("Done Filter");
// save for analysys
// WriteSamples(&intFilteredMixedSamples[oldlen], Length);
// WriteSamples(intMixedSamples, Length);
}
// Function to Correct Raw demodulated data with Reed Solomon FEC
int CorrectRawDataWithRS(UCHAR * bytRawData, UCHAR * bytCorrectedData, int intDataLen, int intRSLen, int bytFrameType, int Carrier)
{
BOOL blnRSOK;
BOOL FrameOK;
BOOL OK;
//Dim bytNoRS(1 + intDataLen + 2 - 1) As Byte ' 1 byte byte Count, Data, 2 byte CRC
//Array.Copy(bytRawData, 0, bytNoRS, 0, bytNoRS.Length)
if (CarrierOk[Carrier]) // Already decoded this carrier?
{
// Athough we have already checked the data, it may be in the wrong place
// in the buffer if another carrier was decoded wrong.
memcpy(bytCorrectedData, &bytRawData[1], bytRawData[0] + 1); // Extra char in case OFDM
if (strFrameType[LastDataFrameType][0] == 'O')
Debugprintf("[CorrectRawDataWithRS] Carrier %d already decoded Block %d, Len %d", Carrier, bytRawData[1], bytRawData[0]);
else
Debugprintf("[CorrectRawDataWithRS] Carrier %d already decoded Len %d", Carrier, bytRawData[0]);
return bytRawData[0]; // don't do it again
}
if (strFrameType[intFrameType][0] == 'O')
OK = CheckCRC16(bytRawData, intDataLen + 1);
else
OK = CheckCRC16FrameType(bytRawData, intDataLen + 1, bytFrameType);
// As crc can fail also check returned lenght is reasonable
if (OK && bytRawData[0] <= intDataLen) // No RS correction needed // return the actual data
{
memcpy(bytCorrectedData, &bytRawData[1], bytRawData[0] + 1);
if (strFrameType[intFrameType][0] == 'O')
Debugprintf("[CorrectRawDataWithRS] Carrier %d OK without RS, Block %d Len = %d", Carrier, bytRawData[1], bytRawData[0]);
else
Debugprintf("[CorrectRawDataWithRS] Carrier %d OK without RS, Len = %d", Carrier, bytRawData[0]);
CarrierOk[Carrier] = TRUE;
return bytRawData[0];
}
// Try correcting with RS Parity
FrameOK = RSDecode(bytRawData, intDataLen + 3 + intRSLen, intRSLen, &blnRSOK);
if (blnRSOK)
{}
// Debugprintf("RS Says OK without correction");
else
if (FrameOK)
{}
// Debugprintf("RS Says OK after %d correction(s)", NErrors);
else
{
Debugprintf("[intFrameType] RS Says Can't Correct");
goto returnBad;
}
if (FrameOK)
{
if (strFrameType[intFrameType][0] == 'O')
OK = CheckCRC16(bytRawData, intDataLen + 1);
else
OK = CheckCRC16FrameType(bytRawData, intDataLen + 1, bytFrameType);
if (OK && bytRawData[0] <= intDataLen) // Now OK - return the actual data
{
int intFailedByteCnt = 0;
if (strFrameType[intFrameType][0] == 'O')
Debugprintf("[CorrectRawDataWithRS] Carrier %d OK with RS %d corrections, Block %d, Len = %d", Carrier, NErrors, bytRawData[1], bytRawData[0]);
else
Debugprintf("[CorrectRawDataWithRS] Carrier %d OK with RS %d corrections, Len = %d", Carrier, NErrors, bytRawData[0]);
totalRSErrors += NErrors;
memcpy(bytCorrectedData, &bytRawData[1], bytRawData[0] + 1);
CarrierOk[Carrier] = TRUE;
return bytRawData[0];
}
Debugprintf("[CorrectRawDataWithRS] Carrier %d RS says ok but CRC still bad", Carrier);
}
// return uncorrected data without byte count or RS Parity
returnBad:
memcpy(bytCorrectedData, &bytRawData[1], intDataLen + 1);
CarrierOk[Carrier] = FALSE;
return intDataLen;
}
// Subroutine to process new samples as received from the sound card via Main.ProcessCapturedData
// Only called when not transmitting
double dblPhaseInc; // in milliradians
short intNforGoertzel[MAXCAR];
short intPSKPhase_1[MAXCAR], intPSKPhase_0[MAXCAR];
short intCP[MAXCAR]; // Cyclic prefix offset
float dblFreqBin[MAXCAR];
BOOL CheckFrameTypeParity(int intTonePtr, int * intToneMags);
2023-09-12 21:38:15 +01:00
void ARDOPProcessNewSamples(int chan, short * Samples, int nSamples)
2023-09-04 19:06:44 +01:00
{
BOOL blnFrameDecodedOK = FALSE;
// LookforUZ7HOLeader(Samples, nSamples);
// printtick("Start afsk");
// DemodAFSK(Samples, nSamples);
// printtick("End afsk");
// return;
if (ProtocolState == FECSend)
return;
// Append new data to anything in rawSamples
memcpy(&rawSamples[rawSamplesLength], Samples, nSamples * 2);
rawSamplesLength += nSamples;
if (rawSamplesLength > maxrawSamplesLength)
maxrawSamplesLength = rawSamplesLength;
if (rawSamplesLength >= 2400)
Debugprintf("Corrupt rawSamplesLength %d", rawSamplesLength);
nSamples = rawSamplesLength;
Samples = rawSamples;
rawSamplesLength = 0;
// printtick("Start Busy");
if (nSamples >= 1024)
UpdateBusyDetector(Samples);
// printtick("Done Busy");
// it seems that searchforleader runs on unmixed and unfilered samples
// Searching for leader
if (State == SearchingForLeader)
{
// Search for leader as long as 960 samples (8 symbols) available
// printtick("Start Leader Search");
if (nSamples >= 1200)
{
if (ProtocolState == FECSend)
return;
}
while (State == SearchingForLeader && nSamples >= 1200)
{
int intSN;
blnLeaderFound = SearchFor2ToneLeader4(Samples, nSamples, &dblOffsetHz, &intSN);
// blnLeaderFound = SearchFor2ToneLeader2(Samples, nSamples, &dblOffsetHz, &intSN);
if (blnLeaderFound)
{
// Debugprintf("Got Leader");
dttLastLeaderDetect = Now;
nSamples -= 480;
Samples += 480; // !!!! needs attention !!!
InitializeMixedSamples();
State = AcquireSymbolSync;
}
else
{
if (SlowCPU)
{
nSamples -= 480;
Samples += 480; // advance pointer 2 symbols (40 ms) ' reduce CPU loading
}
else
{
nSamples -= 240;
Samples += 240; // !!!! needs attention !!!
}
}
}
if (State == SearchingForLeader)
{
// Save unused samples
memmove(rawSamples, Samples, nSamples * 2);
rawSamplesLength = nSamples;
// printtick("End Leader Search");
return;
}
}
// Got leader
// At this point samples haven't been processed, and are in Samples, len nSamples
// I'm going to filter all samples into intFilteredMixedSamples.
// printtick("Start Mix");
MixNCOFilter(Samples, nSamples, dblOffsetHz); // Mix and filter new samples (Mixing consumes all intRcvdSamples)
nSamples = 0; // all used
// printtick("Done Mix Samples");
// Acquire Symbol Sync
if (State == AcquireSymbolSync)
{
if ((intFilteredMixedSamplesLength - intMFSReadPtr) > 960)
{
blnSymbolSyncFound = Acquire2ToneLeaderSymbolFraming(); // adjust the pointer to the nominal symbol start based on phase
if (blnSymbolSyncFound)
State = AcquireFrameSync;
else
{
// Rick's Latest code (2.0.3) advances pointer instead of clearing samples
// DiscardOldSamples();
// ClearAllMixedSamples();
intMFSReadPtr += 240; // advance the MFSReadPointer one symbol and try to search for leader again.
State = SearchingForLeader;
return;
}
// printtick("Got Sym Sync");
}
}
// Acquire Frame Sync
if (State == AcquireFrameSync)
{
blnFrameSyncFound = AcquireFrameSyncRSB();
// Remove used samples
intFilteredMixedSamplesLength -= intMFSReadPtr;
if (intFilteredMixedSamplesLength < 0)
Debugprintf("Corrupt intFilteredMixedSamplesLength");
memmove(intFilteredMixedSamples,
&intFilteredMixedSamples[intMFSReadPtr], intFilteredMixedSamplesLength * 2);
intMFSReadPtr = 0;
if (blnFrameSyncFound)
{
State = AcquireFrameType;
// Have frame Sync. Remove used samples from buffer
printtick("Got Frame Sync");
}
else if ((Now - dttLastLeaderDetect) > 1000) // no Frame sync within 1000 ms (may want to make this limit a funciton of Mode and leaders)
{
DiscardOldSamples();
ClearAllMixedSamples();
State = SearchingForLeader;
printtick("frame sync timeout");
}
/*
else if (intPhaseError > 2)
{
DiscardOldSamples();
ClearAllMixedSamples();
State = SearchingForLeader;
printtick("frame sync timeout");
}
// else
// printtick("no frame sync");
*/
}
// Acquire Frame Type
if (State == AcquireFrameType)
{
// printtick("getting frame type");
intFrameType = Acquire4FSKFrameType();
if (intFrameType == -2)
{
// sprintf(Msg, "not enough %d %d", intFilteredMixedSamplesLength, intMFSReadPtr);
// printtick(Msg);
return; // insufficient samples
}
if (intFrameType == -1) // poor decode quality (large decode distance)
{
State = SearchingForLeader;
ClearAllMixedSamples();
DiscardOldSamples();
Debugprintf("poor frame type decode");
// stcStatus.BackColor = SystemColors.Control
// stcStatus.Text = ""
// stcStatus.ControlName = "lblRcvFrame"
// queTNCStatus.Enqueue(stcStatus)
}
else
{
// Get Frame info and Initialise Demodulate variables
// We've used intMFSReadPtr samples, so remove from Buffer
// sprintf(Msg, "Got Frame Type %x", intFrameType);
// printtick(Msg);
blnLastBusyStatus = 1;
blnBusyStatus = 1;
BusyCount = 10;
intFilteredMixedSamplesLength -= intMFSReadPtr;
if (intFilteredMixedSamplesLength < 0)
Debugprintf("Corrupt intFilteredMixedSamplesLength");
memmove(intFilteredMixedSamples,
&intFilteredMixedSamples[intMFSReadPtr], intFilteredMixedSamplesLength * 2);
intMFSReadPtr = 0;
if (!FrameInfo(intFrameType, &blnOdd, &intNumCar, strMod, &intBaud, &intDataLen, &intRSLen, &bytMinQualThresh, strType))
{
printtick("bad frame type");
State = SearchingForLeader;
ClearAllMixedSamples();
DiscardOldSamples();
return;
}
if (IsShortControlFrame(intFrameType))
{
// Frame has no data so is now complete
DrawRXFrame(1, Name(intFrameType));
// See if IRStoISS shortcut can be invoked
// prepare for next
DiscardOldSamples();
ClearAllMixedSamples();
State = SearchingForLeader;
blnFrameDecodedOK = TRUE;
Debugprintf("[DecodeFrame] Frame: %s ", Name(intFrameType));
DecodeCompleteTime = Now;
goto ProcessFrame;
}
DrawRXFrame(0, Name(intFrameType));
if (intBaud == 25)
intSampPerSym = 480;
else if (intBaud == 50)
intSampPerSym = 240;
else if (intBaud == 55)
intSampPerSym = 216;
else if (intBaud == 100)
intSampPerSym = 120;
else if (intBaud == 167)
intSampPerSym = 72;
else if (intBaud == 600)
intSampPerSym = 20;
if (IsDataFrame(intFrameType))
SymbolsLeft = intDataLen + intRSLen + 3; // Data has crc + length byte
else if (intFrameType == OFDMACK)
SymbolsLeft = intDataLen + intRSLen + 2; // CRC but no len
else
SymbolsLeft = intDataLen + intRSLen; // No CRC
if (intDataLen == 600)
SymbolsLeft += 6; // 600 baud has 3 * RS Blocks
// Save data rate for PTC reporting
if (Rate[intFrameType] > 0)
DataRate = Rate[intFrameType];
intToneMagsLength = 16 * SymbolsLeft; // 4 tones, 2 bits per set
memset(intToneMagsIndex, 0, sizeof(intToneMagsIndex));
charIndex = 0;
PSKInitDone = 0;
frameLen = 0;
totalRSErrors = 0;
DummyCarrier = 0; // pseudo carrier used for long 600 baud frames
Decode600Buffer = &bytFrameData[0][0];
State = AcquireFrame;
// if a data frame, and not the same frame type as last, reinitialise
// correctly received carriers byte and memory ARQ fields
// if (IsDataFrame(intFrameType) && LastDataFrameType != intFrameType)
if (intFrameType == PktFrameHeader || intFrameType == PktFrameData)
{
memset(CarrierOk, 0, sizeof(CarrierOk));
memset(intSumCounts, 0, sizeof(intSumCounts));
#ifdef MEMORYARQ
memset(intToneMagsAvg, 0, sizeof(intToneMagsAvg));
memset(intCarPhaseAvg, 0, sizeof(intCarPhaseAvg));
memset(intCarMagAvg, 0, sizeof(intCarMagAvg));
#endif
LastDataFrameType = intFrameType;
}
else if (LastDataFrameType != intFrameType)
{
if (strFrameType[LastDataFrameType][0] == 'O')
{
// OFDM Frame, We know the ISS received the last ack, so can remove any data passed to host.
// We need to do that, as new frame block numbers will start from first unacked block.
if (intFrameType == OFDMACK)
RepeatedFrame = FALSE;
RepeatedFrame = FALSE;
RemoveProcessedOFDMData();
}
Debugprintf("New frame type - MEMARQ flags reset");
memset(CarrierOk, 0, sizeof(CarrierOk));
if (IsDataFrame(intFrameType))
LastDataFrameType = intFrameType;
// note that although we only do mem arq if enough RAM we
// still skip decoding carriers that have been received;
#ifdef MEMORYARQ
memset(intSumCounts, 0, sizeof(intSumCounts));
memset(intToneMagsAvg, 0, sizeof(intToneMagsAvg));
memset(intCarPhaseAvg, 0, sizeof(intCarPhaseAvg));
memset(intCarMagAvg, 0, sizeof(intCarMagAvg));
#endif
}
else
{
// Repeated frame. OFDM needs to know, as we may have passed data to host.
if (IsDataFrame(intFrameType))
RepeatedFrame = TRUE;
}
PrintCarrierFlags();
}
}
// Acquire Frame
if (State == AcquireFrame)
{
// Call DemodulateFrame for each set of samples
DemodulateFrame(intFrameType);
if (State == AcquireFrame)
// We haven't got it all yet so wait for more samples
return;
// We have the whole frame, so process it
// printtick("got whole frame");
LastDemodType = intFrameType;
if (strcmp (strMod, "4FSK") == 0)
Update4FSKConstellation(&intToneMags[0][0], &intLastRcvdFrameQuality);
else if (strcmp (strMod, "16FSK") == 0)
Update16FSKConstellation(&intToneMags[0][0], &intLastRcvdFrameQuality);
else if (strcmp (strMod, "8FSK") == 0)
Update8FSKConstellation(&intToneMags[0][0], &intLastRcvdFrameQuality);
// PSK and QAM quality done in Decode routines
Debugprintf("Qual = %d", intLastRcvdFrameQuality);
// This mechanism is to skip actual decoding and reply/change state...no need to decode
2023-09-12 21:38:15 +01:00
blnFrameDecodedOK = DecodeFrame(chan, intFrameType, bytData);
2023-09-04 19:06:44 +01:00
ProcessFrame:
if (!blnFrameDecodedOK)
DrawRXFrame(2, Name(intFrameType));
if (intFrameType == PktFrameData)
{
#ifdef TEENSY
SetLED(PKTLED, TRUE); // Flash LED
PKTLEDTimer = Now + 200; // for 200 mS
#endif
return;
}
if (blnFrameDecodedOK)
{
// Set input level if supported
#ifdef HASPOTS
CheckandAdjustRXLevel(lastmax, lastmin, TRUE);
#endif
if (AccumulateStats)
if (IsDataFrame(intFrameType))
if (strstr (strMod, "PSK"))
intGoodPSKFrameDataDecodes++;
else if (strstr (strMod, "QAM"))
intGoodQAMFrameDataDecodes++;
else if (strstr (strMod, "OFDM"))
intGoodOFDMFrameDataDecodes++;
else
intGoodFSKFrameDataDecodes++;
#ifdef TEENSY
if (IsDataFrame(intFrameType))
{
SetLED(PKTLED, TRUE); // Flash LED
PKTLEDTimer = Now + 400; // For 400 Ms
}
#endif
}
else
{
// Bad decode
if (AccumulateStats)
if (IsDataFrame(intFrameType))
if (strstr(strMod, "PSK"))
intFailedPSKFrameDataDecodes++;
else if (strstr(strMod, "QAM"))
intFailedQAMFrameDataDecodes++;
else if (strstr(strMod, "OFDM"))
intFailedOFDMFrameDataDecodes++;
else
intFailedFSKFrameDataDecodes++;
// Debug.WriteLine("[DecodePSKData2] bytPass = " & Format(bytPass, "X"))
}
skipDecode:
State = SearchingForLeader;
ClearAllMixedSamples();
DiscardOldSamples();
return;
}
}
// Subroutine to compute Goertzel algorithm and return Real and Imag components for a single frequency bin
void GoertzelRealImag(short intRealIn[], int intPtr, int N, float m, float * dblReal, float * dblImag)
{
// intRealIn is a buffer at least intPtr + N in length
// N need not be a power of 2
// m need not be an integer
// Computes the Real and Imaginary Freq values for bin m
// Verified to = FFT results for at least 10 significant digits
// Timings for 1024 Point on Laptop (64 bit Core Duo 2.2 Ghz)
// GoertzelRealImag .015 ms Normal FFT (.5 ms)
// assuming Goertzel is proportional to N and FFT time proportional to Nlog2N
// FFT:Goertzel time ratio ~ 3.3 Log2(N)
// Sanity check
//if (intPtr < 0 Or (intRealIn.Length - intPtr) < N Then
// dblReal = 0 : dblImag = 0 : Exit Sub
// End If
float dblZ_1 = 0.0f, dblZ_2 = 0.0f, dblW = 0.0f;
float dblCoeff = 2 * cosf(2 * M_PI * m / N);
int i;
for (i = 0; i <= N; i++)
{
if (i == N)
dblW = dblZ_1 * dblCoeff - dblZ_2;
else
dblW = intRealIn[intPtr] + dblZ_1 * dblCoeff - dblZ_2;
dblZ_2 = dblZ_1;
dblZ_1 = dblW;
intPtr++;
}
*dblReal = 2 * (dblW - cosf(2 * M_PI * m / N) * dblZ_2) / N; // scale results by N/2
*dblImag = 2 * (sinf(2 * M_PI * m / N) * dblZ_2) / N; // scale results by N/2 (this sign agrees with Scope DSP phase values)
}
// Subroutine to compute Goertzel algorithm and return Real and Imag components for a single frequency bin with a Hanning Window function
float dblHanWin[120];
float dblHanAng;
int HanWinLen = 0;
float dblHannWin[480];
float dblHannAng;
// Subroutine to compute Goertzel algorithm and return Real and Imag components for a single frequency bin with a Hann Window function for N a multiple of 120
void GoertzelRealImagHann120(short intRealIn[], int intPtr, int N, float m, float * dblReal, float * dblImag)
{
// This version precomputes the raised cosine (Hann or Hanning) window and uses it for any length that is a multiple of 120 samples
// intRealIn is a buffer at least intPtr + N in length
// N must be 960 to use this function
// Hann coefficients are approximated for N>120 but should be close
// m need not be an integer
// Computes the Real and Imaginary Freq values for bin m
// Verified to = FFT results for at least 10 significant digits
// Timings for 1024 Point on Laptop (64 bit Core Duo 2.2 Ghz)
// GoertzelRealImag .015 ms Normal FFT (.5 ms)
// assuming Goertzel is proportional to N and FFT time proportional to Nlog2N
// FFT:Goertzel time ratio ~ 3.3 Log2(N)
float dblZ_1 = 0.0f, dblZ_2 = 0.0f, dblW = 0.0f;
float dblCoeff = 2 * cosf(2 * M_PI * m / N);
int i;
int intM = N / 120; // No if 120 sample blocks
if (HanWinLen != N) //if there is any change in N this is then recalculate the Hanning Window...this mechanism reduces use of Cos
{
HanWinLen = N;
dblHanAng = 2 * M_PI / 120;
for (i = 0; i < 60; i++)
{
dblHanWin[i] = 0.5 - 0.5 * cosf(i * dblHanAng + dblHanAng);
}
}
for (i = 0; i <= N; i++)
{
if (i == N)
dblW = dblZ_1 * dblCoeff - dblZ_2;
else if (i < (N / 2)) // ist half of 120 sample block
// looks like we use values 0 ti 59 then 59 down to 0
dblW = intRealIn[intPtr] * dblHanWin[(i /intM) % 60] + dblZ_1 * dblCoeff - dblZ_2;
else
dblW = intRealIn[intPtr] * dblHanWin[59 - ((i /intM) % 60)] + dblZ_1 * dblCoeff - dblZ_2;
dblZ_2 = dblZ_1;
dblZ_1 = dblW;
intPtr++;
}
*dblReal = 2 * (dblW - cosf(2 * M_PI * m / N) * dblZ_2) / N; // scale results by N/2
*dblImag = 2 * (sinf(2 * M_PI * m / N) * dblZ_2) / N; // scale results by N/2 (this sign agrees with Scope DSP phase values)
}
void GoertzelRealImagHann960(short intRealIn[], int intPtr, int N, float m, float * dblReal, float * dblImag)
{
// This version precomputes the raised cosine (Hann or Hanning) window and uses it for any length that is a multiple of 120 samples
// intRealIn is a buffer at least intPtr + N in length
// N must be a multiple of 120 to use this function
// Hann coefficients are approximated for N>120 but should be close
// m need not be an integer
// Computes the Real and Imaginary Freq values for bin m
// Verified to = FFT results for at least 10 significant digits
// Timings for 1024 Point on Laptop (64 bit Core Duo 2.2 Ghz)
// GoertzelRealImag .015 ms Normal FFT (.5 ms)
// assuming Goertzel is proportional to N and FFT time proportional to Nlog2N
// FFT:Goertzel time ratio ~ 3.3 Log2(N)
float dblZ_1 = 0.0f, dblZ_2 = 0.0f, dblW = 0.0f;
float dblCoeff = 2 * cosf(2 * M_PI * m / N);
int i;
int intM = N / 120; // No if 120 sample blocks
if (dblHannWin[479] < 0.5) //if there is any change in N this is then recalculate the Hanning Window...this mechanism reduces use of Cos
{
dblHannAng = 2 * M_PI / 960;
for (i = 0; i < 480; i++)
{
dblHannWin[i] = 0.5 - 0.5 * cosf(i * dblHannAng + dblHannAng);
}
}
for (i = 0; i <= N; i++)
{
if (i == N)
dblW = dblZ_1 * dblCoeff - dblZ_2;
else if (i < (N / 2)) // ist half of 120 sample block
// looks like we use values 0 ti 59 then 59 down to 0
dblW = intRealIn[intPtr] * dblHannWin[(i /intM) % 60] + dblZ_1 * dblCoeff - dblZ_2;
else
dblW = intRealIn[intPtr] * dblHannWin[479 - ((i /intM) % 60)] + dblZ_1 * dblCoeff - dblZ_2;
dblZ_2 = dblZ_1;
dblZ_1 = dblW;
intPtr++;
}
*dblReal = 2 * (dblW - cosf(2 * M_PI * m / N) * dblZ_2) / N; // scale results by N/2
*dblImag = 2 * (sinf(2 * M_PI * m / N) * dblZ_2) / N; // scale results by N/2 (this sign agrees with Scope DSP phase values)
}
void GoertzelRealImagHanning(short intRealIn[], int intPtr, int N, float m, float * dblReal, float * dblImag)
{
// intRealIn is a buffer at least intPtr + N in length
// N need not be a power of 2
// m need not be an integer
// Computes the Real and Imaginary Freq values for bin m
// Verified to = FFT results for at least 10 significant digits
// Timings for 1024 Point on Laptop (64 bit Core Duo 2.2 Ghz)
// GoertzelRealImag .015 ms Normal FFT (.5 ms)
// assuming Goertzel is proportional to N and FFT time proportional to Nlog2N
// FFT:Goertzel time ratio ~ 3.3 Log2(N)
// Sanity check
float dblZ_1 = 0.0f, dblZ_2 = 0.0f, dblW = 0.0f;
float dblCoeff = 2 * cosf(2 * M_PI * m / N);
int i;
if (HanWinLen != N) //if there is any change in N this is then recalculate the Hanning Window...this mechanism reduces use of Cos
{
HanWinLen = N;
dblHanAng = 2 * M_PI / (N - 1);
for (i = 0; i < N; i++)
{
dblHanWin[i] = 0.5 - 0.5 * cosf(i * dblHanAng);
}
}
for (i = 0; i <= N; i++)
{
if (i == N)
dblW = dblZ_1 * dblCoeff - dblZ_2;
else
dblW = intRealIn[intPtr] * dblHanWin[i] + dblZ_1 * dblCoeff - dblZ_2;
dblZ_2 = dblZ_1;
dblZ_1 = dblW;
intPtr++;
}
*dblReal = 2 * (dblW - cosf(2 * M_PI * m / N) * dblZ_2) / N; // scale results by N/2
*dblImag = 2 * (sinf(2 * M_PI * m / N) * dblZ_2) / N; // scale results by N/2 (this sign agrees with Scope DSP phase values)
}
float dblHamWin[1200];
float dblHamAng;
int HamWinLen = 0;
void GoertzelRealImagHamming(short intRealIn[], int intPtr, int N, float m, float * dblReal, float * dblImag)
{
// intRealIn is a buffer at least intPtr + N in length
// N need not be a power of 2
// m need not be an integer
// Computes the Real and Imaginary Freq values for bin m
// Verified to = FFT results for at least 10 significant digits
// Timings for 1024 Point on Laptop (64 bit Core Duo 2.2 Ghz)
// GoertzelRealImag .015 ms Normal FFT (.5 ms)
// assuming Goertzel is proportional to N and FFT time proportional to Nlog2N
// FFT:Goertzel time ratio ~ 3.3 Log2(N)
// Sanity check
float dblZ_1 = 0.0f, dblZ_2 = 0.0f, dblW = 0.0f;
float dblCoeff = 2 * cosf(2 * M_PI * m / N);
int i;
if (HamWinLen != N) //if there is any cHamge in N this is then recalculate the Hanning Window...this mechanism reduces use of Cos
{
HamWinLen = N;
dblHamAng = 2 * M_PI / (N - 1);
for (i = 0; i < N; i++)
{
dblHamWin[i] = 0.54f - 0.46f * cosf(i * dblHamAng);
}
}
for (i = 0; i <= N; i++)
{
if (i == N)
dblW = dblZ_1 * dblCoeff - dblZ_2;
else
dblW = intRealIn[intPtr] * dblHamWin[i] + dblZ_1 * dblCoeff - dblZ_2;
dblZ_2 = dblZ_1;
dblZ_1 = dblW;
intPtr++;
}
*dblReal = 2 * (dblW - cosf(2 * M_PI * m / N) * dblZ_2) / N; // scale results by N/2
*dblImag = 2 * (sinf(2 * M_PI * m / N) * dblZ_2) / N; // scale results by N/2 (this sign agrees with Scope DSP phase values)
}
// Function to interpolate spectrum peak using Quinn algorithm
float QuinnSpectralPeakLocator(float XkM1Re, float XkM1Im, float XkRe, float XkIm, float XkP1Re, float XkP1Im)
{
// based on the Quinn algorithm in Streamlining Digital Processing page 139
// Alpha1 = Re(Xk-1/Xk)
// Alpha2 = Re(Xk+1/Xk)
//Delta1 = Alpha1/(1 - Alpha1)
//'Delta2 = Alpha2/(1 - Alpha2)
// if Delta1 > 0 and Delta2 > 0 then Delta = Delta2 else Delta = Delta1
// should be within .1 bin for S:N > 2 dB
float dblDenom = powf(XkRe, 2) + powf(XkIm, 2);
float dblAlpha1;
float dblAlpha2;
float dblDelta1;
float dblDelta2;
dblAlpha1 = ((XkM1Re * XkRe) + (XkM1Im * XkIm)) / dblDenom;
dblAlpha2 = ((XkP1Re * XkRe) + (XkP1Im * XkIm)) / dblDenom;
dblDelta1 = dblAlpha1 / (1 - dblAlpha1);
dblDelta2 = dblAlpha2 / (1 - dblAlpha2);
if (dblDelta1 > 0 && dblDelta2 > 0)
return dblDelta2;
else
return dblDelta1;
}
// Function to interpolate spectrum peak using simple interpolation
float SpectralPeakLocator(float XkM1Re, float XkM1Im, float XkRe, float XkIm, float XkP1Re, float XkP1Im, float * dblCentMag, char * Win)
{
// Use this for Windowed samples instead of QuinnSpectralPeakLocator
float dblLeftMag, dblRightMag;
*dblCentMag = sqrtf(powf(XkRe, 2) + powf(XkIm, 2));
dblLeftMag = sqrtf(powf(XkM1Re, 2) + powf(XkM1Im, 2));
dblRightMag = sqrtf(powf(XkP1Re, 2) + powf(XkP1Im, 2));
//Factor 1.22 empirically determine optimum for Hamming window
// For Hanning Window use factor of 1.36
// For Blackman Window use factor of 1.75
if (strcmp(Win, "Blackman"))
return 1.75 * (dblRightMag - dblLeftMag) / (dblLeftMag + *dblCentMag + dblRightMag); // Optimized for Hamming Window
if (strcmp(Win, "Hann"))
return 1.36 * (dblRightMag - dblLeftMag) / (dblLeftMag + *dblCentMag + dblRightMag); // Optimized for Hamming Window
if (strcmp(Win, "Hamming"))
return 1.22 * (dblRightMag - dblLeftMag) / (dblLeftMag + *dblCentMag + dblRightMag); // Optimized for Hamming Window
return 0;
}
// Function to detect and tune the 50 baud 2 tone leader (for all bandwidths) Updated version of SearchFor2ToneLeader2
float dblPriorFineOffset = 1000.0f;
BOOL SearchFor2ToneLeader3(short * intNewSamples, int Length, float * dblOffsetHz, int * intSN)
{
// This version uses 10Hz bin spacing. Hamming window on Goertzel, and simple spectral peak interpolator
// It requires about 50% more CPU time when running but produces more sensive leader detection and more accurate tuning
// search through the samples looking for the telltail 50 baud 2 tone pattern (nominal tones 1475, 1525 Hz)
// Find the offset in Hz (due to missmatch in transmitter - receiver tuning
// Finds the S:N (power ratio of the tones 1475 and 1525 ratioed to "noise" averaged from bins at 1425, 1450, 1550, and 1575Hz)
float dblGoertzelReal[56];
float dblGoertzelImag[56];
float dblMag[56];
float dblPower, dblLeftMag, dblRightMag;
float dblMaxPeak = 0.0, dblMaxPeakSN = 0.0, dblBinAdj;
int intInterpCnt = 0; // the count 0 to 3 of the interpolations that were < +/- .5 bin
int intIatMaxPeak = 0;
float dblAlpha = 0.3f; // Works well possibly some room for optimization Changed from .5 to .3 on Rev 0.1.5.3
float dblInterpretThreshold= 1.0f; // Good results June 6, 2014 (was .4) ' Works well possibly some room for optimization
float dblFilteredMaxPeak = 0;
int intStartBin, intStopBin;
float dblLeftCar, dblRightCar, dblBinInterpLeft, dblBinInterpRight, dblCtrR, dblCtrI, dblLeftP, dblRightP;
float dblLeftR[3], dblLeftI[3], dblRightR[3], dblRightI[3];
int i;
int Ptr = 0;
float dblAvgNoisePerBin, dblCoarsePwrSN, dblBinAdj1475, dblBinAdj1525, dblCoarseOffset = 1000;
float dblTrialOffset, dblPowerEarly, dblSNdBPwrEarly;
if ((Length) < 1200)
return FALSE; // ensure there are at least 1200 samples (5 symbols of 240 samples)
// if ((Now - dttLastGoodFrameTypeDecode > 20000) && TuningRange > 0)
{
// this is the full search over the full tuning range selected. Uses more CPU time and with possibly larger deviation once connected.
intStartBin = ((200 - TuningRange) / 10);
intStopBin = 55 - intStartBin;
dblMaxPeak = 0;
// Generate the Power magnitudes for up to 56 10 Hz bins (a function of MCB.TuningRange)
for (i = intStartBin; i <= intStopBin; i++)
{
// note hamming window reduces end effect caused by 1200 samples (not an even multiple of 240) but spreads response peaks
GoertzelRealImagHamming(intNewSamples, Ptr, 1200, i + 122.5f, &dblGoertzelReal[i], &dblGoertzelImag[i]);
dblMag[i] = powf(dblGoertzelReal[i], 2) + powf(dblGoertzelImag[i], 2); // dblMag(i) in units of power (V^2)
}
// Search the bins to locate the max S:N in the two tone signal/avg noise.
for (i = intStartBin + 5; i <= intStopBin - 10; i++) // ' +/- MCB.TuningRange from nominal
{
dblPower = sqrtf(dblMag[i] * dblMag[i + 5]); // using the product to minimize sensitivity to one strong carrier vs the two tone
// sqrt converts back to units of power from Power ^2
// don't use center noise bin as too easily corrupted by adjacent carriers
dblAvgNoisePerBin = (dblMag[i - 5] + dblMag[i - 3] + dblMag[i + 8] + dblMag[i + 10]) / 4; // Simple average
dblMaxPeak = dblPower / dblAvgNoisePerBin;
if (dblMaxPeak > dblMaxPeakSN)
{
dblMaxPeakSN = dblMaxPeak;
dblCoarsePwrSN = 10 * log10f(dblMaxPeak);
intIatMaxPeak = i + 122;
}
}
// Do the interpolation based on the two carriers at nominal 1475 and 1525Hz
if (((intIatMaxPeak - 123) >= intStartBin) && ((intIatMaxPeak - 118) <= intStopBin)) // check to ensure no index errors
{
// Interpolate the adjacent bins using QuinnSpectralPeakLocator
dblBinAdj1475 = SpectralPeakLocator(
dblGoertzelReal[intIatMaxPeak - 123], dblGoertzelImag[intIatMaxPeak - 123],
dblGoertzelReal[intIatMaxPeak - 122], dblGoertzelImag[intIatMaxPeak - 122],
dblGoertzelReal[intIatMaxPeak - 121], dblGoertzelImag[intIatMaxPeak - 121], &dblLeftMag, "Hamming");
if (dblBinAdj1475 < dblInterpretThreshold && dblBinAdj1475 > -dblInterpretThreshold)
{
dblBinAdj = dblBinAdj1475;
intInterpCnt += 1;
}
dblBinAdj1525 = SpectralPeakLocator(
dblGoertzelReal[intIatMaxPeak - 118], dblGoertzelImag[intIatMaxPeak - 118],
dblGoertzelReal[intIatMaxPeak - 117], dblGoertzelImag[intIatMaxPeak - 117],
dblGoertzelReal[intIatMaxPeak - 116], dblGoertzelImag[intIatMaxPeak - 116], &dblRightMag, "Hamming");
if (dblBinAdj1525 < dblInterpretThreshold && dblBinAdj1525 > -dblInterpretThreshold)
{
dblBinAdj += dblBinAdj1525;
intInterpCnt += 1;
}
if (intInterpCnt == 0)
{
dblPriorFineOffset = 1000.0f;
return FALSE;
}
else
{
dblBinAdj = dblBinAdj / intInterpCnt; // average the offsets that are within 1 bin
dblCoarseOffset = 10.0f * (intIatMaxPeak + dblBinAdj - 147); // compute the Coarse tuning offset in Hz
}
}
else
{
dblPriorFineOffset = 1000.0f;
return FALSE;
}
}
// Drop into Narrow Search
if (dblCoarseOffset < 999)
dblTrialOffset = dblCoarseOffset; // use the CoarseOffset calculation from above
else
dblTrialOffset = *dblOffsetHz; // use the prior offset value
if (fabsf(dblTrialOffset) > TuningRange && TuningRange > 0)
{
dblPriorFineOffset = 1000.0f;
return False;
}
dblLeftCar = 147.5f + dblTrialOffset / 10.0f; // the nominal positions of the two tone carriers based on the last computerd dblOffsetHz
dblRightCar = 152.5f + dblTrialOffset / 10.0f;
// Calculate 4 bins total for Noise values in S/N computation (calculate average noise) ' Simple average of noise bins
GoertzelRealImagHamming(intNewSamples, Ptr, 1200, 142.5f + dblTrialOffset / 10.0f, &dblCtrR, &dblCtrI); // nominal center -75 Hz
dblAvgNoisePerBin = powf(dblCtrR, 2) + powf(dblCtrI, 2);
GoertzelRealImagHamming(intNewSamples, Ptr, 1200, 145.0f + dblTrialOffset / 10.0f, &dblCtrR, &dblCtrI); // center - 50 Hz
dblAvgNoisePerBin += powf(dblCtrR, 2) + powf(dblCtrI, 2);
GoertzelRealImagHamming(intNewSamples, Ptr, 1200, 155.0 + dblTrialOffset / 10.0f, &dblCtrR, &dblCtrI); // center + 50 Hz
dblAvgNoisePerBin += powf(dblCtrR, 2) + powf(dblCtrI, 2);
GoertzelRealImagHamming(intNewSamples, Ptr, 1200, 157.5 + dblTrialOffset / 10.0f, &dblCtrR, &dblCtrI); // center + 75 Hz
dblAvgNoisePerBin += powf(dblCtrR, 2) + powf(dblCtrI, 2);
dblAvgNoisePerBin = dblAvgNoisePerBin * 0.25f; // simple average, now units of power
// Calculate one bin above and below the two nominal 2 tone positions for Quinn Spectral Peak locator
GoertzelRealImagHamming(intNewSamples, Ptr, 1200, dblLeftCar - 1, &dblLeftR[0], &dblLeftI[0]);
GoertzelRealImagHamming(intNewSamples, Ptr, 1200, dblLeftCar, &dblLeftR[1], &dblLeftI[1]);
dblLeftP = powf(dblLeftR[1], 2) + powf(dblLeftI[1], 2);
GoertzelRealImagHamming(intNewSamples, Ptr, 1200, dblLeftCar + 1, &dblLeftR[2], &dblLeftI[2]);
GoertzelRealImagHamming(intNewSamples, Ptr, 1200, dblRightCar - 1, &dblRightR[0], &dblRightI[0]);
GoertzelRealImagHamming(intNewSamples, Ptr, 1200, dblRightCar, &dblRightR[1], &dblRightI[1]);
dblRightP = powf(dblRightR[1], 2) + powf(dblRightI[1], 2);
GoertzelRealImag(intNewSamples, Ptr, 1200, dblRightCar + 1, &dblRightR[2], &dblRightI[2]);
// Calculate the total power in the two tones
// This mechanism designed to reject single carrier but average both carriers if ratios is less than 4:1
if (dblLeftP > 4 * dblRightP)
dblPower = dblRightP;
else if (dblRightP > 4 * dblLeftP)
dblPower = dblLeftP;
else
dblPower = sqrtf(dblLeftP * dblRightP);
dblSNdBPwr = 10 * log10f(dblPower / dblAvgNoisePerBin);
// Early leader detect code to calculate S:N on the first 2 symbols)
// concept is to allow more accurate framing and sync detection and reduce false leader detects
GoertzelRealImag(intNewSamples, Ptr, 480, 57.0f + dblTrialOffset / 25.0f, &dblCtrR, &dblCtrI); // nominal center -75 Hz
dblAvgNoisePerBin = powf(dblCtrR, 2) + powf(dblCtrI, 2);
GoertzelRealImag(intNewSamples, Ptr, 480, 58.0f + dblTrialOffset / 25.0f, &dblCtrR, &dblCtrI); // nominal center -75 Hz
dblAvgNoisePerBin += powf(dblCtrR, 2) + powf(dblCtrI, 2);
GoertzelRealImag(intNewSamples, Ptr, 480, 62.0f + dblTrialOffset / 25.0f, &dblCtrR, &dblCtrI); // nominal center -75 Hz
dblAvgNoisePerBin += powf(dblCtrR, 2) + powf(dblCtrI, 2);
GoertzelRealImag(intNewSamples, Ptr, 480, 63.0f + dblTrialOffset / 25.0f, &dblCtrR, &dblCtrI); // nominal center -75 Hz
dblAvgNoisePerBin = max(1000.0f, 0.25 * (dblAvgNoisePerBin + powf(dblCtrR, 2) + powf(dblCtrI, 2))); // average of 4 noise bins
dblLeftCar = 59 + dblTrialOffset / 25; // the nominal positions of the two tone carriers based on the last computerd dblOffsetHz
dblRightCar = 61 + dblTrialOffset / 25;
GoertzelRealImag(intNewSamples, Ptr, 480, dblLeftCar, &dblCtrR, &dblCtrI); // LEFT carrier
dblLeftP = powf(dblCtrR, 2) + powf(dblCtrI, 2);
GoertzelRealImag(intNewSamples, Ptr, 480, dblRightCar, &dblCtrR, &dblCtrI); // Right carrier
dblRightP = powf(dblCtrR, 2) + powf(dblCtrI, 2);
// the following rejects a single tone carrier but averages the two tones if ratio is < 4:1
if (dblLeftP > 4 * dblRightP)
dblPowerEarly = dblRightP;
else if (dblRightP > 4 * dblLeftP)
dblPowerEarly = dblLeftP;
else
dblPowerEarly = sqrtf(dblLeftP * dblRightP);
dblSNdBPwrEarly = 10 * log10f(dblPowerEarly / dblAvgNoisePerBin);
// End of Early leader detect test code
if (dblSNdBPwr > (4 + Squelch) && dblSNdBPwrEarly > Squelch && (dblAvgNoisePerBin > 100.0f || dblPriorFineOffset != 1000.0f)) // making early threshold = lower (after 3 dB compensation for bandwidth)
{
// Debugprintf("Fine Search S:N= %f dB, Early S:N= %f dblAvgNoisePerBin %f ", dblSNdBPwr, dblSNdBPwrEarly, dblAvgNoisePerBin);
// Calculate the interpolation based on the left of the two tones
dblBinInterpLeft = SpectralPeakLocator(dblLeftR[0], dblLeftI[0], dblLeftR[1], dblLeftI[1], dblLeftR[2], dblLeftI[2], &dblLeftMag, "Hamming");
// And the right of the two tones
dblBinInterpRight = SpectralPeakLocator(dblRightR[0], dblRightI[0], dblRightR[1], dblRightI[1], dblRightR[2], dblRightI[2], &dblRightMag, "Hamming");
// Weight the interpolated values in proportion to their magnitudes
dblBinInterpLeft = dblBinInterpLeft * dblLeftMag / (dblLeftMag + dblRightMag);
dblBinInterpRight = dblBinInterpRight * dblRightMag / (dblLeftMag + dblRightMag);
#ifdef ARMLINUX
{
int x = round(dblBinInterpLeft); // odd, but PI doesnt print floats properly
int y = round(dblBinInterpRight);
// Debugprintf(" SPL Left= %d SPL Right= %d Offset %f, LeftMag %f RightMag %f", x, y, *dblOffsetHz, dblLeftMag, dblRightMag);
}
#else
// Debugprintf(" SPL Left= %f SPL Right= %f, Offset %f, LeftMag %f RightMag %f",
// dblBinInterpLeft, dblBinInterpRight, *dblOffsetHz, dblLeftMag, dblRightMag);
#endif
if (fabsf(dblBinInterpLeft + dblBinInterpRight) < 1.0) // sanity check for the interpolators
{
if (dblBinInterpLeft + dblBinInterpRight > 0) // consider different bounding below
*dblOffsetHz = dblTrialOffset + min((dblBinInterpLeft + dblBinInterpRight) * 10.0f, 3); // average left and right, adjustment bounded to +/- 3Hz max
else
*dblOffsetHz = dblTrialOffset + max((dblBinInterpLeft + dblBinInterpRight) * 10.0f, -3);
// Note the addition of requiring a second detect with small offset dramatically reduces false triggering even at Squelch values of 3
// The following demonstrated good detection down to -10 dB S:N with squelch = 3 and minimal false triggering.
// Added rev 0.8.2.2 11/6/2016 RM
if (abs(dblPriorFineOffset - *dblOffsetHz) < 2.9f)
{
Debugprintf("Prior-Offset= %f", (dblPriorFineOffset - *dblOffsetHz));
// Capture power for debugging ...note: convert to 3 KHz noise bandwidth from 25Hz or 12.Hz for reporting consistancy.
Debugprintf("Ldr; S:N(3KHz) Early= %f dB, Full %f dB, Offset= %f Hz: ", dblSNdBPwrEarly - 20.8f, dblSNdBPwr - 24.77f, *dblOffsetHz);
dttStartRmtLeaderMeasure = Now;
if (AccumulateStats)
{
dblLeaderSNAvg = ((dblLeaderSNAvg * intLeaderDetects) + dblSNdBPwr) / (1 + intLeaderDetects);
intLeaderDetects++;
}
dblNCOFreq = 3000 + *dblOffsetHz; // Set the NCO frequency and phase inc for mixing
dblNCOPhaseInc = dbl2Pi * dblNCOFreq / 12000;
dttLastLeaderDetect = dttStartRmtLeaderMeasure = Now;
State = AcquireSymbolSync;
*intSN = dblSNdBPwr - 24.77; // 23.8dB accomodates ratio of 3Kz BW:10 Hz BW (10Log 3000/10 = 24.77)
// don't advance the pointer here
dblPriorFineOffset = 1000.0f;
return TRUE;
}
else
dblPriorFineOffset = *dblOffsetHz;
// always use 1 symbol inc when looking for next minimal offset
}
}
return FALSE;
}
BOOL SearchFor2ToneLeader4(short * intNewSamples, int Length, float * dblOffsetHz, int * intSN)
{
// This version uses 12.5 Hz bin spacing. Blackman window on Goertzel, and simple spectral peak interpolator optimized for Blackman
// Blackman selected for maximum rejection (about 60 dB) of the other two-tone bin 50 Hz (4 x 12.5 Hz bins) away.
// search through the samples looking for the telltail 50 baud 2 tone pattern (nominal tones 1475, 1525 Hz)
// Find the offset in Hz (due to missmatch in transmitter - receiver tuning
// Finds the S:N (power ratio of the tones 1475 and 1525 ratioed to "noise" averaged from bins at 1425, 1450, 1550, and 1575Hz)
float dblGoertzelReal[45];
float dblGoertzelImag[45];
float dblMag[45];
float dblPower, dblPwrSNdB, dblLeftMag, dblRightMag, dblAvgNoisePerBinAtPeak;
float dblRealL, dblRealR, dblImagL, dblImagR;
float dblMaxPeak = 0.0, dblMaxPeakSN = 0.0, dblMagWindow;
int intInterpCnt = 0; // the count 0 to 3 of the interpolations that were < +/- .5 bin
int intIatMaxPeak = 0;
float dblAlpha = 0.3f; // Works well possibly some room for optimization Changed from .5 to .3 on Rev 0.1.5.3
float dblInterpretThreshold= 1.0f; // Good results June 6, 2014 (was .4) ' Works well possibly some room for optimization
float dblFilteredMaxPeak = 0;
int intStartBin, intStopBin;
int i;
int Ptr = 0;
float dblAvgNoisePerBin, dblBinAdj1475, dblBinAdj1525, dblCoarseOffset = 1000;
float dblOffset = 1000; // initialize to impossible value
// This should allow tunning from nominal bins at 1425Hz to 1575Hz +/- 200 Hz tuning range
if ((Length) < 1200)
return FALSE; // ensure there are at least 1200 samples (5 symbols of 240 samples)
// if ((Now - dttLastGoodFrameTypeDecode > 20000) && TuningRange > 0)
// {
// // this is the full search over the full tuning range selected. Uses more CPU time and with possibly larger deviation once connected.
intStartBin = ((200 - TuningRange) / 12.5);
intStopBin = 44 - intStartBin;
dblMaxPeak = 0;
dblMagWindow = 0;
dblMaxPeakSN = -100;
// Generate the Power magnitudes for up to 56 10 Hz bins (a function of MCB.TuningRange)
for (i = intStartBin; i <= intStopBin; i++)
{
// note Blackman window reduced end effect but looses sensitivity so sticking with Hann window
// Test of 4/22/2018 indicated accurate Hann window (960) gives about 1-2 dB more sensitivity than Blackman window
GoertzelRealImagHann960(intNewSamples, Ptr, 960, i + 98, &dblGoertzelReal[i], &dblGoertzelImag[i]);
dblMag[i] = powf(dblGoertzelReal[i], 2) + powf(dblGoertzelImag[i], 2); // dblMag(i) in units of power (V^2)
dblMagWindow += dblMag[i];
}
// Search the bins to locate the max S:N in the two tone signal/avg noise.
for (i = intStartBin + 4; i <= intStopBin - 8; i++) // ' +/- MCB.TuningRange from nominal
{
dblPower = sqrtf(dblMag[i] * dblMag[i + 4]); // using the product to minimize sensitivity to one strong carrier vs the two tone
// sqrt converts back to units of power from Power ^2
// don't use center 7 noise bins as too easily corrupted by adjacent two-tone carriers
dblAvgNoisePerBin = (dblMagWindow - (dblMag[i - 1] + dblMag[i] + dblMag[i + 1] + dblMag[i + 2] + dblMag[i + 3] + dblMag[i + 4] + dblMag[i + 5])) / (intStopBin - (intStartBin + 7));
dblMaxPeak = dblPower / dblAvgNoisePerBin;
if (dblMaxPeak > dblMaxPeakSN)
{
dblMaxPeakSN = dblMaxPeak;
dblAvgNoisePerBinAtPeak = max(dblAvgNoisePerBin, 1000.0f);
intIatMaxPeak = i + 98;
}
}
dblMaxPeakSN = (dblMag[intIatMaxPeak - 98] + dblMag[intIatMaxPeak - 94]) / dblAvgNoisePerBinAtPeak;
dblPwrSNdB = 10.0f * log10f(dblMaxPeakSN);
// Check aquelch
if ((dblPwrSNdB > (3 * Squelch)) && dblPwrSNPower_dBPrior > (3 * Squelch))
{
// Do the interpolation based on the two carriers at nominal 1475 and 1525Hz
if (((intIatMaxPeak - 99) >= intStartBin) && ((intIatMaxPeak - 103) <= intStopBin)) // check to ensure no index errors
{
// Interpolate the adjacent bins using QuinnSpectralPeakLocator
dblBinAdj1475 = SpectralPeakLocator(
dblGoertzelReal[intIatMaxPeak - 99], dblGoertzelImag[intIatMaxPeak - 99],
dblGoertzelReal[intIatMaxPeak - 98], dblGoertzelImag[intIatMaxPeak - 98],
dblGoertzelReal[intIatMaxPeak - 97], dblGoertzelImag[intIatMaxPeak - 97], &dblLeftMag, "Hann");
dblBinAdj1525 = SpectralPeakLocator(
dblGoertzelReal[intIatMaxPeak - 95], dblGoertzelImag[intIatMaxPeak - 95],
dblGoertzelReal[intIatMaxPeak - 94], dblGoertzelImag[intIatMaxPeak - 94],
dblGoertzelReal[intIatMaxPeak - 93], dblGoertzelImag[intIatMaxPeak - 93], &dblRightMag, "Hann");
// Weight the offset calculation by the magnitude of the dblLeftMag and dblRightMag carriers
dblOffset = 12.5 * (intIatMaxPeak + dblBinAdj1475 * dblLeftMag / (dblLeftMag + dblRightMag) + dblBinAdj1525 * dblRightMag / (dblLeftMag + dblRightMag) - 118); // compute the Coarse tuning offset in Hz
if (fabsf(dblOffset) > (TuningRange + 7)) // Was 7 caused tuning problems
{
dblPwrSNPower_dBPrior = dblPwrSNdB;
return False;
}
// recompute the S:N based on the interpolated bins and average with computation 1 and 2 symbols in the future
// Use of Hann window increases sensitivity slightly (1-2 dB)
GoertzelRealImagHann120(intNewSamples, 0, 960, intIatMaxPeak + dblOffset / 12.5, &dblRealL, &dblImagL);
GoertzelRealImagHann120(intNewSamples, 0, 960, intIatMaxPeak + 4 + dblOffset / 12.5, &dblRealR, &dblImagR);
dblMaxPeakSN = (powf(dblRealL, 2) + powf(dblImagL, 2) + powf(dblRealR, 2) + powf(dblImagR, 2)) / dblAvgNoisePerBinAtPeak;
// now compute for 120 samples later
GoertzelRealImagHann120(intNewSamples, 120, 960, intIatMaxPeak + dblOffset / 12.5, &dblRealL, &dblImagL);
GoertzelRealImagHann120(intNewSamples, 120, 960, intIatMaxPeak + 4 + dblOffset / 12.5, &dblRealR, &dblImagR);
dblMaxPeakSN += (powf(dblRealL, 2) + powf(dblImagL, 2) + powf(dblRealR, 2) + powf(dblImagR, 2)) / dblAvgNoisePerBinAtPeak;
// and a third 240 samples later
GoertzelRealImagHann120(intNewSamples, 240, 960, intIatMaxPeak + dblOffset / 12.5, &dblRealL, &dblImagL);
GoertzelRealImagHann120(intNewSamples, 240, 960, intIatMaxPeak + 4 + dblOffset / 12.5, &dblRealR, &dblImagR);
dblMaxPeakSN += (powf(dblRealL, 2) + powf(dblImagL, 2) + powf(dblRealR, 2) + powf(dblImagR, 2)) / dblAvgNoisePerBinAtPeak;
dblMaxPeakSN = dblMaxPeakSN / 3; // average the dblMaxPeakSN over the three calculations
// ???? Calc Twice ????
dblMaxPeakSN = (powf(dblRealL, 2) + powf(dblImagL, 2) + powf(dblRealR, 2) + powf(dblImagR, 2)) / dblAvgNoisePerBinAtPeak;
dblPwrSNdB = 10 * log10f(dblMaxPeakSN);
if (dblPwrSNdB > 3 * Squelch) // This average power now includes two samples from symbols +120 and + 240 samples
{
//strDecodeCapture = "Ldr; S:N(3KHz) Prior=" & Format(dblPwrSNPower_dBPrior, "#.0") & "dB, Current=" & Format(dblPwrSNdB, "#.0") & "dB, Offset=" & Format(dblOffset, "##0.00") & "Hz "
Debugprintf("Ldr; S:N(3KHz) Avg= %f dB, Offset== %f Hz", dblPwrSNdB, dblOffset);
dttStartRmtLeaderMeasure = Now;
if (AccumulateStats)
{
dblLeaderSNAvg = ((dblLeaderSNAvg * intLeaderDetects) + dblPwrSNdB) / (1 + intLeaderDetects);
intLeaderDetects += 1;
}
*dblOffsetHz = dblOffset;
dblNCOFreq = 3000 + *dblOffsetHz; // Set the NCO frequency and phase inc for mixing
dblNCOPhaseInc = dbl2Pi * dblNCOFreq / 12000;
// don't advance the pointer here
State = AcquireSymbolSync;
dttLastLeaderDetect = Now;
dblPhaseDiff1_2Avg = 10; // initialize to 10 to cause initialization of exponential averager in AcquireFrameSyncRSBAvg
*intSN = round(dblPwrSNdB - 20.8); // 20.8dB accomodates ratio of 3Kz BW: (effective Blackman Window bandwidth of ~25 Hz)
return True;
}
else
{
return False;
}
}
}
dblPwrSNPower_dBPrior = dblPwrSNdB;
return FALSE;
}
// Function to look at the 2 tone leader and establishes the Symbol framing using envelope search and minimal phase error.
BOOL Acquire2ToneLeaderSymbolFraming()
{
float dblCarPh;
float dblReal, dblImag;
int intLocalPtr = intMFSReadPtr; // try advancing one symbol to minimize initial startup errors
float dblAbsPhErr;
float dblMinAbsPhErr = 5000; // initialize to an excessive value
int intIatMinErr;
float dblPhaseAtMinErr;
int intAbsPeak = 0;
int intJatPeak = 0;
int i;
// Use Phase of 1500 Hz leader to establish symbol framing. Nominal phase is 0 or 180 degrees
if ((intFilteredMixedSamplesLength - intLocalPtr) < 960)
return FALSE; // not enough
intLocalPtr = intMFSReadPtr + EnvelopeCorrelatorNew(); // should position the pointer at the symbol boundary
if (intLocalPtr < intMFSReadPtr)
return False; // use negative value of EnvelopeCorrelator to indicate insufficient correlation.
// Check 2 samples either side of the intLocalPtr for minimum phase error.(closest to Pi or -Pi)
// Could be as much as .4 Radians (~70 degrees) depending on sampling positions.
for (i = -2; i <= 2; i++) // 0 To 0 ' -2 To 2 ' for just 5 samples
{
// using the full symbol seemed to work best on weak Signals (0 to -5 dB S/N) June 15, 2015
GoertzelRealImagHann120(intFilteredMixedSamples, intLocalPtr + i, 240, 30, &dblReal, &dblImag); // Carrier at 1500 Hz nominal Positioning
dblCarPh = atan2f(dblImag, dblReal);
dblAbsPhErr = fabsf(dblCarPh - (ceil(dblCarPh / M_PI) * M_PI));
if (dblAbsPhErr < dblMinAbsPhErr)
{
dblMinAbsPhErr = dblAbsPhErr;
intIatMinErr = i;
dblPhaseAtMinErr = dblCarPh;
}
}
intMFSReadPtr = intLocalPtr + intIatMinErr;
Debugprintf("[Acquire2ToneLeaderSymbolFraming] intIatMinError= %d, Leader Length %d mS", intIatMinErr, Now - dttLastLeaderDetect);
State = AcquireFrameSync;
if (AccumulateStats)
intLeaderSyncs++;
//Debug.WriteLine(" [Acquire2ToneLeaderSymbolSync] iAtMinError = " & intIatMinErr.ToString & " Ptr = " & intMFSReadPtr.ToString & " MinAbsPhErr = " & Format(dblMinAbsPhErr, "#.00"))
//Debug.WriteLine(" [Acquire2ToneLeaderSymbolSync] Ph1500 @ MinErr = " & Format(dblPhaseAtMinErr, "#.000"))
//strDecodeCapture &= "Framing; iAtMinErr=" & intIatMinErr.ToString & ", Ptr=" & intMFSReadPtr.ToString & ", MinAbsPhErr=" & Format(dblMinAbsPhErr, "#.00") & ": "
intPhaseError = 0;
return TRUE;
}
// Function to establish symbol sync
int EnvelopeCorrelator()
{
// Compute the two symbol correlation with the Two tone leader template.
// slide the correlation one sample and repeat up to 240 steps
// keep the point of maximum or minimum correlation...and use this to identify the the symbol start.
float dblCorMax = -1000000.0f; // Preset to excessive values
float dblCorMin = 1000000.0f;
int intJatMax = 0, intJatMin = 0;
float dblCorSum, dblCorProduct, dblCorMaxProduct = 0.0;
int i,j;
short int75HzFiltered[720];
if (intFilteredMixedSamplesLength < intMFSReadPtr + 720)
return -1;
Filter75Hz(int75HzFiltered, TRUE, 720); // This filter appears to help reduce avg decode distance (10 frames) by about 14%-19% at WGN-5 May 3, 2015
for (j = 0; j < 360; j++) // Over 1.5 symbols
{
dblCorSum = 0;
for (i = 0; i < 240; i++) // over 1 50 baud symbol (may be able to reduce to 1 symbol)
{
dblCorProduct = int50BaudTwoToneLeaderTemplate[i] * int75HzFiltered[120 + i + j]; // note 120 accomdates filter delay of 120 samples
dblCorSum += dblCorProduct;
if (fabsf(dblCorProduct) > dblCorMaxProduct)
dblCorMaxProduct = fabsf(dblCorProduct);
}
if (fabsf(dblCorSum) > dblCorMax)
{
dblCorMax = fabsf(dblCorSum);
intJatMax = j;
}
}
if (AccumulateStats)
{
dblAvgCorMaxToMaxProduct = (dblAvgCorMaxToMaxProduct * intEnvelopeCors + (dblCorMax / dblCorMaxProduct)) / (intEnvelopeCors + 1);
intEnvelopeCors++;
}
// if (dblCorMax > 40 * dblCorMaxProduct)
{
Debugprintf("EnvelopeCorrelator CorMax:MaxProd= %f J= %d", dblCorMax / dblCorMaxProduct, intJatMax);
return intJatMax;
}
// else
// return -1;
}
int EnvelopeCorrelatorNew()
{
// Compute the two symbol correlation with the Two tone leader template.
// slide the correlation one sample and repeat up to 240 steps
// keep the point of maximum or minimum correlation...and use this to identify the the symbol start.
float dblCorMax = -1000000.0f; // Preset to excessive values
float dblCorMin = 1000000.0f;
int intJatMax = 0, intJatMin = 0;
float dblCorSum, dblCorProduct, dblCorMaxProduct = 0.0;
int i,j;
short int75HzFiltered[960];
if (intFilteredMixedSamplesLength < intMFSReadPtr + 960)
return -1;
Filter75Hz(int75HzFiltered, TRUE, 960); // This filter appears to help reduce avg decode distance (10 frames) by about 14%-19% at WGN-5 May 3, 2015
for (j = 360; j < 600; j++) // Over 2 symbols
{
dblCorSum = 0;
for (i = 0; i < 240; i++) // over 1 50 baud symbol (may be able to reduce to 1 symbol)
{
dblCorProduct = int50BaudTwoToneLeaderTemplate[i] * int75HzFiltered[120 + i + j]; // note 120 accomdates filter delay of 120 samples
dblCorSum += dblCorProduct;
if (fabsf(dblCorProduct) > dblCorMaxProduct)
dblCorMaxProduct = fabsf(dblCorProduct);
}
if (fabsf(dblCorSum) > dblCorMax)
{
dblCorMax = fabsf(dblCorSum);
intJatMax = j;
}
}
if (AccumulateStats)
{
dblAvgCorMaxToMaxProduct = (dblAvgCorMaxToMaxProduct * intEnvelopeCors + (dblCorMax / dblCorMaxProduct)) / (intEnvelopeCors + 1);
intEnvelopeCors++;
}
if (dblCorMax > 40 * dblCorMaxProduct)
{
Debugprintf("EnvelopeCorrelator CorMax:MaxProd= %f J= %d", dblCorMax / dblCorMaxProduct, intJatMax);
return intJatMax;
}
Debugprintf("EnvelopeCorrelator failed %d", dblCorMax / dblCorMaxProduct);
return -1;
}
// Function to acquire the Frame Sync for all Frames
BOOL AcquireFrameSyncRSB()
{
// Two improvements could be incorporated into this function:
// 1) Provide symbol tracking until the frame sync is found (small corrections should be less than 1 sample per 4 symbols ~2000 ppm)
// 2) Ability to more accurately locate the symbol center (could be handled by symbol tracking 1) above.
// This is for acquiring FSKFrameSync After Mixing Tones Mirrored around 1500 Hz. e.g. Reversed Sideband
// Frequency offset should be near 0 (normally within +/- 1 Hz)
// Locate the sync Symbol which has no phase change from the prior symbol (BPSK leader @ 1500 Hz)
int intLocalPtr = intMFSReadPtr;
int intAvailableSymbols = (intFilteredMixedSamplesLength - intMFSReadPtr) / 240;
float dblPhaseSym1; //' phase of the first symbol
float dblPhaseSym2; //' phase of the second symbol
float dblPhaseSym3; //' phase of the third symbol
float dblReal, dblImag;
float dblPhaseDiff12, dblPhaseDiff23;
int i;
if (intAvailableSymbols < 3)
return FALSE; // must have at least 360 samples to search
// Calculate the Phase for the First symbol
GoertzelRealImag(intFilteredMixedSamples, intLocalPtr, 240, 30, &dblReal, &dblImag); // Carrier at 1500 Hz nominal Positioning with no cyclic prefix
dblPhaseSym1 = atan2f(dblImag, dblReal);
intLocalPtr += 240; // advance one symbol
GoertzelRealImag(intFilteredMixedSamples, intLocalPtr, 240, 30, &dblReal, &dblImag); // Carrier at 1500 Hz nominal Positioning with no cyclic prefix
dblPhaseSym2 = atan2f(dblImag, dblReal);
intLocalPtr += 240; // advance one symbol
for (i = 0; i <= intAvailableSymbols - 3; i++)
{
// Compute the phase of the next symbol
GoertzelRealImag(intFilteredMixedSamples, intLocalPtr, 240, 30, &dblReal, &dblImag); // Carrier at 1500 Hz nominal Positioning with no cyclic prefix
dblPhaseSym3 = atan2f(dblImag, dblReal);
// Compute the phase differences between sym1-sym2, sym2-sym3
dblPhaseDiff12 = dblPhaseSym1 - dblPhaseSym2;
if (dblPhaseDiff12 > M_PI) // bound phase diff to +/- Pi
dblPhaseDiff12 -= dbl2Pi;
else if (dblPhaseDiff12 < -M_PI)
dblPhaseDiff12 += dbl2Pi;
dblPhaseDiff23 = dblPhaseSym2 - dblPhaseSym3;
if (dblPhaseDiff23 > M_PI) // bound phase diff to +/- Pi
dblPhaseDiff23 -= dbl2Pi;
else if (dblPhaseDiff23 < -M_PI)
dblPhaseDiff23 += dbl2Pi;
if (fabsf(dblPhaseDiff12) > 0.6667f * M_PI && fabsf(dblPhaseDiff23) < 0.3333f * M_PI) // Tighten the margin to 60 degrees
{
// intPSKRefPhase = (short)dblPhaseSym3 * 1000;
intLeaderRcvdMs = (int)ceil((intLocalPtr - 30) / 12); // 30 is to accomodate offset of inital pointer for filter length.
intMFSReadPtr = intLocalPtr + 240; // Position read pointer to start of the symbol following reference symbol
if (AccumulateStats)
intFrameSyncs += 1; // accumulate tuning stats
//strDecodeCapture &= "Sync; Phase1>2=" & Format(dblPhaseDiff12, "0.00") & " Phase2>3=" & Format(dblPhaseDiff23, "0.00") & ": "
return TRUE; // pointer is pointing to first 4FSK data symbol. (first symbol of frame type)
}
else
{
dblPhaseSym1 = dblPhaseSym2;
dblPhaseSym2 = dblPhaseSym3;
intLocalPtr += 240; // advance one symbol
}
}
intMFSReadPtr = intLocalPtr - 480; // back up 2 symbols for next attempt (Current Sym2 will become new Sym1)
return FALSE;
}
// Function to acquire the Frame Sync for all Frames using exponential averaging
int AcquireFrameSyncRSBAvg()
{
// This new routine uses exponential averaging on the ptr reference leader phases to minimize noise contribution
// Needs optimization of filter values and decision thresholds with actual simulator at low S:N and multipath.
// This is for acquiring FSKFrameSync After Mixing Tones Mirrored around 1500 Hz. e.g. Reversed Sideband
// Frequency offset should be near 0 (normally within +/- 1 Hz)
// Locate the sync Symbol which has no phase change from the prior symbol (50 baud BPSK leader @ 1500 Hz)
int intLocalPtr = intMFSReadPtr;
int intAvailableSymbols = (intFilteredMixedSamplesLength - intMFSReadPtr) / 240;
float dblPhaseSym1; //' phase of the first symbol
float dblPhaseSym2; //' phase of the second symbol
float dblPhaseSym3; //' phase of the third symbol
float dblReal, dblImag;
float dblPhaseDiff12, dblPhaseDiff23;
int i;
if (intAvailableSymbols < 3)
return FALSE; // must have at least 360 samples to search
// Calculate the Phase for the First symbol
GoertzelRealImagHann120(intFilteredMixedSamples, intLocalPtr, 240, 30, &dblReal, &dblImag); // Carrier at 1500 Hz nominal Positioning with no cyclic prefix
dblPhaseSym1 = atan2f(dblImag, dblReal);
intLocalPtr += 240; // advance one symbol
GoertzelRealImagHann120(intFilteredMixedSamples, intLocalPtr, 240, 30, &dblReal, &dblImag); // Carrier at 1500 Hz nominal Positioning with no cyclic prefix
dblPhaseSym2 = atan2f(dblImag, dblReal);
intLocalPtr += 240; // advance one symbol
for (i = 0; i <= intAvailableSymbols - 3; i++)
{
// Compute the phase of the next symbol
GoertzelRealImagHann120(intFilteredMixedSamples, intLocalPtr, 240, 30, &dblReal, &dblImag); // Carrier at 1500 Hz nominal Positioning with no cyclic prefix
dblPhaseSym3 = atan2f(dblImag, dblReal);
// Compute the phase differences between sym1-sym2, sym2-sym3
dblPhaseDiff12 = dblPhaseSym1 - dblPhaseSym2;
if (dblPhaseDiff12 > M_PI) // bound phase diff to +/- Pi
dblPhaseDiff12 -= dbl2Pi;
else if (dblPhaseDiff12 < -M_PI)
dblPhaseDiff12 += dbl2Pi;
if (dblPhaseDiff1_2Avg > 9)
dblPhaseDiff1_2Avg = fabsf(dblPhaseDiff12); // initialize the difference average after a prior detect
else
dblPhaseDiff1_2Avg = 0.75 * dblPhaseDiff1_2Avg + 0.25 * fabsf(dblPhaseDiff12); // exponential average
dblPhaseDiff23 = dblPhaseSym2 - dblPhaseSym3;
if (dblPhaseDiff23 > M_PI) // bound phase diff to +/- Pi
dblPhaseDiff23 -= dbl2Pi;
else if (dblPhaseDiff23 < -M_PI)
dblPhaseDiff23 += dbl2Pi;
if (fabsf(dblPhaseDiff1_2Avg ) > (0.83333 * M_PI) && fabsf(dblPhaseDiff23) < (0.25f * M_PI)) // Margin ~30 deg and 45 degrees
{
intLeaderRcvdMs = (int)ceil((intLocalPtr - 30) / 12); // 30 is to accomodate offset of inital pointer for filter length.
intMFSReadPtr = intLocalPtr + 240; // Position read pointer to start of the symbol following reference symbol
if (AccumulateStats)
intFrameSyncs += 1; // accumulate tuning stats
//strDecodeCapture &= "Sync; Phase1>2=" & Format(dblPhaseDiff12, "0.00") & " Phase2>3=" & Format(dblPhaseDiff23, "0.00") & ": "
// dttLastLeaderSync = Now;
dblPwrSNPower_dBPrior = -1000; // Reset the prior Leader power to small value to insure minimum of two symbol passes on next leader detect.
return TRUE; // pointer is pointing to first 4FSK data symbol. (first symbol of frame type)
}
// The following looks for phase errors (which should nomimally be Pi or 180 deg) and counts errors
// abandoning search on the second error, Then advancing the main intMFSReadPtr one symbol (240 samples) and returning to SearchingForLeader state.
if (fabsf(dblPhaseDiff1_2Avg) < (0.6667 * M_PI) || fabsf(dblPhaseDiff23) < (0.6667 * M_PI)) // Margin 60 deg
{
intPhaseError += 1;
dblPhaseSym1 = dblPhaseSym2;
dblPhaseSym2 = dblPhaseSym3;
intLocalPtr += 240; // advance one symbol
// if (intPhaseError > 1) // This bailout mechanism for sync failure is superior and doesn't make any assumptions about leader length
// {
// intMFSReadPtr += 240; // advance the MFSReadPointer one symbol and try to search for leader again.
// State = SearchingForLeader;
// return False;
// }
}
else
{
// keep searching available samples
dblPhaseSym1 = dblPhaseSym2;
dblPhaseSym2 = dblPhaseSym3;
intLocalPtr += 240; // advance one symbol
}
}
intMFSReadPtr = intLocalPtr - 480; // back up 2 symbols for next attempt (Current Sym2 will become new Sym1)
return FALSE;
}
// Function to Demod FrameType4FSK
BOOL DemodFrameType4FSK(int intPtr, short * intSamples, int * intToneMags)
{
float dblReal, dblImag;
int i;
if ((intFilteredMixedSamplesLength - intPtr) < 1920) // 8 symbols
return FALSE;
intToneMagsLength = 8;
for (i = 0; i < 8; i++)
{
GoertzelRealImagHann120(intSamples, intPtr, 240, 1350 / 50.0f, &dblReal, &dblImag);
intToneMags[4 * i] = (int)powf(dblReal, 2) + powf(dblImag, 2);
GoertzelRealImagHann120(intSamples, intPtr, 240, 1450 / 50.0f, &dblReal, &dblImag);
intToneMags[1 + 4 * i] = (int)powf(dblReal, 2) + powf(dblImag, 2);
GoertzelRealImagHann120(intSamples, intPtr, 240, 1550 / 50.0f, &dblReal, &dblImag);
intToneMags[2 + 4 * i] = (int)powf(dblReal, 2) + powf(dblImag, 2);
GoertzelRealImagHann120(intSamples, intPtr, 240, 1650 / 50.0f, &dblReal, &dblImag);
intToneMags[3 + 4 * i] = (int)powf(dblReal, 2) + powf(dblImag, 2);
intPtr += 240;
}
return TRUE;
}
// Function to compute the "distance" from a specific bytFrame Xored by bytID using 1 symbol parity
float ComputeDecodeDistance(int intTonePtr, int * intToneMags, UCHAR bytFrameType, UCHAR bytID)
{
// intTonePtr is the offset into the Frame type symbols. 0 for first Frame byte 16 = (4 x 4) for second frame byte
float dblDistance = 0;
int int4ToneSum;
int intToneIndex;
UCHAR bytMask = 0x30;
int j, k;
for (j = 0; j <= 3; j++) // over 4 symbols
{
int4ToneSum = 0;
for (k = 0; k <=3; k++)
{
int4ToneSum += intToneMags[intTonePtr + (4 * j) + k];
}
if (int4ToneSum == 0)
int4ToneSum = 1; // protects against possible overflow
if (j < 3)
intToneIndex = ((bytFrameType ^ bytID) & bytMask) >> (4 - 2 * j);
else
intToneIndex = ComputeTypeParity(bytFrameType ^ bytID);
dblDistance += 1.0f - ((1.0f * intToneMags[intTonePtr + (4 * j) + (3 - intToneIndex)]) / (1.0f * int4ToneSum));
bytMask = bytMask >> 2;
}
dblDistance = dblDistance / 4; // normalize back to 0 to 1 range
return dblDistance;
}
// A function to check the parity symbol used in the frame type decoding
BOOL CheckTypeParity(UCHAR bytFrameType)
{
// Returns True if Parity OK
UCHAR bytMask = 0x30; // Look at only 6 bits of data (values only 0 to 63)
UCHAR bytParitySum = 3;
UCHAR bytSym = 0;
int k;
for (k = 0; k < 3; k++)
{
bytSym = (bytMask & bytFrameType) >> (2 * (2 - k));
bytParitySum = bytParitySum ^ bytSym;
bytMask = bytMask >> 2;
}
return bytParitySum == ((bytFrameType & 0x0C0) >> 6);
}
// Function to check Parity of frame type bytes
UCHAR GetFrameTypeByte(int intTonePtr, int * intToneMags)
{
// Demodulate the byte pointed to postion of tone PTR and return it
UCHAR bytData = 0, bytParity, bytSym;
int intIndex = intTonePtr;
int j;
for (j = 0; j < 4; j++)
{
// for each 4FSK symbol (2 bits) in a byte
if (intToneMags[intIndex] > intToneMags[intIndex + 1] && intToneMags[intIndex] > intToneMags[intIndex + 2] && intToneMags[intIndex] > intToneMags[intIndex + 3])
bytSym = 3;
else if (intToneMags[intIndex + 1] > intToneMags[intIndex] && intToneMags[intIndex + 1] > intToneMags[intIndex + 2] && intToneMags[intIndex + 1] > intToneMags[intIndex + 3])
bytSym = 2;
else if (intToneMags[intIndex + 2] > intToneMags[intIndex] && intToneMags[intIndex + 2] > intToneMags[intIndex + 1] && intToneMags[intIndex + 2] > intToneMags[intIndex + 3])
bytSym = 1;
else
bytSym = 0;
if (j < 3)
bytData = (bytData << 2) + bytSym;
else
bytParity = bytSym << 6;
intIndex += 4;
}
return bytData | bytParity;
}
BOOL CheckFrameTypeParity(int intTonePtr, int * intToneMags)
{
// Demodulate the byte pointed to postion of tone PTR and check Parity Return True if OK
UCHAR bytData = GetFrameTypeByte(intTonePtr, intToneMags);
return CheckTypeParity(bytData);
}
// Function to compute the frame type by selecting the minimal distance from all valid frame types.
int MinimalDistanceFrameType(int * intToneMags, UCHAR bytSessionID)
{
float dblMinDistance1 = 5; // minimal distance for the first byte initialize to large value
float dblMinDistance2 = 5; // minimal distance for the second byte initialize to large value
float dblMinDistance3 = 5; // minimal distance for the second byte under exceptional cases initialize to large value
int intIatMinDistance1, intIatMinDistance2, intIatMinDistance3;
float dblDistance1, dblDistance2, dblDistance3;
int i;
strDecodeCapture[0] = 0;
if (ProtocolState == ISS)
{
bytValidFrameTypes = bytValidFrameTypesISS;
bytValidFrameTypesLength = bytValidFrameTypesLengthISS;
}
else
{
bytValidFrameTypes = bytValidFrameTypesALL;
bytValidFrameTypesLength = bytValidFrameTypesLengthALL;
}
// Search through all the valid frame types finding the minimal distance
// This looks like a lot of computation but measured < 1 ms for 135 iterations....RM 11/1/2016
for (i = 0; i < bytValidFrameTypesLength; i++)
{
dblDistance1 = ComputeDecodeDistance(0, intToneMags, bytValidFrameTypes[i], 0);
dblDistance2 = ComputeDecodeDistance(16, intToneMags, bytValidFrameTypes[i], bytSessionID);
if (blnPending)
dblDistance3 = ComputeDecodeDistance(16, intToneMags, bytValidFrameTypes[i], 0x3F);
else
dblDistance3 = ComputeDecodeDistance(16, intToneMags, bytValidFrameTypes[i], bytLastARQSessionID);
if (dblDistance1 < dblMinDistance1)
{
dblMinDistance1 = dblDistance1;
intIatMinDistance1 = bytValidFrameTypes[i];
}
if (dblDistance2 < dblMinDistance2)
{
dblMinDistance2 = dblDistance2;
intIatMinDistance2 = bytValidFrameTypes[i];
}
if (dblDistance3 < dblMinDistance3)
{
dblMinDistance3 = dblDistance3;
intIatMinDistance3 = bytValidFrameTypes[i];
}
}
Debugprintf("Frame Decode type %x %x %x Dist %.2f %.2f %.2f Sess %x pend %d conn %d lastsess %d",
intIatMinDistance1, intIatMinDistance2, intIatMinDistance3,
dblMinDistance1, dblMinDistance2, dblMinDistance3,
bytSessionID, blnPending, blnARQConnected, bytLastARQSessionID);
if (bytSessionID == 0x3F) // ' we are in a FEC QSO, monitoring an ARQ session or have not yet reached the ARQ Pending or Connected status
{
if (intIatMinDistance1 == intIatMinDistance2 && ((dblMinDistance1 < 0.3) || (dblMinDistance2 < 0.3)))
{
sprintf(strDecodeCapture, "%s MD Decode;2 ID=H%X, Type=H%X:%s, D1= %.2f, D2= %.2f",
strDecodeCapture, bytSessionID, intIatMinDistance1, Name(intIatMinDistance1), dblMinDistance1, dblMinDistance2);
Debugprintf("[Frame Type Decode OK ] %s", strDecodeCapture);
dblOffsetLastGoodDecode = dblOffsetHz;
return intIatMinDistance1;
}
if ((dblMinDistance1 < 0.3) && CheckFrameTypeParity(0, intToneMags) && IsDataFrame(intIatMinDistance1) ) // this would handle the case of monitoring an ARQ connection where the SessionID is not 0x3F
{
sprintf(strDecodeCapture, "%s MD Decode;3 ID=H%X, Type=H%X:%s, D1= %.2f, D2= %.2f",
strDecodeCapture, bytSessionID, intIatMinDistance1, Name(intIatMinDistance1), dblMinDistance1, dblMinDistance2);
Debugprintf("[Frame Type Decode OK ] %s", strDecodeCapture);
return intIatMinDistance1;
}
if ((dblMinDistance2 < 0.3) && CheckFrameTypeParity(16, intToneMags) && IsDataFrame(intIatMinDistance2)) // this would handle the case of monitoring an FEC transmission that failed above when the session ID is = 03F
{
sprintf(strDecodeCapture, "%s MD Decode;4 ID=H%X, Type=H%X:%s, D1= %.2f, D2= %.2f",
strDecodeCapture, bytSessionID, intIatMinDistance1, Name(intIatMinDistance2), dblMinDistance1, dblMinDistance2);
Debugprintf("[Frame Type Decode OK ] %s", strDecodeCapture);
return intIatMinDistance2;
}
return -1; // indicates poor quality decode so don't use
}
sprintf(strDecodeCapture, "%s MD Decode;12 Type1=H%X: Type2=H%X: , D1= %.2f, D2= %.2f",
strDecodeCapture, intIatMinDistance1 , intIatMinDistance2, dblMinDistance1, dblMinDistance2);
Debugprintf("[Frame Type Decode Fail] %s", strDecodeCapture);
return -1; // indicates poor quality decode so don't use
}
// Function to acquire the 4FSK frame type
int Acquire4FSKFrameType()
{
// intMFSReadPtr is pointing to start of first symbol of Frame Type (total of 8 4FSK symbols in frame type (2 bytes) + 1 parity symbol per byte
// returns -1 if minimal distance decoding is below threshold (low likelyhood of being correct)
// returns -2 if insufficient samples
// Else returns frame type 0-255
int NewType = 0;
char Offset[32];
if ((intFilteredMixedSamplesLength - intMFSReadPtr) < (240 * 8))
return -2; // Check for 8 available 4FSK Symbols
if (!DemodFrameType4FSK(intMFSReadPtr, intFilteredMixedSamples, &intToneMags[0][0]))
{
Update4FSKConstellation(&intToneMags[0][0], &intLastRcvdFrameQuality);
intMFSReadPtr += (240 * 8);
return -1;
}
intRmtLeaderMeasure = (Now - dttStartRmtLeaderMeasure);
dttLastGoodFrameTypeDecode = Now;
// Now do check received Tone array for testing minimum distance decoder
if (blnPending) // If we have a pending connection (btween the IRS first decode of ConReq until it receives a ConAck from the iSS)
NewType = MinimalDistanceFrameType(&intToneMags[0][0], bytPendingSessionID); // The pending session ID will become the session ID once connected)
else if (blnARQConnected) // If we are connected then just use the stcConnection.bytSessionID
NewType = MinimalDistanceFrameType(&intToneMags[0][0], bytSessionID);
else // not connected and not pending so use &FF (FEC or ARQ unconnected session ID
NewType = MinimalDistanceFrameType(&intToneMags[0][0], 0x3F);
sprintf(Offset, "Offset %5.1f", dblOffsetHz);
SendtoGUI('O', Offset, strlen(Offset));
if (NewType >= 0 && IsShortControlFrame(NewType)) // update the constellation if a short frame (no data to follow)
Update4FSKConstellation(&intToneMags[0][0], &intLastRcvdFrameQuality);
if (AccumulateStats)
if (NewType >= 0)
intGoodFSKFrameTypes++;
else
intFailedFSKFrameTypes++;
intMFSReadPtr += (240 * 8); // advance to read pointer to the next symbol (if there is one)
return NewType;
}
// Demodulate Functions. These are called repeatedly as samples arrive
// and buld a frame in static array bytFrameData
// Function to demodulate one carrier for all low baud rate 4FSK frame types
// Is called repeatedly to decode multitone modes
int Corrections = 0;
BOOL Demod1Car4FSK()
{
int Start = 0;
// We can't wait for the full frame as we don't have enough ram, so
// we do one character at a time, until we run out or end of frame
// Only continue if we have more than intSampPerSym * 4 chars
while (State == AcquireFrame)
{
if (intFilteredMixedSamplesLength < ((intSampPerSym * 4) + 20)) // allow for correcrions
{
// Move any unprocessessed data down buffer
// (while checking process - will use cyclic buffer eventually
// Debugprintf("Corrections %d", Corrections);
// If corrections is non-zero, we have to adjust
// number left
intFilteredMixedSamplesLength -= Corrections;
if (intFilteredMixedSamplesLength < 0)
Debugprintf("Corrupt intFilteredMixedSamplesLength");
Corrections = 0;
if (intFilteredMixedSamplesLength > 0)
memmove(intFilteredMixedSamples,
&intFilteredMixedSamples[Start], intFilteredMixedSamplesLength * 2);
return FALSE;
}
// If this is a multicarrier mode, we must call the
// decode char routing for each carrier
switch (intNumCar)
{
case 1:
intCenterFreq = 1500;
if (CarrierOk[0] == FALSE) // Don't redo if already decoded
Demod1Car4FSKChar(Start, bytFrameData[0], 0);
break;
case 2:
intCenterFreq = 1750;
if (CarrierOk[0] == FALSE)
Demod1Car4FSKChar(Start, bytFrameData[0], 0);
intCenterFreq = 1250;
if (CarrierOk[1] == FALSE)
Demod1Car4FSKChar(Start, bytFrameData[1], 1);
break;
/* case 4:
intCenterFreq = 2250;
if (CarrierOk[0] == FALSE)
Demod1Car4FSKChar(Start, bytFrameData1, 0);
intCenterFreq = 1750;
if (CarrierOk[1] == FALSE)
Demod1Car4FSKChar(Start, bytFrameData2, 1);
intCenterFreq = 1250;
if (CarrierOk[2] == FALSE)
Demod1Car4FSKChar(Start, bytFrameData3, 2);
intCenterFreq = 750;
if (CarrierOk[3] == FALSE)
Demod1Car4FSKChar(Start, bytFrameData4, 3);
break;
*/
}
charIndex++; // Index into received chars
SymbolsLeft--; // number still to decode
Start += intSampPerSym * 4; // 4 FSK bit pairs per byte
intFilteredMixedSamplesLength -= intSampPerSym * 4;
if (intFilteredMixedSamplesLength < 0)
Debugprintf("Corrupt intFilteredMixedSamplesLength");
if (SymbolsLeft == 0)
{
//- prepare for next
// If variable length packet frame header we only have header - leave rx running
if (intFrameType == PktFrameHeader)
{
State = SearchingForLeader;
// Save any unused samples
if (intFilteredMixedSamplesLength > 0 && Start > 0)
memmove(intFilteredMixedSamples,
&intFilteredMixedSamples[Start], intFilteredMixedSamplesLength * 2);
return TRUE;
}
DecodeCompleteTime = Now;
DiscardOldSamples();
ClearAllMixedSamples();
State = SearchingForLeader;
}
}
return TRUE;
}
// Function to demodulate one carrier for all low baud rate 4FSK frame types
void Demod1Car4FSKChar(int Start, UCHAR * Decoded, int Carrier)
{
// Converts intSamples to an array of bytes demodulating the 4FSK symbols with center freq intCenterFreq
// intPtr should be pointing to the approximate start of the first data symbol
// Updates bytData() with demodulated bytes
// Updates bytMinSymQuality with the minimum (range is 25 to 100) symbol making up each byte.
float dblReal, dblImag;
float dblSearchFreq;
float dblMagSum = 0;
float dblMag[4]; // The magnitude for each of the 4FSK frequency bins
UCHAR bytSym;
static UCHAR bytSymHistory[3];
int j;
UCHAR bytData = 0;
int * intToneMagsptr = &intToneMags[Carrier][intToneMagsIndex[Carrier]];
intToneMagsIndex[Carrier] += 16;
// ReDim intToneMags(4 * intNumOfSymbols - 1)
// ReDim bytData(intNumOfSymbols \ 4 - 1)
if (intBaud == 100)
dblSearchFreq = intCenterFreq + (1.5f * intBaud); // the highest freq (equiv to lowest sent freq because of sideband reversal)
else
dblSearchFreq = intCenterFreq + (3.0f * intBaud); // the highest freq (equiv to lowest sent freq because of sideband reversal)
// Do one symbol
for (j = 0; j < 4; j++) // for each 4FSK symbol (2 bits) in a byte
{
dblMagSum = 0;
if (intBaud == 100)
{
GoertzelRealImag(intFilteredMixedSamples, Start, intSampPerSym, dblSearchFreq / intBaud, &dblReal, &dblImag);
dblMag[0] = powf(dblReal,2) + powf(dblImag, 2);
dblMagSum += dblMag[0];
GoertzelRealImag(intFilteredMixedSamples, Start, intSampPerSym, (dblSearchFreq - intBaud) / intBaud, &dblReal, &dblImag);
dblMag[1] = powf(dblReal,2) + powf(dblImag, 2);
dblMagSum += dblMag[1];
GoertzelRealImag(intFilteredMixedSamples, Start, intSampPerSym, (dblSearchFreq - 2 * intBaud) / intBaud, &dblReal, &dblImag);
dblMag[2] = powf(dblReal,2) + powf(dblImag, 2);
dblMagSum += dblMag[2];
GoertzelRealImag(intFilteredMixedSamples, Start, intSampPerSym, (dblSearchFreq - 3 * intBaud) / intBaud, &dblReal,& dblImag);
dblMag[3] = powf(dblReal,2) + powf(dblImag, 2);
dblMagSum += dblMag[3];
}
else
{
dblMagSum = 0;
GoertzelRealImagHann120(intFilteredMixedSamples, Start, intSampPerSym, dblSearchFreq / intBaud, &dblReal, &dblImag);
dblMag[0] = powf(dblReal,2) + powf(dblImag, 2);
dblMagSum += dblMag[0];
GoertzelRealImagHann120(intFilteredMixedSamples, Start, intSampPerSym, (dblSearchFreq - 2 * intBaud) / intBaud, &dblReal, &dblImag);
dblMag[1] = powf(dblReal,2) + powf(dblImag, 2);
dblMagSum += dblMag[1];
GoertzelRealImagHann120(intFilteredMixedSamples, Start, intSampPerSym, (dblSearchFreq - 4 * intBaud) / intBaud, &dblReal, &dblImag);
dblMag[2] = powf(dblReal,2) + powf(dblImag, 2);
dblMagSum += dblMag[2];
GoertzelRealImagHann120(intFilteredMixedSamples, Start, intSampPerSym, (dblSearchFreq - 6 * intBaud) / intBaud, &dblReal,& dblImag);
dblMag[3] = powf(dblReal,2) + powf(dblImag, 2);
dblMagSum += dblMag[3];
}
if (dblMag[0] > dblMag[1] && dblMag[0] > dblMag[2] && dblMag[0] > dblMag[3])
bytSym = 0;
else if (dblMag[1] > dblMag[0] && dblMag[1] > dblMag[2] && dblMag[1] > dblMag[3])
bytSym = 1;
else if (dblMag[2] > dblMag[0] && dblMag[2] > dblMag[1] && dblMag[2] > dblMag[3])
bytSym = 2;
else
bytSym = 3;
bytData = (bytData << 2) + bytSym;
// !!!!!!! this needs attention !!!!!!!!
*intToneMagsptr++ = dblMag[0];
*intToneMagsptr++ = dblMag[1];
*intToneMagsptr++ = dblMag[2];
*intToneMagsptr++ = dblMag[3];
bytSymHistory[0] = bytSymHistory[1];
bytSymHistory[1] = bytSymHistory[2];
bytSymHistory[2] = bytSym;
// if ((bytSymHistory[0] != bytSymHistory[1]) && (bytSymHistory[1] != bytSymHistory[2]))
{
// only track when adjacent symbols are different (statistically about 56% of the time)
// this should allow tracking over 2000 ppm sampling rate error
// if (Start > intSampPerSym + 2)
// Track1Car4FSK(intFilteredMixedSamples, &Start, intSampPerSym, dblSearchFreq, intBaud, bytSymHistory);
}
Start += intSampPerSym; // advance the pointer one symbol
}
if (AccumulateStats)
intFSKSymbolCnt += 4;
Decoded[charIndex] = bytData;
return;
}
extern int intBW;
// Function to Demodulate Frame based on frame type
// Will be called repeatedly as new samples arrive
void DemodulateFrame(int intFrameType)
{
// Dim stcStatus As Status = Nothing
int intConstellationQuality = 0;
// ReDim bytData(-1)
strRcvFrameTag[0] = 0;
switch (intFrameType)
{
case ConReq200:
case ConReq500:
case ConReq2500:
case OConReq500:
case OConReq2500:
case PING:
case IDFRAME:
case PINGACK:
case CQ_de:
case PktFrameHeader: // Experimental Variable Length Frame
case OFDMACK:
Demod1Car4FSK();
return;
case PktFrameData: // Experimantal Variable Length Frame
if (strcmp(strMod, "4FSK") == 0)
Demod1Car4FSK();
else if (strcmp(strMod, "16QAM") == 0)
DemodQAM();
else
DemodPSK();
return;
}
switch (intFrameType & 0xFE) // Others are even/odd data frames
{
case D4FSK_500_50_E:
case D4FSK_1000_50_E:
Demod1Car4FSK();
break;
case D4PSK_200_50_E:
case D4PSK_200_100_E:
case D4PSK_500_50_E:
case D4PSK_500_100_E:
case D4PSKR_2500_50_E:
case D4PSK_2500_50_E:
case D4PSK_2500_100_E:
DemodPSK();
break;
case D16QAM_200_100_E:
case D16QAMR_500_100_E:
case D16QAM_500_100_E:
case D16QAMR_2500_100_E:
case D16QAM_2500_100_E:
DemodQAM();
break;
case DOFDM_200_55_E:
case DOFDM_500_55_E:
case DOFDM_2500_55_E:
DemodOFDM();
break;
default:
Debugprintf("Unsupported frame type %x", intFrameType);
DiscardOldSamples();
ClearAllMixedSamples();
State = SearchingForLeader;
intFilteredMixedSamplesLength = 0; // Testing
}
}
int intSNdB = 0, intQuality = 0;
2023-09-12 21:38:15 +01:00
BOOL DecodeFrame(int chan, int xxx, UCHAR * bytData)
2023-09-04 19:06:44 +01:00
{
BOOL blnDecodeOK = FALSE;
char strCallerCallsign[10] = "";
char strTargetCallsign[10] = "";
char strIDCallSign[11] = "";
char strGridSQ[20] = "";
char Reply[80];
strRcvFrameTag[0] = 0;
//DataACK/NAK and short control frames
if (IsShortControlFrame(intFrameType)) // Short Control Frames
{
blnDecodeOK = TRUE;
DrawRXFrame(1, Name(intFrameType));
goto returnframe;
}
totalRSErrors = 0;
if (IsDataFrame(intFrameType))
PrintCarrierFlags();
switch (intFrameType)
{
case PktFrameHeader:
{
// Variable Length Packet Frame Header
// 6 bits Type 10 Bits Len
int Len;
int pktNumCar;
int pktDataLen;
int pktRSLen;
frameLen = CorrectRawDataWithRS(&bytFrameData[0][0], bytData, intDataLen, intRSLen, intFrameType, 0);
if (CarrierOk[0])
{
pktRXMode = bytFrameData[0][1] >> 2;
pktNumCar = pktCarriers[pktRXMode];
Len = ((bytFrameData[0][1] & 0x3) << 8) | bytFrameData[0][2];
}
// Now only using one carrier
// else if (CarrierOk[1])
// {
// pktRXMode = bytFrameData2[1] >> 5;
// pktNumCar = ((bytFrameData2[1] & 0x1c) >> 2) + 1;
// Len = ((bytFrameData2[1] & 0x3) << 8) | bytFrameData2[2];
// }
else
{
// Cant decode
DiscardOldSamples();
ClearAllMixedSamples();
break;
}
strcpy(strMod, &pktMod[pktRXMode][0]);
// Reset to receive rest of frame
pktDataLen = (Len + (pktNumCar - 1)) / pktNumCar; // Round up
// This must match the encode settings
pktRSLen = pktDataLen >> 2; // Try 25% for now
if (pktRSLen & 1)
pktRSLen++; // Odd RS bytes no use
if (pktRSLen < 4)
pktRSLen = 4; // At least 4
SymbolsLeft = pktDataLen + pktRSLen + 3; // Data has crc + length byte
State = AcquireFrame;
intFrameType = PktFrameData;
CarrierOk[1] = CarrierOk[0] = 0;
charIndex = 0;
frameLen = 0;
intPhasesLen = 0;
memset(intToneMagsIndex, 0, sizeof(intToneMagsIndex));
intDataLen = pktDataLen;
intRSLen = pktRSLen;
intNumCar = pktNumCar;
PSKInitDone = 0;
Debugprintf("Pkt Frame Header Type %s Len %d", strMod, Len);
strlop(strMod, '/');
blnDecodeOK = TRUE;
return 0;
}
case PktFrameData:
{
if (pktFSK[pktRXMode])
{
// Need to Check RS
frameLen = CorrectRawDataWithRS(&bytFrameData[0][0], bytData, intDataLen, intRSLen, intFrameType, 0);
if (intNumCar > 1)
frameLen += CorrectRawDataWithRS(&bytFrameData[1][0], &bytData[frameLen], intDataLen, intRSLen, intFrameType, 1);
if (intNumCar > 2)
{
frameLen += CorrectRawDataWithRS(&bytFrameData[2][0], &bytData[frameLen], intDataLen, intRSLen, intFrameType, 2);
frameLen += CorrectRawDataWithRS(&bytFrameData[3][0], &bytData[frameLen], intDataLen, intRSLen, intFrameType, 3);
}
}
if (memcmp(CarrierOk, Good, intNumCar) == 0)
{
blnDecodeOK = TRUE;
// Packet Data - if KISS interface ias active
// Pass to Host as KISS frame, else pass to
// Session code
// Data in bytData len in frameLen
2023-09-12 21:38:15 +01:00
ProcessPktFrame(chan, bytData, frameLen);
2023-09-04 19:06:44 +01:00
// else
// L2Routine(bytData, frameLen, intLastRcvdFrameQuality, totalRSErrors, intNumCar, pktRXMode);
}
break;
}
default:
Debugprintf("Unrecognised frame type");
}
if (blnDecodeOK)
{
Debugprintf("[DecodeFrame] Frame: %s Decode PASS, Constellation Quality= %d", Name(intFrameType), intLastRcvdFrameQuality);
#ifdef PLOTCONSTELLATION
if (intFrameType >= 0x30 && intFrameType <= 0x38)
DrawDecode(lastGoodID); // ID or CONREQ
else
DrawDecode("PASS");
updateDisplay();
#endif
}
else
{
Debugprintf("[DecodeFrame] Frame: %s Decode FAIL, Constellation Quality= %d", Name(intFrameType), intLastRcvdFrameQuality);
#ifdef PLOTCONSTELLATION
DrawDecode("FAIL");
updateDisplay();
#endif
}
returnframe:
if (blnDecodeOK && IsDataFrame(intFrameType))
bytLastReceivedDataFrameType = intFrameType;
// if (DebugLog)
// if (blnDecodeOK)
// Debugprintf("[DecodeFrame] Frame: %s Decode PASS, Constellation Quality= %d", Name(intFrameType), intLastRcvdFrameQuality);
// else
// Debugprintf("[DecodeFrame] Frame: %s Decode FAIL, Constellation Quality= %d", Name(intFrameType), intLastRcvdFrameQuality);
return blnDecodeOK;
}
// Subroutine to update the 4FSK Constellation
void drawFastVLine(int x0, int y0, int length, int color);
void drawFastHLine(int x0, int y0, int length, int color);
void Update4FSKConstellation(int * intToneMags, int * intQuality)
{
// Subroutine to update bmpConstellation plot for 4FSK modes...
int intToneSum = 0;
int intMagMax = 0;
float dblPi4 = 0.25 * M_PI;
float dblDistanceSum = 0;
int intRad = 0;
int i, x, y;
int yCenter = 0;
int xCenter = 0;
#ifdef PLOTCONSTELLATION
int clrPixel;
int yCenter = (ConstellationHeight)/ 2;
int xCenter = (ConstellationWidth) / 2;
clearDisplay();
#endif
for (i = 0; i < intToneMagsLength; i += 4) // for the number of symbols represented by intToneMags
{
intToneSum = intToneMags[i] + intToneMags[i + 1] + intToneMags[i + 2] + intToneMags[i + 3];
if (intToneMags[i] > intToneMags[i + 1] && intToneMags[i] > intToneMags[i + 2] && intToneMags[i] > intToneMags[i + 3])
{
if (intToneSum > 0)
intRad = max(5, 42 - 80 * (intToneMags[i + 1] + intToneMags[i + 2] + intToneMags[i + 3]) / intToneSum);
dblDistanceSum += (42 - intRad);
intRad = (intRad * PLOTRADIUS) / 50; // rescale for OLED (50 instead of 42 as we rotate constellation 35 degrees
x = xCenter + intRad;
y = yCenter + intRad;
}
else if (intToneMags[i + 1] > intToneMags[i] && intToneMags[i + 1] > intToneMags[i + 2] && intToneMags[i + 1] > intToneMags[i + 3])
{
if (intToneSum > 0)
intRad = max(5, 42 - 80 * (intToneMags[i] + intToneMags[i + 2] + intToneMags[i + 3]) / intToneSum);
dblDistanceSum += (42 - intRad);
intRad = (intRad * PLOTRADIUS) / 50; // rescale for OLED (50 instead of 42 as we rotate constellation 35 degrees
x = xCenter + intRad;
y = yCenter - intRad;
}
else if (intToneMags[i + 2] > intToneMags[i] && intToneMags[i + 2] > intToneMags[i + 1] && intToneMags[i + 2] > intToneMags[i + 3])
{
if (intToneSum > 0)
intRad = max(5, 42 - 80 * (intToneMags[i + 1] + intToneMags[i] + intToneMags[i + 3]) / intToneSum);
dblDistanceSum += (42 - intRad);
intRad = (intRad * PLOTRADIUS) / 50; // rescale for OLED (50 instead of 42 as we rotate constellation 35 degrees
x = xCenter - intRad;
y = yCenter - intRad;
}
else if (intToneSum > 0)
{
intRad = max(5, 42 - 80 * (intToneMags[i + 1] + intToneMags[i + 2] + intToneMags[i]) / intToneSum);
dblDistanceSum += (42 - intRad);
intRad = (intRad * PLOTRADIUS) / 50; // rescale for OLED (50 instead of 42 as we rotate constellation 35 degrees
x = xCenter - intRad;
y = yCenter + intRad;
}
#ifdef PLOTCONSTELLATION
if (intRad < 15)
clrPixel = Tomato;
else if (intRad < 30)
clrPixel = Gold;
else
clrPixel = Lime;
mySetPixel(x, y, clrPixel);
#endif
}
*intQuality = 100 - (2.7f * (dblDistanceSum / (intToneMagsLength / 4))); // ' factor 2.7 emperically chosen for calibration (Qual range 25 to 100)
if (*intQuality < 0)
*intQuality = 0;
else if (*intQuality > 100)
*intQuality = 100;
if (AccumulateStats)
{
int4FSKQualityCnts += 1;
int4FSKQuality += *intQuality;
}
#ifdef PLOTCONSTELLATION
DrawAxes(*intQuality, shortName(intFrameType), strMod);
#endif
return;
}
// Subroutine to update the 16FSK constallation
void Update16FSKConstellation(int * intToneMags, int * intQuality)
{
// Subroutine to update bmpConstellation plot for 16FSK modes...
int intToneSum = 0;
float intMagMax = 0;
float dblDistanceSum = 0;
float dblPlotRotation = 0;
// Dim stcStatus As Status
int intRad;
// Dim clrPixel As System.Drawing.Color
int intJatMaxMag;
int i, j;
#ifdef PLOTCONSTELLATION
float dblRad;
float dblAng;
int x, y,clrPixel;
int yCenter = (ConstellationHeight - 2)/ 2;
int xCenter = (ConstellationWidth - 2) / 2;
clearDisplay();
#endif
for (i = 0; i< intToneMagsLength; i += 16) // for the number of symbols represented by intToneMags
{
intToneSum = 0;
intMagMax = 0;
for (j = 0; j < 16; j++)
{
if (intToneMags[i + j] > intMagMax)
{
intMagMax = intToneMags[i + j];
intJatMaxMag = j;
}
intToneSum += intToneMags[i + j];
}
intRad = max(5, 42 - 40 * (intToneSum - intMagMax) / intToneSum);
dblDistanceSum += (43 - intRad);
#ifdef PLOTCONSTELLATION
if (intRad < 15)
clrPixel = Tomato;
else if (intRad < 30)
clrPixel = Gold;
else
clrPixel = Lime;
// plot the symbols rotated to avoid the axis
intRad = (intRad * PLOTRADIUS) /42; // rescale for OLED
dblAng = M_PI / 16.0f + (intJatMaxMag * M_PI / 8);
x = xCenter + intRad * cosf(dblAng);
y = yCenter + intRad * sinf(dblAng);
mySetPixel(x, y, clrPixel);
#endif
}
*intQuality = max(0, (100 - 2.2 * (dblDistanceSum / (intToneMagsLength / 16)))); // factor 2.2 emperically chosen for calibration (Qual range 25 to 100)
// *intQuality = max(0, (100 - 1.0 * (dblDistanceSum / (intToneMagsLength / 16)))); // factor 2.2 emperically chosen for calibration (Qual range 25 to 100)
if(AccumulateStats)
{
int16FSKQualityCnts++;
int16FSKQuality += *intQuality;
}
#ifdef PLOTCONSTELLATION
DrawAxes(*intQuality, shortName(intFrameType), strMod);
#endif
}
// Subroutine to udpate the 8FSK Constellation
void Update8FSKConstellation(int * intToneMags, int * intQuality)
{
// Subroutine to update bmpConstellation plot for 8FSK modes...
int intToneSum = 0;
int intMagMax = 0;
float dblPi4 = 0.25 * M_PI;
float dblDistanceSum = 0;
int intRad = 0;
int i, j, intJatMaxMag;
#ifdef PLOTCONSTELLATION
float dblAng;
int yCenter = (ConstellationHeight - 2)/ 2;
int xCenter = (ConstellationWidth - 2) / 2;
unsigned short clrPixel = WHITE;
unsigned short x, y;
clearDisplay();
#endif
for (i = 0; i < intToneMagsLength; i += 8) // for the number of symbols represented by intToneMags
{
intToneSum = 0;
intMagMax = 0;
for (j = 0; j < 8; j++)
{
if (intToneMags[i + j] > intMagMax)
{
intMagMax = intToneMags[i + j];
intJatMaxMag = j;
}
intToneSum += intToneMags[i + j];
}
intRad = max(5, 42 - 40 * (intToneSum - intMagMax) / intToneSum);
dblDistanceSum += (43 - intRad);
#ifdef PLOTCONSTELLATION
if (intRad < 15)
clrPixel = Tomato;
else if (intRad < 30)
clrPixel = Gold;
else
clrPixel = Lime;
// plot the symbols rotated to avoid the axis
intRad = (intRad * PLOTRADIUS) /42; // rescale for OLED
dblAng = M_PI / 9.0f + (intJatMaxMag * M_PI / 4);
x = xCenter + intRad * cosf(dblAng);
y = yCenter + intRad * sinf(dblAng);
mySetPixel(x, y, clrPixel);
#endif
}
*intQuality = max(0, (100 - 2.0 * (dblDistanceSum / (intToneMagsLength / 8)))); // factor 2.0 emperically chosen for calibration (Qual range 25 to 100)
if(AccumulateStats)
{
int8FSKQualityCnts++;
int8FSKQuality += *intQuality;
}
#ifdef PLOTCONSTELLATION
DrawAxes(*intQuality, shortName(intFrameType), strMod);
#endif
return;
}
// Subroutine to Update the PhaseConstellation
int UpdatePhaseConstellation(short * intPhases, short * intMag, int intPSKPhase, BOOL blnQAM, BOOL OFDM)
{
// Subroutine to update bmpConstellation plot for PSK modes...
// Skip plotting and calculations of intPSKPhase(0) as this is a reference phase (9/30/2014)
float dblPhaseError;
float dblPhaseErrorSum = 0;
int intPSKIndex;
float intP = 0;
float dblRad = 0;
float dblAvgRad = 0;
float intMagMax = 0;
float dblPi4 = 0.25 * M_PI;
float dbPhaseStep;
float dblRadError = 0;
float dblPlotRotation = 0;
int intRadInner = 0, intRadOuter = 0;
float dblAvgRadOuter = 0, dblAvgRadInner = 0, dblRadErrorInner = 0, dblRadErrorOuter = 0;
int i,j, k, intQuality;
#ifdef PLOTCONSTELLATION
int intX, intY;
int yCenter = (ConstellationHeight - 2)/ 2;
int xCenter = (ConstellationWidth - 2) / 2;
unsigned short clrPixel = WHITE;
clearDisplay();
#endif
if (intPSKPhase == 4)
intPSKIndex = 0;
else
intPSKIndex = 1;
if (blnQAM)
{
intPSKPhase = 8;
intPSKIndex = 1;
dbPhaseStep = 2 * M_PI / intPSKPhase;
for (j = 1; j < intPhasesLen; j++) // skip the magnitude of the reference in calculation
{
intMagMax = max(intMagMax, intMag[j]); // find the max magnitude to auto scale
}
for (k = 1; k < intPhasesLen; k++)
{
if (intMag[k] < 0.75f * intMagMax)
{
dblAvgRadInner += intMag[k];
intRadInner++;
}
else
{
dblAvgRadOuter += intMag[k];
intRadOuter++;
}
}
dblAvgRadInner = dblAvgRadInner / intRadInner;
dblAvgRadOuter = dblAvgRadOuter / intRadOuter;
}
else
{
dbPhaseStep = 2 * M_PI / intPSKPhase;
for (j = 1; j < intPhasesLen; j++) // skip the magnitude of the reference in calculation
{
intMagMax = max(intMagMax, intMag[j]); // find the max magnitude to auto scale
dblAvgRad += intMag[j];
}
}
dblAvgRad = dblAvgRad / (intPhasesLen - 1); // the average radius
for (i = 1; i < intPhasesLen; i++) // Don't plot the first phase (reference)
{
intP = round((0.001f * intPhases[i]) / dbPhaseStep);
// compute the Phase and Radius errors
if (intMag[i] > (dblAvgRadInner + dblAvgRadOuter) / 2)
dblRadErrorOuter += fabsf(dblAvgRadOuter - intMag[i]);
else
dblRadErrorInner += fabsf(dblAvgRadInner - intMag[i]);
dblPhaseError = fabsf(((0.001 * intPhases[i]) - intP * dbPhaseStep)); // always positive and < .5 * dblPhaseStep
dblPhaseErrorSum += dblPhaseError;
#ifdef PLOTCONSTELLATION
dblRad = PLOTRADIUS * intMag[i] / intMagMax; // scale the radius dblRad based on intMagMax
intX = xCenter + dblRad * cosf(dblPlotRotation + intPhases[i] / 1000.0f);
intY = yCenter + dblRad * sinf(dblPlotRotation + intPhases[i] / 1000.0f);
if (intX > 0 && intY > 0)
if (intX != xCenter && intY != yCenter)
mySetPixel(intX, intY, Yellow); // don't plot on top of axis
#endif
}
if (blnQAM)
{
// intQuality = max(0, ((100 - 200 * (dblPhaseErrorSum / (intPhasesLen)) / dbPhaseStep))); // ignore radius error for (PSK) but include for QAM
intQuality = max(0, (1 - (dblRadErrorInner / (intRadInner * dblAvgRadInner) + dblRadErrorOuter / (intRadOuter * dblAvgRadOuter))) * (100 - 200 * (dblPhaseErrorSum / intPhasesLen) / dbPhaseStep));
// intQuality = max(0, ((100 - 200 * (dblPhaseErrorSum / (intPhasesLen)) / dbPhaseStep))); // ignore radius error for (PSK) but include for QAM
if (AccumulateStats)
{
if (OFDM)
{
intOFDMQualityCnts[RXOFDMMode] ++;
intOFDMQuality[RXOFDMMode] += intQuality;
intOFDMSymbolsDecoded += intPhasesLen;
}
else
{
intQAMQualityCnts += 1;
intQAMQuality += intQuality;
intQAMSymbolsDecoded += intPhasesLen;
}
}
}
else
{
intQuality = max(0, ((100 - 200 * (dblPhaseErrorSum / (intPhasesLen)) / dbPhaseStep))); // ignore radius error for (PSK) but include for QAM
if (AccumulateStats)
{
if (OFDM)
{
intOFDMQualityCnts[RXOFDMMode] ++;
intOFDMQuality[RXOFDMMode] += intQuality;
intOFDMSymbolsDecoded += intPhasesLen;
}
else
{
intPSKQualityCnts[intPSKIndex]++;
intPSKQuality[intPSKIndex] += intQuality;
intPSKSymbolsDecoded += intPhasesLen;
}
}
}
#ifdef PLOTCONSTELLATION
DrawAxes(intQuality, shortName(intFrameType), strMod);
#endif
return intQuality;
}
// Subroutine to track 1 carrier 4FSK. Used for both single and multiple simultaneous carrier 4FSK modes.
VOID Track1Car4FSK(short * intSamples, int * intPtr, int intSampPerSymbol, float dblSearchFreq, int intBaud, UCHAR * bytSymHistory)
{
// look at magnitude of the tone for bytHistory(1) 2 sample2 earlier and 2 samples later. and pick the maximum adjusting intPtr + or - 1
// this seems to work fine on test Mar 16, 2015. This should handle sample rate offsets (sender to receiver) up to about 2000 ppm
float dblReal, dblImag, dblMagEarly, dblMag, dblMagLate;
float dblBinToSearch = (dblSearchFreq - (intBaud * bytSymHistory[1])) / intBaud; // select the 2nd last symbol for magnitude comparison
GoertzelRealImag(intSamples, (*intPtr - intSampPerSymbol - 2), intSampPerSymbol, dblBinToSearch, &dblReal, &dblImag);
dblMagEarly = powf(dblReal, 2) + powf(dblImag, 2);
GoertzelRealImag(intSamples, (*intPtr - intSampPerSymbol), intSampPerSymbol, dblBinToSearch, &dblReal, &dblImag);
dblMag = powf(dblReal, 2) + powf(dblImag, 2);
GoertzelRealImag(intSamples, (*intPtr - intSampPerSymbol + 2), intSampPerSymbol, dblBinToSearch, &dblReal, &dblImag);
dblMagLate = powf(dblReal, 2) + powf(dblImag, 2);
if (dblMagEarly > dblMag && dblMagEarly > dblMagLate)
{
*intPtr --;
Corrections--;
if (AccumulateStats)
intAccumFSKTracking--;
}
else if (dblMagLate > dblMag && dblMagLate > dblMagEarly)
{
*intPtr ++;
Corrections++;
if (AccumulateStats)
intAccumFSKTracking++;
}
}
// Function to Decode one Carrier of PSK modulation
// Ideally want to be able to call on for each symbol, as I don't have the
// RAM to build whole frame
// Call for each set of 4 or 8 Phase Values
int pskStart = 0;
VOID Decode1CarPSK(int Carrier, BOOL OFDM)
{
unsigned int int24Bits;
UCHAR bytRawData;
int k;
int Len = intPhasesLen;
UCHAR * Decoded;
if (OFDM)
Decoded = &bytFrameData[0][0]; // Always uses same buffer
else
{
if (CarrierOk[Carrier])
return; // don't do it again
Decoded = &bytFrameData[Carrier][0];
}
pskStart = 0;
charIndex = 0;
while (Len >= 0)
{
// Phase Samples are in intPhases
switch (intPSKMode)
{
case 2: // process 8 sequential phases per byte (1 bits per phase)
for (k = 0; k < 8; k++)
{
if (k == 0)
bytRawData = 0;
else
bytRawData <<= 1;
if (intPhases[Carrier][pskStart] >= 1572 || intPhases[Carrier][pskStart]<= -1572)
bytRawData += 1;
pskStart++;
}
Decoded[charIndex++] = bytRawData;
Len -= 8;
break;
case 4: // process 4 sequential phases per byte (2 bits per phase)
for (k = 0; k < 4; k++)
{
if (k == 0)
bytRawData = 0;
else
bytRawData <<= 2;
if (intPhases[Carrier][pskStart] < 786 && intPhases[Carrier][pskStart] > -786)
{
} // Zero so no need to do anything
else if (intPhases[Carrier][pskStart] >= 786 && intPhases[Carrier][pskStart] < 2356)
bytRawData += 1;
else if (intPhases[Carrier][pskStart] >= 2356 || intPhases[Carrier][pskStart] <= -2356)
bytRawData += 2;
else
bytRawData += 3;
pskStart++;
}
Decoded[charIndex++] = bytRawData;
Len -= 4;
break;
case 8: // Process 8 sequential phases (3 bits per phase) for 24 bits or 3 bytes
// Status verified on 1 Carrier 8PSK with no RS needed for High S/N
// Assume we check for 8 available phase samples before being called
int24Bits = 0;
for (k = 0; k < 8; k++)
{
int24Bits <<= 3;
if (intPhases[Carrier][pskStart] < 393 && intPhases[Carrier][pskStart] > -393)
{
} // Zero so no need to do anything
else if (intPhases[Carrier][pskStart] >= 393 && intPhases[Carrier][pskStart] < 1179)
int24Bits += 1;
else if (intPhases[Carrier][pskStart] >= 1179 && intPhases[Carrier][pskStart] < 1965)
int24Bits += 2;
else if (intPhases[Carrier][pskStart] >= 1965 && intPhases[Carrier][pskStart] < 2751)
int24Bits += 3;
else if (intPhases[Carrier][pskStart] >= 2751 || intPhases[Carrier][pskStart] < -2751)
int24Bits += 4;
else if (intPhases[Carrier][pskStart] >= -2751 && intPhases[Carrier][pskStart] < -1965)
int24Bits += 5;
else if (intPhases[Carrier][pskStart] >= -1965 && intPhases[Carrier][pskStart] <= -1179)
int24Bits += 6;
else
int24Bits += 7;
pskStart ++;
}
Decoded[charIndex++] = int24Bits >> 16;
Decoded[charIndex++] = int24Bits >> 8;
Decoded[charIndex++] = int24Bits;
Len -= 8;
break;
case 16: // Process 2 sequential phases (4 bits per phase) for 1 bytes
for (k = 0; k < 2; k++)
{
if (k == 0)
bytRawData = 0;
else
bytRawData <<= 4;
if (intPhases[Carrier][pskStart] < 196 && intPhases[Carrier][pskStart] > -196)
{
} // Zero so no need to do anything
else if (intPhases[Carrier][pskStart] >= 196 && intPhases[Carrier][pskStart] < 589)
bytRawData += 1;
else if (intPhases[Carrier][pskStart] >= 589 && intPhases[Carrier][pskStart] < 981)
bytRawData += 2;
else if (intPhases[Carrier][pskStart] >= 981 && intPhases[Carrier][pskStart] < 1374)
bytRawData += 3;
else if (intPhases[Carrier][pskStart] >= 1374 && intPhases[Carrier][pskStart] < 1766)
bytRawData += 4;
else if (intPhases[Carrier][pskStart] >= 1766 && intPhases[Carrier][pskStart] < 2159)
bytRawData += 5;
else if (intPhases[Carrier][pskStart] >= 2159 && intPhases[Carrier][pskStart] < 2551)
bytRawData += 6;
else if (intPhases[Carrier][pskStart] >= 2551 && intPhases[Carrier][pskStart] < 2944)
bytRawData += 7;
else if (intPhases[Carrier][pskStart] >= 2944 || intPhases[Carrier][pskStart] < -2944)
bytRawData += 8;
else if (intPhases[Carrier][pskStart] >= -2944 && intPhases[Carrier][pskStart] < -2551)
bytRawData += 9;
else if (intPhases[Carrier][pskStart] >= -2551 && intPhases[Carrier][pskStart] < -2159)
bytRawData += 10;
else if (intPhases[Carrier][pskStart] >= -2159 && intPhases[Carrier][pskStart] < -1766)
bytRawData += 11;
else if (intPhases[Carrier][pskStart] >= -1766 && intPhases[Carrier][pskStart] < -1374)
bytRawData += 12;
else if (intPhases[Carrier][pskStart] >= -1374 && intPhases[Carrier][pskStart] < -981)
bytRawData += 13;
else if (intPhases[Carrier][pskStart] >= -981 && intPhases[Carrier][pskStart] < -589)
bytRawData += 14;
else
bytRawData += 15;
pskStart ++;
}
Decoded[charIndex++] = bytRawData;
Len -= 2;
break;
default:
return; //????
}
}
return;
}
// Function to compute PSK symbol tracking (all PSK modes, used for single or multiple carrier modes)
int Track1CarPSK(int floatCarFreq, int PSKMode, BOOL QAM, BOOL OFDM, float dblUnfilteredPhase, BOOL blnInit)
{
// This routine initializes and tracks the phase offset per symbol and adjust intPtr +/-1 when the offset creeps to a threshold value.
// adjusts (by Ref) intPtr 0, -1 or +1 based on a filtering of phase offset.
// this seems to work fine on test Mar 21, 2015. May need optimization after testing with higher sample rate errors.
// This should handle sample rate offsets (sender to receiver) up to about 2000 ppm
float dblAlpha = 0.3f; // low pass filter constant may want to optimize value after testing with large sample rate error.
// (Affects how much averaging is done) lower values of dblAlpha will minimize adjustments but track more slugishly.
float dblPhaseOffset;
static float dblTrackingPhase = 0;
static float dblModFactor;
static float dblRadiansPerSample; // range is .4188 @ car freq = 800 to 1.1195 @ car freq 2200
static float dblPhaseAtLastTrack;
static int intCountAtLastTrack;
static float dblFilteredPhaseOffset;
if (blnInit)
{
// dblFilterredPhase = dblUnfilteredPhase;
dblTrackingPhase = dblUnfilteredPhase;
if (PSKMode == 16)
dblModFactor = M_PI / 8;
else if (PSKMode == 8)
dblModFactor = M_PI / 4;
else if (PSKMode == 4)
dblModFactor = M_PI / 2;
else
dblModFactor = M_PI; // 2PSK
dblRadiansPerSample = (floatCarFreq * dbl2Pi) / 12000.0f;
dblPhaseOffset = dblUnfilteredPhase - dblModFactor * round(dblUnfilteredPhase / dblModFactor);
dblPhaseAtLastTrack = dblPhaseOffset;
dblFilteredPhaseOffset = dblPhaseOffset;
intCountAtLastTrack = 0;
return 0;
}
intCountAtLastTrack += 1;
dblPhaseOffset = dblUnfilteredPhase - dblModFactor * round(dblUnfilteredPhase / dblModFactor);
dblFilteredPhaseOffset = (1 - dblAlpha) * dblFilteredPhaseOffset + dblAlpha * dblPhaseOffset;
if ((dblFilteredPhaseOffset - dblPhaseAtLastTrack) > dblRadiansPerSample)
{
//Debug.WriteLine("Filtered>LastTrack: Cnt=" & intCountAtLastTrack.ToString & " Filtered = " & Format(dblFilteredPhaseOffset, "00.000") & " Offset = " & Format(dblPhaseOffset, "00.000") & " Unfiltered = " & Format(dblUnfilteredPhase, "00.000"))
dblFilteredPhaseOffset = dblPhaseOffset - dblRadiansPerSample;
dblPhaseAtLastTrack = dblFilteredPhaseOffset;
if (AccumulateStats)
{
if (OFDM)
{
intOFDMTrackAttempts++;
intAccumOFDMTracking--;
}
else
if (QAM)
{
intQAMTrackAttempts++;
intAccumQAMTracking--;
}
else
{
intPSKTrackAttempts++;
intAccumPSKTracking--;
}
}
return -1;
}
if ((dblPhaseAtLastTrack - dblFilteredPhaseOffset) > dblRadiansPerSample)
{
//'Debug.WriteLine("Filtered<LastTrack: Cnt=" & intCountAtLastTrack.ToString & " Filtered = " & Format(dblFilteredPhaseOffset, "00.000") & " Offset = " & Format(dblPhaseOffset, "00.000") & " Unfiltered = " & Format(dblUnfilteredPhase, "00.000"))
dblFilteredPhaseOffset = dblPhaseOffset + dblRadiansPerSample;
dblPhaseAtLastTrack = dblFilteredPhaseOffset;
if (AccumulateStats)
{
if (OFDM)
{
intOFDMTrackAttempts++;
intAccumOFDMTracking++;
}
else
if (QAM) // 16QAM" Then
{
intQAMTrackAttempts++;
intAccumQAMTracking++;
}
else
{
intPSKTrackAttempts++;
intAccumPSKTracking++;
}
}
return 1;
}
// 'Debug.WriteLine("Filtered Phase = " & Format(dblFilteredPhaseOffset, "00.000") & " Offset = " & Format(dblPhaseOffset, "00.000") & " Unfiltered = " & Format(dblUnfilteredPhase, "00.000"))
return 0;
}
// Function to compute the differenc of two angles
int ComputeAng1_Ang2(int intAng1, int intAng2)
{
// do an angle subtraction intAng1 minus intAng2 (in milliradians)
// Results always between -3142 and 3142 (+/- Pi)
int intDiff;
intDiff = intAng1 - intAng2;
if (intDiff < -3142)
intDiff += 6284;
else if (intDiff > 3142 )
intDiff -= 6284;
return intDiff;
}
// Subroutine to "rotate" the phases to try and set the average offset to 0.
void CorrectPhaseForTuningOffset(short * intPhase, int intPhaseLength, int intPSKMode)
{
// A tunning error of -1 Hz will rotate the phase calculation Clockwise ~ 64 milliradians (~4 degrees)
// This corrects for:
// 1) Small tuning errors which result in a phase bias (rotation) of then entire constellation
// 2) Small Transmitter/receiver drift during the frame by averaging and adjusting to constellation to the average.
// It only processes phase values close to the nominal to avoid generating too large of a correction from outliers: +/- 30 deg for 4PSK, +/- 15 deg for 8PSK
// Is very affective in handling initial tuning error.
short intPhaseMargin = 2095 / intPSKMode; // Compute the acceptable phase correction range (+/-30 degrees for 4 PSK)
short intPhaseInc = 6284 / intPSKMode;
int intTest;
int i;
int intOffset, intAvgOffset, intAvgOffsetBeginning, intAvgOffsetEnd;
int intAccOffsetCnt = 0, intAccOffsetCntBeginning = 0, intAccOffsetCntEnd = 0;
int intAccOffsetBeginning = 0, intAccOffsetEnd = 0, intAccOffset = 0;
// Note Rev 0.6.2.4 The following phase margin value increased from 2095 (120 deg) to 2793 (160 deg) yielded an improvement in decode at low S:N
intPhaseMargin = 2793 / intPSKMode; // Compute the acceptable phase correction range (+/-30 degrees for 4 PSK)
intPhaseInc = 6284 / intPSKMode;
// Compute the average offset (rotation) for all symbols within +/- intPhaseMargin of nominal
for (i = 0; i < intPhaseLength; i++)
{
intTest = (intPhase[i] / intPhaseInc);
intOffset = intPhase[i] - intTest * intPhaseInc;
if ((intOffset >= 0 && intOffset <= intPhaseMargin) || (intOffset < 0 && intOffset >= -intPhaseMargin))
{
intAccOffsetCnt += 1;
intAccOffset += intOffset;
if (i <= intPhaseLength / 4)
{
intAccOffsetCntBeginning += 1;
intAccOffsetBeginning += intOffset;
}
else if (i >= (3 * intPhaseLength) / 4)
{
intAccOffsetCntEnd += 1;
intAccOffsetEnd += intOffset;
}
}
}
if (intAccOffsetCnt > 0)
intAvgOffset = (intAccOffset / intAccOffsetCnt);
if (intAccOffsetCntBeginning > 0)
intAvgOffsetBeginning = (intAccOffsetBeginning / intAccOffsetCntBeginning);
if (intAccOffsetCntEnd > 0)
intAvgOffsetEnd = (intAccOffsetEnd / intAccOffsetCntEnd);
//Debugprintf("[CorrectPhaseForOffset] Beginning: %d End: %d Total: %d",
//intAvgOffsetBeginning, intAvgOffsetEnd, intAvgOffset);
if ((intAccOffsetCntBeginning > intPhaseLength / 8) && (intAccOffsetCntEnd > intPhaseLength / 8))
{
for (i = 0; i < intPhaseLength; i++)
{
intPhase[i] = intPhase[i] - ((intAvgOffsetBeginning * (intPhaseLength - i) / intPhaseLength) + (intAvgOffsetEnd * i / intPhaseLength));
if (intPhase[i] > 3142)
intPhase[i] -= 6284;
else if (intPhase[i] < -3142)
intPhase[i] += 6284;
}
Debugprintf("[CorrectPhaseForTuningOffset] AvgOffsetBeginning=%d AvgOffsetEnd=%d AccOffsetCnt=%d/%d",
intAvgOffsetBeginning, intAvgOffsetEnd, intAccOffsetCnt, intPhaseLength);
}
else if (intAccOffsetCnt > intPhaseLength / 2)
{
for (i = 0; i < intPhaseLength; i++)
{
intPhase[i] -= intAvgOffset;
if (intPhase[i] > 3142)
intPhase[i] -= 6284;
else if (intPhase[i] < -3142)
intPhase[i] += 6284;
}
Debugprintf("[CorrectPhaseForTuningOffset] AvgOffset=%d AccOffsetCnt=%d/%d",
intAvgOffset, intAccOffsetCnt, intPhaseLength);
}
}
// Function to Decode one Carrier of 16QAM modulation
// Call for each set of 4 or 8 Phase Values
short intCarMagThreshold[MAXCAR] = {0};
VOID Decode1CarQAM(int Carrier)
{
unsigned int intData;
int k;
float dblAlpha = 0.1f; // this determins how quickly the rolling average dblTrackingThreshold responds.
// dblAlpha value of .1 seems to work well...needs to be tested on fading channel (e.g. Multipath)
int Threshold = intCarMagThreshold[Carrier];
int Len = intPhasesLen;
UCHAR * Decoded = bytFrameData[Carrier];
if (CarrierOk[Carrier])
return; // don't do it again
pskStart = 0;
charIndex = 0;
// We calculated initial mag from reference symbol
// use filtered tracking of refernce phase amplitude
// (should be full amplitude value)
// On WGN this appears to improve decoding threshold about 1 dB 9/3/2016
while (Len >= 0)
{
// Phase Samples are in intPhases
intData = 0;
for (k = 0; k < 2; k++)
{
intData <<= 4;
if (intPhases[Carrier][pskStart] < 393 && intPhases[Carrier][pskStart] > -393)
{
} // Zero so no need to do anything
else if (intPhases[Carrier][pskStart] >= 393 && intPhases[Carrier][pskStart] < 1179)
intData += 1;
else if (intPhases[Carrier][pskStart] >= 1179 && intPhases[Carrier][pskStart] < 1965)
intData += 2;
else if (intPhases[Carrier][pskStart] >= 1965 && intPhases[Carrier][pskStart] < 2751)
intData += 3;
else if (intPhases[Carrier][pskStart] >= 2751 || intPhases[Carrier][pskStart] < -2751)
intData += 4;
else if (intPhases[Carrier][pskStart] >= -2751 && intPhases[Carrier][pskStart] < -1965)
intData += 5;
else if (intPhases[Carrier][pskStart] >= -1965 && intPhases[Carrier][pskStart] <= -1179)
intData += 6;
else
intData += 7;
if (intMags[Carrier][pskStart] < Threshold)
{
intData += 8; // add 8 to "inner circle" symbols.
Threshold = (Threshold * 900 + intMags[Carrier][pskStart] * 150) / 1000;
}
else
{
Threshold = ( Threshold * 900 + intMags[Carrier][pskStart] * 75) / 1000;
}
intCarMagThreshold[Carrier] = Threshold;
pskStart++;
}
Decoded[charIndex++] = intData;
Len -=2;
}
}
// Functions to demod all PSKData frames single or multiple carriers
VOID InitDemodPSK()
{
// Called at start of frame
int i;
float dblPhase, dblReal, dblImag;
intPSKMode = strMod[0] - '0';
PSKInitDone = TRUE;
intPhasesLen = 0;
if (intPSKMode == 8)
dblPhaseInc = 2 * M_PI * 1000 / 8;
else
dblPhaseInc = 2 * M_PI * 1000 / 4;
if (intBaud == 50)
intSampPerSym = 240;
else
intSampPerSym = 120;
if (intNumCar == 1)
floatCarFreq = 1500;
else
floatCarFreq = 1400 + (intNumCar / 2) * 200; // start at the highest carrier freq which is actually the lowest transmitted carrier due to Reverse sideband mixing
for (i= 0; i < intNumCar; i++)
{
if (intBaud == 50)
{
intCP[i] = 0;
intNforGoertzel[i] = 240;
dblFreqBin[i] = floatCarFreq / 50;
}
else if (intBaud == 100)
{
//Experimental use of Hanning Windowing
intNforGoertzel[i] = 120;
dblFreqBin[i] = floatCarFreq / 100;
intCP[i] = 0;
}
/* if (intBaud == 100 && floatCarFreq == 1500)
{
intCP[i] = 20; // These values selected for best decode percentage (92%) and best average 4PSK Quality (82) on MPP0dB channel
dblFreqBin[i] = floatCarFreq / 150;
intNforGoertzel[i] = 80;
}
else if (intBaud == 100)
{
intCP[i] = 28; // This value selected for best decoding percentage (56%) and best Averag 4PSK Quality (77) on mpg +5 dB
intNforGoertzel[i] = 60;
dblFreqBin[i] = floatCarFreq / 200;
}
else if (intBaud == 167)
{
intCP[i] = 6; // Need to optimize (little difference between 6 and 12 @ wgn5, 2 Car 500 Hz)
intNforGoertzel[i] = 60;
dblFreqBin[i] = floatCarFreq / 200;
}
*/
// Get initial Reference Phase
GoertzelRealImagHann120(intFilteredMixedSamples, 0, intNforGoertzel[i], dblFreqBin[i], &dblReal, &dblImag);
dblPhase = atan2f(dblImag, dblReal);
Track1CarPSK(floatCarFreq, strMod[0] - '0', FALSE, FALSE, dblPhase, TRUE);
intPSKPhase_1[i] = -1000 * dblPhase; // negative sign compensates for phase reverse after mixing
// Set initial mag from Reference Phase (which should be full power)
// Done here as well as in initQAM for pkt where we may switch mode midpacket
intCarMagThreshold[i] = sqrtf(powf(dblReal, 2) + powf(dblImag, 2));
intCarMagThreshold[i] *= 0.75;
floatCarFreq -= 200; // Step through each carrier Highest to lowest which is equivalent to lowest to highest before RSB mixing.
}
}
int Demod1CarPSKChar(int Start, int Carrier);
void SavePSKSamples(int i);
short WeightedAngleAvg(short intAng1, short intAng2);
int CheckCarrierPairPSK(int Base, int Dup, int frameLen)
{
int i, Len;
Debugprintf("DemodPSK Carriers %d and %d", Base, Dup);
Decode1CarPSK(Base, FALSE);
Len = CorrectRawDataWithRS(&bytFrameData[Base][0], &bytData[frameLen], intDataLen, intRSLen, intFrameType, Base);
if (CarrierOk[Base])
{
// No need to decode 2nd
CarrierOk[Dup] = 1; // So FrameOk test is passed
return Len + frameLen;
}
Debugprintf("DemodPSK Carrier %d bad, trying %d", Base, Dup);
Decode1CarPSK(Dup, FALSE); // Decode Dup carrier
Len = CorrectRawDataWithRS(&bytFrameData[Dup][0], &bytData[frameLen], intDataLen, intRSLen, intFrameType, Base); // Save as carrier 1
if (CarrierOk[Base])
{
CarrierOk[Dup] = 1; // So FrameOk test is passed
bytFrameData[Base][0] = Len;
memcpy(&bytFrameData[Base][1], &bytData[frameLen], Len); // Any retry will use first copy without new decode
return Len + frameLen;
}
// Try to average phases for the two carriers
Debugprintf("DemodPSK both bad, trying average");
for (i = 0; i <intPhasesLen; i++)
{
intPhases[Base][i] = WeightedAngleAvg(intPhases[Base][i], intPhases[Dup][i]);
}
// Try decode again on averages
Decode1CarPSK(Base, FALSE);
Len = CorrectRawDataWithRS(&bytFrameData[Base][0], &bytData[frameLen], intDataLen, intRSLen, intFrameType, Base);
if (CarrierOk[Base])
CarrierOk[Dup] = 1; // So FrameOk test is passed
return Len + frameLen;
}
void DemodPSK()
{
int Used[MAXCAR] = {0}, Carrier;
int Start = 0, i;
int MemARQRetries = 0;
// We can't wait for the full frame as we don't have enough RAM, so
// we do one DMA Buffer at a time, until we run out or end of frame
// Only continue if we have enough samples
intPSKMode = strMod[0] - '0';
while (State == AcquireFrame)
{
if (intFilteredMixedSamplesLength < intPSKMode * intSampPerSym + 10) // allow for a few phase corrections
{
// Move any unprocessessed data down buffer
// (while checking process - will use cyclic buffer eventually
if (intFilteredMixedSamplesLength > 0 && Start > 0)
memmove(intFilteredMixedSamples,
&intFilteredMixedSamples[Start], intFilteredMixedSamplesLength * 2);
return;
}
if (PSKInitDone == 0) // First time through
{
if (intFilteredMixedSamplesLength < 2 * intPSKMode * intSampPerSym + 10)
return; // Wait for at least 2 chars worth
InitDemodPSK();
intFilteredMixedSamplesLength -= intSampPerSym;
if (intFilteredMixedSamplesLength < 0)
Debugprintf("Corrupt intFilteredMixedSamplesLength");
Start += intSampPerSym;
}
// If this is a multicarrier mode, we must call the
// decode char routing for each carrier
if (intNumCar == 1)
floatCarFreq = 1500;
else
floatCarFreq = 1400 + (intNumCar / 2) * 200; // start at the highest carrier freq which is actually the lowest transmitted carrier due to Reverse sideband mixing
Used[0] = Demod1CarPSKChar(Start, 0);
if (intNumCar > 1)
{
intPhasesLen -= intPSKMode;
floatCarFreq -= 200; // Step through each carrier Highest to lowest which is equivalent to lowest to highest before RSB mixing.
Used[1] = Demod1CarPSKChar(Start, 1);
}
if (intNumCar > 2)
{
intPhasesLen -= intPSKMode;
floatCarFreq -= 200; // Step through each carrier Highest to lowest which is equivalent to lowest to highest before RSB mixing.
Used[2] = Demod1CarPSKChar(Start, 2);
intPhasesLen -= intPSKMode;
floatCarFreq -= 200; // Step through each carrier Highest to lowest which is equivalent to lowest to highest before RSB mixing.
Used[3] = Demod1CarPSKChar(Start, 3);
}
if (intNumCar > 4)
{
intPhasesLen -= intPSKMode;
floatCarFreq -= 200; // Step through each carrier Highest to lowest which is equivalent to lowest to highest before RSB mixing.
Used[4] = Demod1CarPSKChar(Start, 4);
intPhasesLen -= intPSKMode;
floatCarFreq -= 200; // Step through each carrier Highest to lowest which is equivalent to lowest to highest before RSB mixing.
Used[5] = Demod1CarPSKChar(Start, 5);
intPhasesLen -= intPSKMode;
floatCarFreq -= 200; // Step through each carrier Highest to lowest which is equivalent to lowest to highest before RSB mixing.
Used[6] = Demod1CarPSKChar(Start, 6);
intPhasesLen -= intPSKMode;
floatCarFreq -= 200; // Step through each carrier Highest to lowest which is equivalent to lowest to highest before RSB mixing.
Used[7] = Demod1CarPSKChar(Start, 7);
intPhasesLen -= intPSKMode;
floatCarFreq -= 200; // Step through each carrier Highest to lowest which is equivalent to lowest to highest before RSB mixing.
Used[8] = Demod1CarPSKChar(Start, 8);
intPhasesLen -= intPSKMode;
floatCarFreq -= 200; // Step through each carrier Highest to lowest which is equivalent to lowest to highest before RSB mixing.
Used[9] = Demod1CarPSKChar(Start, 9);
}
if (intPSKMode == 4)
SymbolsLeft--; // number still to decode
else
SymbolsLeft -=3;
// If/when we reenable phase correstion we can take average of Used values.
// ?? Should be also keep start value per carrier ??
Start += Used[0];
intFilteredMixedSamplesLength -= Used[0];
if (intFilteredMixedSamplesLength < 0)
Debugprintf("Corrupt intFilteredMixedSamplesLength");
if (SymbolsLeft > 0)
continue;
// Decode the phases
DecodeCompleteTime = Now;
// CorrectPhaseForTuningOffset(&intPhases[0][0], intPhasesLen, strMod);
// if (intNumCar > 1)
// CorrectPhaseForTuningOffset(&intPhases[1][0], intPhasesLen, strMod);
if (intNumCar > 2)
{
// CorrectPhaseForTuningOffset(&intPhases[2][0], intPhasesLen, strMod);
// CorrectPhaseForTuningOffset(&intPhases[3][0], intPhasesLen, strMod);
}
if (intNumCar > 4)
{
// CorrectPhaseForTuningOffset(&intPhases[4][0], intPhasesLen, strMod);
// CorrectPhaseForTuningOffset(&intPhases[5][0], intPhasesLen, strMod);
// CorrectPhaseForTuningOffset(&intPhases[6][0], intPhasesLen, strMod);
// CorrectPhaseForTuningOffset(&intPhases[7][0], intPhasesLen, strMod);
}
// Rick uses the last carier for Quality
intLastRcvdFrameQuality = UpdatePhaseConstellation(&intPhases[intNumCar - 1][0], &intMags[intNumCar - 1][0], strMod[0] - '0', FALSE, FALSE);
// prepare for next
State = SearchingForLeader;
DiscardOldSamples();
ClearAllMixedSamples();
if (strchr(strMod, 'R'))
{
// Robust Mode - data is repeated (1-2 or 1-6, 2-7, etc
if (intNumCar == 2)
{
frameLen = CheckCarrierPairPSK(0, 1, 0);
return;
}
//Only have 2 or 10 (500 or 2500 modes)
frameLen = CheckCarrierPairPSK(0, 5, 0);
frameLen = CheckCarrierPairPSK(1, 6, frameLen);
frameLen = CheckCarrierPairPSK(2, 7, frameLen);
frameLen = CheckCarrierPairPSK(3, 8, frameLen);
frameLen = CheckCarrierPairPSK(4, 9, frameLen);
return;
}
// Non -robust
frameLen = 0;
for (i = 0; i < intNumCar; i++)
{
Decode1CarPSK(i, FALSE);
frameLen += CorrectRawDataWithRS(&bytFrameData[i][0], &bytData[frameLen], intDataLen, intRSLen, intFrameType, i);
}
// If variable length packet frame header we only have header - leave rx running
if (intFrameType == PktFrameHeader)
{
State = SearchingForLeader;
// Save any unused samples
if (intFilteredMixedSamplesLength > 0 && Start > 0)
memmove(intFilteredMixedSamples,
&intFilteredMixedSamples[Start], intFilteredMixedSamplesLength * 2);
return;
}
#ifdef MEMORYARQ
for (Carrier = 0; Carrier < intNumCar; Carrier++)
{
if (!CarrierOk[Carrier])
{
// Decode error - save data for MEM ARQ
SavePSKSamples(Carrier);
if (intSumCounts[Carrier] > 1)
{
Decode1CarQAM(Carrier); // try to decode based on the WeightedAveragePhases
MemARQRetries++;
}
}
}
if (MemARQRetries)
{
// We've retryed to decode - see if ok now
int OKNow = TRUE;
Debugprintf("DemodPSK retry RS on MEM ARQ Corrected frames");
frameLen = 0;
for (Carrier = 0; Carrier < intNumCar; Carrier++)
{
frameLen += CorrectRawDataWithRS(bytFrameData[Carrier], bytData, intDataLen, intRSLen, intFrameType, Carrier);
if (CarrierOk[Carrier] == 0)
OKNow = FALSE;
}
if (OKNow && AccumulateStats)
intGoodPSKSummationDecodes++;
}
#endif
}
return;
}
// Function to demodulate one carrier for all PSK frame types
int Demod1CarPSKChar(int Start, int Carrier)
{
// Converts intSample to an array of differential phase and magnitude values for the Specific Carrier Freq
// intPtr should be pointing to the approximate start of the first reference/training symbol (1 of 3)
// intPhase() is an array of phase values (in milliradians range of 0 to 6283) for each symbol
// intMag() is an array of Magnitude values (not used in PSK decoding but for constellation plotting or QAM decoding)
// Objective is to use Minimum Phase Error Tracking to maintain optimum pointer position
// This is called for one DMA buffer of samples (normally 1200)
float dblReal, dblImag;
int intMiliRadPerSample = floatCarFreq * M_PI / 6;
int i;
int intNumOfSymbols = intPSKMode;
int origStart = Start;;
if (CarrierOk[Carrier]) // Already decoded this carrier?
{
intPhasesLen += intNumOfSymbols;
return intSampPerSym * intNumOfSymbols;
}
for (i = 0; i < intNumOfSymbols; i++)
{
GoertzelRealImag(intFilteredMixedSamples, Start, intNforGoertzel[Carrier], dblFreqBin[Carrier], &dblReal, &dblImag);
// GoertzelRealImagHann120(intFilteredMixedSamples, Start, intNforGoertzel[Carrier], dblFreqBin[Carrier], &dblReal, &dblImag);
intMags[Carrier][intPhasesLen] = sqrtf(powf(dblReal, 2) + powf(dblImag, 2));
intPSKPhase_0[Carrier] = -1000 * atan2f(dblImag, dblReal);
intPhases[Carrier][intPhasesLen] = (ComputeAng1_Ang2(intPSKPhase_0[Carrier], intPSKPhase_1[Carrier]));
/*
if (Carrier == 0)
{
Corrections = Track1CarPSK(floatCarFreq, strMod, atan2f(dblImag, dblReal), FALSE);
if (Corrections != 0)
{
Start += Corrections;
if (intCP[i] == 0)
GoertzelRealImagHanning(intFilteredMixedSamples, Start, intNforGoertzel[Carrier], dblFreqBin[Carrier], &dblReal, &dblImag);
else
GoertzelRealImag(intFilteredMixedSamples, Start + intCP[Carrier], intNforGoertzel[Carrier], dblFreqBin[Carrier], &dblReal, &dblImag);
intPSKPhase_0[Carrier] = 1000 * atan2f(dblImag, dblReal);
}
}
*/
intPSKPhase_1[Carrier] = intPSKPhase_0[Carrier];
intPhasesLen++;
Start += intSampPerSym;
}
if (AccumulateStats)
intPSKSymbolCnt += intNumOfSymbols;
return (Start - origStart); // Symbols we've consumed
}
VOID InitDemodQAM()
{
// Called at start of frame
int i;
float dblPhase, dblReal, dblImag;
intPSKMode = 8; // 16QAM uses 8 PSK
dblPhaseInc = 2 * M_PI * 1000 / 8;
intPhasesLen = 0;
PSKInitDone = TRUE;
intSampPerSym = 120;
if (intNumCar == 1)
floatCarFreq = 1500;
else
floatCarFreq = 1400 + (intNumCar / 2) * 200; // start at the highest carrier freq which is actually the lowest transmitted carrier due to Reverse sideband mixing
for (i= 0; i < intNumCar; i++)
{
// Only 100 Hz for QAM
intCP[i] = 0;
intNforGoertzel[i] = 120;
dblFreqBin[i] = floatCarFreq / 100;
// Get initial Reference Phase
GoertzelRealImagHanning(intFilteredMixedSamples, intCP[i], intNforGoertzel[i], dblFreqBin[i], &dblReal, &dblImag);
dblPhase = atan2f(dblImag, dblReal);
// Set initial mag from Reference Phase (which should be full power)
intCarMagThreshold[i] = sqrtf(powf(dblReal, 2) + powf(dblImag, 2));
intCarMagThreshold[i] *= 0.75;
Track1CarPSK(floatCarFreq, 8, TRUE, FALSE, dblPhase, TRUE);
intPSKPhase_1[i] = 1000 * dblPhase;
floatCarFreq -= 200; // Step through each carrier Highest to lowest which is equivalent to lowest to highest before RSB mixing.
}
}
int Demod1CarQAMChar(int Start, int Carrier);
// Function to average two angles using magnitude weighting
short WeightedAngleAvg(short intAng1, short intAng2)
{
// Ang1 and Ang 2 are in the range of -3142 to + 3142 (miliradians)
// works but should come up with a routine that avoids Sin, Cos, Atan2
// Modified in Rev 0.3.5.1 to "weight" averaging by intMag1 and intMag2 (why!!!)
float dblSumX, dblSumY;
dblSumX = cosf(intAng1 / 1000.0) + cosf(intAng2 / 1000.0);
dblSumY = sinf(intAng1 / 1000.0) + sinf(intAng2 / 1000.0);
return (1000 * atan2f(dblSumY, dblSumX));
}
#ifdef MEMORYARQ
void SaveQAMSamples(int i)
{
int m;
if (intSumCounts[i] == 0)
{
// First try - initialize Sum counts Phase average and Mag Average
for (m = 0; m < intPhasesLen; m++)
{
intCarPhaseAvg[i][m] = intPhases[i][m];
intCarMagAvg[i][m] = intMags[i][m];
}
}
else
{
for (m = 0; m < intPhasesLen; m++)
{
intCarPhaseAvg[i][m] = WeightedAngleAvg(intCarPhaseAvg[i][m], intPhases[i][m]);
intPhases[i][m] = intCarPhaseAvg[i][m];
// Use simple weighted average for Mags
intCarMagAvg[i][m] = (intCarMagAvg[i][m] * intSumCounts[i] + intMags[i][m]) / (intSumCounts[i] + 1);
intMags[i][m] = intCarMagAvg[i][m];
}
}
intSumCounts[i]++;
}
void SavePSKSamples(int i)
{
int m;
if (intSumCounts[i] == 0)
{
// First try - initialize Sum counts Phase average and Mag Average
for (m = 0; m < intPhasesLen; m++)
{
intCarPhaseAvg[i][m] = intPhases[i][m];
}
}
else
{
for (m = 0; m < intPhasesLen; m++)
{
intCarPhaseAvg[i][m] = WeightedAngleAvg(intCarPhaseAvg[i][m], intPhases[i][m]);
intPhases[i][m] = intCarPhaseAvg[i][m];
}
}
intSumCounts[i]++;
}
#endif
int CheckCarrierPair(int Base, int Dup, int frameLen)
{
int i, Len;
Debugprintf("DemodQAMR Carriers %d and %d", Base, Dup);
Decode1CarQAM(Base);
Len = CorrectRawDataWithRS(&bytFrameData[Base][0], &bytData[frameLen], intDataLen, intRSLen, intFrameType, Base);
if (CarrierOk[Base])
{
// No need to decode 2nd
CarrierOk[Dup] = 1; // So FrameOk test is passed
Debugprintf("DemodQAMR Returning Len %d", Len);
return Len + frameLen;
}
Debugprintf("DemodQAMR Carrier %d bad, trying %d", Base, Dup);
Decode1CarQAM(Dup); // Decode Dup carrier
Len = CorrectRawDataWithRS(&bytFrameData[Dup][0], &bytData[frameLen], intDataLen, intRSLen, intFrameType, Base); // Save as carrier 1
if (CarrierOk[Base])
{
CarrierOk[Dup] = 1; // So FrameOk test is passed
bytFrameData[Base][0] = Len;
memcpy(&bytFrameData[Base][1], &bytData[frameLen], Len); // Any retry will use first copy without new decode
Debugprintf("DemodQAMR Returning Len %d", Len);
return Len + frameLen;
}
// Try to average phases for the two carriers
Debugprintf("DemodQAMR both bad, trying average");
for (i = 0; i <intPhasesLen; i++)
{
intPhases[Base][i] = WeightedAngleAvg(intPhases[Base][i], intPhases[Dup][i]);
intMags[Base][i] = (intMags[Base][i] + intMags[Dup][i]) / 2;
}
// Try decode again on averages
Decode1CarQAM(Base);
Len = CorrectRawDataWithRS(&bytFrameData[Base][0], &bytData[frameLen], intDataLen, intRSLen, intFrameType, Base);
if (CarrierOk[Base])
CarrierOk[Dup] = 1; // So FrameOk test is passed
Debugprintf("DemodQAMR Returning Len %d", Len);
return Len + frameLen;
}
BOOL DemodQAM()
{
int Used = 0;
int Start = 0;
int i, MemARQOk = 0;
// We can't wait for the full frame as we don't have enough RAM, so
// we do one DMA Buffer at a time, until we run out or end of frame
// Only continue if we have enough samples
while (State == AcquireFrame)
{
if (intFilteredMixedSamplesLength < 8 * intSampPerSym + 10) // allow for a few phase corrections
{
// Move any unprocessessed data down buffer
// (while checking process - will use cyclic buffer eventually
if (intFilteredMixedSamplesLength > 0)
memmove(intFilteredMixedSamples,
&intFilteredMixedSamples[Start], intFilteredMixedSamplesLength * 2);
return FALSE;
}
if (PSKInitDone == 0) // First time through
{
if (intFilteredMixedSamplesLength < 9 * intSampPerSym + 10)
return FALSE; // Wait for at least 2 chars worth
InitDemodQAM();
intFilteredMixedSamplesLength -= intSampPerSym;
Start += intSampPerSym;
}
// If this is a multicarrier mode, we must call the
// decode char routine for each carrier
if (intNumCar == 1)
floatCarFreq = 1500;
else
floatCarFreq = 1400 + (intNumCar / 2) * 200; // start at the highest carrier freq which is actually the lowest transmitted carrier due to Reverse sideband mixing
Used = Demod1CarQAMChar(Start, 0); // demods 2 phase values - enough for one char
if (intNumCar > 1)
{
intPhasesLen -= 2;
floatCarFreq -= 200; // Step through each carrier Highest to lowest which is equivalent to lowest to highest before RSB mixing.
Demod1CarQAMChar(Start, 1);
}
if (intNumCar > 2)
{
intPhasesLen -= 2;
floatCarFreq -= 200; // Step through each carrier Highest to lowest which is equivalent to lowest to highest before RSB mixing.
Demod1CarQAMChar(Start, 2);
intPhasesLen -= 2;
floatCarFreq -= 200; // Step through each carrier Highest to lowest which is equivalent to lowest to highest before RSB mixing.
Demod1CarQAMChar(Start, 3);
}
if (intNumCar > 4)
{
intPhasesLen -= 2;
floatCarFreq -= 200; // Step through each carrier Highest to lowest which is equivalent to lowest to highest before RSB mixing.
Demod1CarQAMChar(Start, 4);
intPhasesLen -= 2;
floatCarFreq -= 200; // Step through each carrier Highest to lowest which is equivalent to lowest to highest before RSB mixing.
Demod1CarQAMChar(Start, 5);
intPhasesLen -= 2;
floatCarFreq -= 200; // Step through each carrier Highest to lowest which is equivalent to lowest to highest before RSB mixing.
Demod1CarQAMChar(Start, 6);
intPhasesLen -= 2;
floatCarFreq -= 200; // Step through each carrier Highest to lowest which is equivalent to lowest to highest before RSB mixing.
Demod1CarQAMChar(Start, 7);
intPhasesLen -= 2;
floatCarFreq -= 200; // Step through each carrier Highest to lowest which is equivalent to lowest to highest before RSB mixing.
Demod1CarQAMChar(Start, 8);
intPhasesLen -= 2;
floatCarFreq -= 200; // Step through each carrier Highest to lowest which is equivalent to lowest to highest before RSB mixing.
Demod1CarQAMChar(Start, 9);
}
SymbolsLeft--; // number still to decode - we've done one
Start += Used;
intFilteredMixedSamplesLength -= Used;
if (SymbolsLeft <= 0)
{
// Frame complete - decode it
DecodeCompleteTime = Now;
// CorrectPhaseForTuningOffset(&intPhases[0][0], intPhasesLen, strMod);
// if (intNumCar > 1)
// CorrectPhaseForTuningOffset(&intPhases[1][0], intPhasesLen, strMod);
if (intNumCar > 2)
{
// CorrectPhaseForTuningOffset(&intPhases[2][0], intPhasesLen, strMod);
// CorrectPhaseForTuningOffset(&intPhases[3][0], intPhasesLen, strMod);
}
if (intNumCar > 4)
{
// CorrectPhaseForTuningOffset(&intPhases[4][0], intPhasesLen, strMod);
// CorrectPhaseForTuningOffset(&intPhases[5][0], intPhasesLen, strMod);
// CorrectPhaseForTuningOffset(&intPhases[6][0], intPhasesLen, strMod);
// CorrectPhaseForTuningOffset(&intPhases[7][0], intPhasesLen, strMod);
}
intLastRcvdFrameQuality = UpdatePhaseConstellation(&intPhases[intNumCar - 1][0], &intMags[intNumCar - 1][0], 8, TRUE, FALSE);
// prepare for next so we can exit when we have finished decode
DiscardOldSamples();
ClearAllMixedSamples();
State = SearchingForLeader;
if (strchr(strMod, 'R'))
{
// Robust Mode - data is repeated (1-2 or 1-6, 2-7, etc
if (intNumCar == 2)
{
frameLen = CheckCarrierPair(0, 1, 0);
return TRUE;
}
//Only have 2 or 10 (500 or 2500 modes)
frameLen = CheckCarrierPair(0, 5, 0);
frameLen = CheckCarrierPair(1, 6, frameLen);
frameLen = CheckCarrierPair(2, 7, frameLen);
frameLen = CheckCarrierPair(3, 8, frameLen);
frameLen = CheckCarrierPair(4, 9, frameLen);
return TRUE;
}
// Non -robust
Decode1CarQAM(0);
frameLen = CorrectRawDataWithRS(&bytFrameData[0][0], bytData, intDataLen, intRSLen, intFrameType, 0);
if (intNumCar > 1)
{
Decode1CarQAM(1);
frameLen += CorrectRawDataWithRS(&bytFrameData[1][0], &bytData[frameLen], intDataLen, intRSLen, intFrameType, 1);
}
if (intNumCar > 2)
{
Decode1CarQAM(2);
Decode1CarQAM(3);
frameLen += CorrectRawDataWithRS(&bytFrameData[2][0], &bytData[frameLen], intDataLen, intRSLen, intFrameType, 2);
frameLen += CorrectRawDataWithRS(&bytFrameData[3][0], &bytData[frameLen], intDataLen, intRSLen, intFrameType, 3);
}
if (intNumCar > 4)
{
Decode1CarQAM(4);
Decode1CarQAM(5);
Decode1CarQAM(6);
Decode1CarQAM(7);
Decode1CarQAM(8);
Decode1CarQAM(9);
frameLen += CorrectRawDataWithRS(&bytFrameData[4][0], &bytData[frameLen], intDataLen, intRSLen, intFrameType, 4);
frameLen += CorrectRawDataWithRS(&bytFrameData[5][0], &bytData[frameLen], intDataLen, intRSLen, intFrameType, 5);
frameLen += CorrectRawDataWithRS(&bytFrameData[6][0], &bytData[frameLen], intDataLen, intRSLen, intFrameType, 6);
frameLen += CorrectRawDataWithRS(&bytFrameData[7][0], &bytData[frameLen], intDataLen, intRSLen, intFrameType, 7);
frameLen += CorrectRawDataWithRS(&bytFrameData[8][0], &bytData[frameLen], intDataLen, intRSLen, intFrameType, 8);
frameLen += CorrectRawDataWithRS(&bytFrameData[9][0], &bytData[frameLen], intDataLen, intRSLen, intFrameType, 9);
}
// Check Data
if (memcmp(CarrierOk, Good, intNumCar) == 0)
return TRUE;
// Bad decode if we have Memory ARQ try it
#ifdef MEMORYARQ
for (i = 0; i < intNumCar; i++)
{
if (!CarrierOk[i] && intFrameType != PktFrameHeader)
{
// Decode error - save data for MEM ARQ
SaveQAMSamples(i);
if (intSumCounts[0] > 1)
{
MemARQOk = 1;
Decode1CarQAM(i); // try to decode based on the WeightedAveragePhases
}
}
}
if (MemARQOk == 0) // Havent averaged yet
return TRUE;
// We've tried to correct - see if it worked
Debugprintf("DemodQAM Trying MEM ARQ");
// Non -robust
frameLen = 0;
for (i = 0; i < intNumCar; i++)
{
frameLen += CorrectRawDataWithRS(&bytFrameData[i][0], &bytData[frameLen], intDataLen, intRSLen, intFrameType, i);
}
// Check Data
if (memcmp(CarrierOk, Good, intNumCar) == 0)
{
Debugprintf("DemodQAM MEM ARQ Corrected frame");
intGoodQAMSummationDecodes++;
}
#endif
}
}
return TRUE;
}
int Demod1CarQAMChar(int Start, int Carrier)
{
// Converts intSample to an array of differential phase and magnitude values for the Specific Carrier Freq
// intPtr should be pointing to the approximate start of the first reference/training symbol (1 of 3)
// intPhase() is an array of phase values (in milliradians range of 0 to 6283) for each symbol
// intMag() is an array of Magnitude values (not used in PSK decoding but for constellation plotting or QAM decoding)
// Objective is to use Minimum Phase Error Tracking to maintain optimum pointer position
// This is called for one DMA buffer of samples (normally 1200)
float dblReal, dblImag;
int intMiliRadPerSample = floatCarFreq * M_PI / 6;
int i;
int intNumOfSymbols = 2;
int origStart = Start;;
if (CarrierOk[Carrier]) // Already decoded this carrier?
{
intPhasesLen += intNumOfSymbols;
return intSampPerSym * intNumOfSymbols;
}
for (i = 0; i < intNumOfSymbols; i++)
{
// GoertzelRealImag(intFilteredMixedSamples, Start + intCP[Carrier], intNforGoertzel[Carrier], dblFreqBin[Carrier], &dblReal, &dblImag);
GoertzelRealImagHanning(intFilteredMixedSamples, Start + intCP[Carrier], intNforGoertzel[Carrier], dblFreqBin[Carrier], &dblReal, &dblImag);
intMags[Carrier][intPhasesLen] = sqrtf(powf(dblReal, 2) + powf(dblImag, 2));
intPSKPhase_0[Carrier] = 1000 * atan2f(dblImag, dblReal);
intPhases[Carrier][intPhasesLen] = -(ComputeAng1_Ang2(intPSKPhase_0[Carrier], intPSKPhase_1[Carrier]));
/*
if (Carrier == 0)
{
Corrections = Track1CarPSK(floatCarFreq, strMod, atan2f(dblImag, dblReal), FALSE);
if (Corrections != 0)
{
Start += Corrections;
// GoertzelRealImag(intFilteredMixedSamples, Start + intCP[Carrier], intNforGoertzel[Carrier], dblFreqBin[Carrier], &dblReal, &dblImag);
GoertzelRealImagHanning(intFilteredMixedSamples, Start + intCP[Carrier], intNforGoertzel[Carrier], dblFreqBin[Carrier], &dblReal, &dblImag);
intPSKPhase_0[Carrier] = 1000 * atan2f(dblImag, dblReal);
}
}
*/
intPSKPhase_1[Carrier] = intPSKPhase_0[Carrier];
intPhasesLen++;
Start += intSampPerSym;
}
if (AccumulateStats)
intQAMSymbolCnt += intNumOfSymbols;
return (Start - origStart); // Symbols we've consumed
}
extern int bytQDataInProcessLen;
// function to decode one carrier from tones (used to decode from Averaged intToneMags)
BOOL Decode1Car4FSKFromTones(UCHAR * bytData, int intToneMags)
{
// Decodes intToneMags() to an array of bytes
// Updates bytData() with decoded
/*
UCHAR bytSym;
int intIndex;
ReDim bytData(intToneMags.Length \ 16 - 1)
For i As Integer = 0 To bytData.Length - 1 ' For each data byte
intIndex = 16 * i
For j As Integer = 0 To 3 ' for each 4FSK symbol (2 bits) in a byte
If intToneMags(intIndex) > intToneMags(intIndex + 1) And intToneMags(intIndex) > intToneMags(intIndex + 2) And intToneMags(intIndex) > intToneMags(intIndex + 3) Then
bytSym = 0
ElseIf intToneMags(intIndex + 1) > intToneMags(intIndex) And intToneMags(intIndex + 1) > intToneMags(intIndex + 2) And intToneMags(intIndex + 1) > intToneMags(intIndex + 3) Then
bytSym = 1
ElseIf intToneMags(intIndex + 2) > intToneMags(intIndex) And intToneMags(intIndex + 2) > intToneMags(intIndex + 1) And intToneMags(intIndex + 2) > intToneMags(intIndex + 3) Then
bytSym = 2
Else
bytSym = 3
End If
bytData(i) = (bytData(i) << 2) + bytSym
intIndex += 4
Next j
Next i
Return True
End Function ' Decode1Car4FSKFromTones
*/
return TRUE;
}
/* ' Function to decode one carrier from tones (used to decode from Averaged intToneMags)
Private Function Decode1Car8FSKFromTones(ByRef bytData() As Byte, ByRef intToneMags() As Int32) As Boolean
' Decodes intToneMags() to an array of bytes
' Updates bytData() with decoded
Dim bytSym As Byte
Dim intThreeBytes As Int32
ReDim bytData(3 * intToneMags.Length \ 64 - 1)
Dim intMaxMag As Int32
For i As Integer = 0 To (bytData.Length \ 3) - 1 ' For each group of 3 bytes data byte
intThreeBytes = 0
For j As Integer = 0 To 7 ' for each group of 8 symbols (24 bits)
intMaxMag = 0
For k As Integer = 0 To 7 ' for each of 8 possible tones per symbol
If intToneMags((i * 64) + 8 * j + k) > intMaxMag Then
intMaxMag = intToneMags((i * 64) + 8 * j + k)
bytSym = k
End If
Next k
intThreeBytes = (intThreeBytes << 3) + bytSym
Next j
bytData(3 * i) = (intThreeBytes And &HFF0000) >> 16
bytData(3 * i + 1) = (intThreeBytes And &HFF00) >> 8
bytData(3 * i + 2) = (intThreeBytes And &HFF)
Next i
Return True
End Function ' Decode1Car8FSKFromTones
' Function to decode one carrier from tones (used to decode from Averaged intToneMags)
Private Function Decode1Car16FSKFromTones(ByRef bytData() As Byte, ByRef intToneMags() As Int32) As Boolean
' Decodes intToneMags() to an array of bytes
' Updates bytData() with decoded tones
Dim bytSym As Byte
Dim intMaxMag As Int32
ReDim bytData(intToneMags.Length \ 32 - 1)
For i As Integer = 0 To bytData.Length - 1 ' For each data byte
For j As Integer = 0 To 1 ' for each 16FSK symbol (4 bits) in a byte
intMaxMag = 0
For k As Integer = 0 To 15
If intToneMags(i * 32 + 16 * j + k) > intMaxMag Then
intMaxMag = intToneMags(i * 32 + 16 * j + k)
bytSym = k
End If
Next k
bytData(i) = (bytData(i) << 4) + bytSym
Next j
Next i
Return True
End Function ' Decode1Car16FSKFromTones
*/
// Subroutine to update the Busy detector when not displaying Spectrum or Waterfall (graphics disabled)
extern int LastBusyCheck;
extern BOOL blnBusyStatus;
int intWaterfallRow = 0;
void UpdateBusyDetector(short * bytNewSamples)
{
float dblReF[1024];
float dblImF[1024];
float dblMag[206];
float dblMagAvg = 0;
int intTuneLineLow, intTuneLineHi, intDelta;
int i;
int BusyFlag;
if (ProtocolState > DISC) // ' Only process busy when in DISC state
return;
if (State != SearchingForLeader)
return; // only when looking for leader
if (Now - LastBusyCheck < 100)
return;
LastBusyCheck = Now;
FourierTransform(1024, bytNewSamples, &dblReF[0], &dblImF[0], FALSE);
for (i = 0; i < 206; i++)
{
// starting at ~300 Hz to ~2700 Hz Which puts the center of the signal in the center of the window (~1500Hz)
dblMag[i] = powf(dblReF[i + 25], 2) + powf(dblImF[i + 25], 2); // first pass
dblMagAvg += dblMag[i];
}
// Not sure about this as we use variable bandwidth frames. For now use 500
intDelta = (500 / 2 + TuningRange) / 11.719f;
intTuneLineLow = max((103 - intDelta), 3);
intTuneLineHi = min((103 + intDelta), 203);
// At the moment we only get here what seaching for leader,
// but if we want to plot spectrum we should call
// it always
BusyFlag = BusyDetect3(dblMag, intTuneLineLow, intTuneLineHi);
if (BusyFlag == 0)
{
if (BusyCount == 0)
blnBusyStatus = 0;
else
BusyCount--;
}
else
{
blnBusyStatus = 1;
BusyCount = 10; // Try delaying busy off a bit
}
if (blnBusyStatus && !blnLastBusyStatus)
{
Debugprintf("BUSY TRUE");
}
// stcStatus.Text = "True"
// queTNCStatus.Enqueue(stcStatus)
// 'Debug.WriteLine("BUSY TRUE @ " & Format(DateTime.UtcNow, "HH:mm:ss"))
else if (blnLastBusyStatus && !blnBusyStatus)
{
Debugprintf("BUSY FALSE");
}
blnLastBusyStatus = blnBusyStatus;
}