kopia lustrzana https://github.com/DL7AD/pecanpico10
997 wiersze
33 KiB
C
997 wiersze
33 KiB
C
/*
|
|
Aerospace Decoder - Copyright (C) 2018 Bob Anderson (VK2GJ)
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
*/
|
|
|
|
/**
|
|
* @file rxafsk.c
|
|
* @brief AFSK channel.
|
|
*
|
|
* @addtogroup channels
|
|
* @{
|
|
*/
|
|
|
|
#include "pktconf.h"
|
|
|
|
/*===========================================================================*/
|
|
/* Driver exported variables. */
|
|
/*===========================================================================*/
|
|
|
|
#if MAG_FILTER_GEN_COEFF == TRUE
|
|
|
|
float32_t mag_filter_coeff_f32[MAG_FILTER_NUM_TAPS];
|
|
|
|
#else
|
|
/*
|
|
* Magnitude (LPF) coefficients.
|
|
* Fs=28800, f1 = 1400, number of taps = 29
|
|
* Matlab/Octave parameters:
|
|
* hc = fir1(28, 1400/(Fs/2), 'low');
|
|
*/
|
|
|
|
float32_t mag_filter_coeff_f32[MAG_FILTER_NUM_TAPS] = {
|
|
-0.0016890122f, -0.0016647590f, -0.0016305187f, -0.0010016907f,
|
|
0.0009925971f, 0.0051420766f, 0.0120550411f, 0.0219694930f,
|
|
0.0346207799f, 0.0492010182f, 0.0644272330f, 0.0787120655f,
|
|
0.0904082200f, 0.0980807163f, 0.1007534795f, 0.0980807163f,
|
|
0.0904082200f, 0.0787120655f, 0.0644272330f, 0.0492010182f,
|
|
0.0346207799f, 0.0219694930f, 0.0120550411f, 0.0051420766f,
|
|
0.0009925971f, -0.0010016907f, -0.0016305187f, -0.0016647590f,
|
|
-0.0016890122f
|
|
};
|
|
#endif /* PREQ_FILTER_GEN_COEFF == TRUE */
|
|
|
|
|
|
#if PRE_FILTER_GEN_COEFF == TRUE
|
|
|
|
float32_t pre_filter_coeff_f32[PRE_FILTER_NUM_TAPS];
|
|
|
|
#else
|
|
/*
|
|
* Pre-filter (BPF) coefficients.
|
|
* Fs=28800, f1 = 925, f2 = 2475, number of taps = 311
|
|
* Matlab/Octave parameters:
|
|
* hc = fir1(310, [925, 2475]/(Fs/2), 'pass');
|
|
*/
|
|
|
|
float32_t pre_filter_coeff_f32[PRE_FILTER_NUM_TAPS] = {
|
|
0.0002630141f, 0.0002628323f, 0.0002174784f, 0.0001439800f,
|
|
0.0000662689f, 0.0000087403f, -0.0000104737f, 0.0000152369f,
|
|
0.0000786174f, 0.0001598901f, 0.0002316217f, 0.0002660169f,
|
|
0.0002428125f, 0.0001556541f, 0.0000150847f, -0.0001529389f,
|
|
-0.0003129410f, -0.0004289345f, -0.0004742560f, -0.0004395107f,
|
|
-0.0003362076f, -0.0001946865f, -0.0000564585f, 0.0000372882f,
|
|
0.0000579911f, -0.0000007155f, -0.0001192216f, -0.0002555907f,
|
|
-0.0003562318f, -0.0003710739f, -0.0002691589f, -0.0000500718f,
|
|
0.0002525572f, 0.0005772283f, 0.0008494657f, 0.0010017974f,
|
|
0.0009933667f, 0.0008233662f, 0.0005338281f, 0.0002000953f,
|
|
-0.0000891793f, -0.0002573906f, -0.0002635755f, -0.0001170745f,
|
|
0.0001218157f, 0.0003565040f, 0.0004809200f, 0.0004114493f,
|
|
0.0001148717f, -0.0003763382f, -0.0009669091f, -0.0015204528f,
|
|
-0.0018949887f, -0.0019829034f, -0.0017439270f, -0.0012209388f,
|
|
-0.0005327554f, 0.0001555675f, 0.0006777111f, 0.0009141328f,
|
|
0.0008292156f, 0.0004858414f, 0.0000315873f, -0.0003412629f,
|
|
-0.0004518110f, -0.0001872172f, 0.0004567840f, 0.0013646274f,
|
|
0.0023242755f, 0.0030807181f, 0.0034059854f, 0.0031663042f,
|
|
0.0023666159f, 0.0011580344f, -0.0001962396f, -0.0013891652f,
|
|
-0.0021544494f, -0.0023437972f, -0.0019738661f, -0.0012274564f,
|
|
-0.0004059321f, 0.0001560330f, 0.0001905957f, -0.0004167487f,
|
|
-0.0015805414f, -0.0030247966f, -0.0043492747f, -0.0051368262f,
|
|
-0.0050731091f, -0.0040446519f, -0.0021854927f, 0.0001442042f,
|
|
0.0024453937f, 0.0042118692f, 0.0050709497f, 0.0048929927f,
|
|
0.0038363447f, 0.0023111613f, 0.0008681171f, 0.0000401684f,
|
|
0.0001807741f, 0.0013455353f, 0.0032543614f, 0.0053501112f,
|
|
0.0069426437f, 0.0074018164f, 0.0063466090f, 0.0037754712f,
|
|
0.0000966124f, -0.0039567264f, -0.0075087956f, -0.0097752826f,
|
|
-0.0102847504f, -0.0090233718f, -0.0064569060f, -0.0034162907f,
|
|
-0.0008715304f, 0.0003478556f, -0.0001914293f, -0.0023698932f,
|
|
-0.0055096823f, -0.0085320077f, -0.0102437760f, -0.0096804313f,
|
|
-0.0064109478f, -0.0007150685f, 0.0064272605f, 0.0135454918f,
|
|
0.0190429897f, 0.0216254883f, 0.0206778373f, 0.0164791731f,
|
|
0.0101834165f, 0.0035516810f, -0.0015090359f, -0.0034886155f,
|
|
-0.0017541991f, 0.0031705084f, 0.0096227405f, 0.0151387937f,
|
|
0.0170529057f, 0.0132228528f, 0.0027036543f, -0.0138107229f,
|
|
-0.0339131039f, -0.0538295639f, -0.0691388898f, -0.0757219326f,
|
|
-0.0707320414f, -0.0533595170f, -0.0251988255f, 0.0098893389f,
|
|
0.0464165977f, 0.0782860621f, 0.0999812724f, 0.1076699117f,
|
|
0.0999812724f, 0.0782860621f, 0.0464165977f, 0.0098893389f,
|
|
-0.0251988255f, -0.0533595170f, -0.0707320414f, -0.0757219326f,
|
|
-0.0691388898f, -0.0538295639f, -0.0339131039f, -0.0138107229f,
|
|
0.0027036543f, 0.0132228528f, 0.0170529057f, 0.0151387937f,
|
|
0.0096227405f, 0.0031705084f, -0.0017541991f, -0.0034886155f,
|
|
-0.0015090359f, 0.0035516810f, 0.0101834165f, 0.0164791731f,
|
|
0.0206778373f, 0.0216254883f, 0.0190429897f, 0.0135454918f,
|
|
0.0064272605f, -0.0007150685f, -0.0064109478f, -0.0096804313f,
|
|
-0.0102437760f, -0.0085320077f, -0.0055096823f, -0.0023698932f,
|
|
-0.0001914293f, 0.0003478556f, -0.0008715304f, -0.0034162907f,
|
|
-0.0064569060f, -0.0090233718f, -0.0102847504f, -0.0097752826f,
|
|
-0.0075087956f, -0.0039567264f, 0.0000966124f, 0.0037754712f,
|
|
0.0063466090f, 0.0074018164f, 0.0069426437f, 0.0053501112f,
|
|
0.0032543614f, 0.0013455353f, 0.0001807741f, 0.0000401684f,
|
|
0.0008681171f, 0.0023111613f, 0.0038363447f, 0.0048929927f,
|
|
0.0050709497f, 0.0042118692f, 0.0024453937f, 0.0001442042f,
|
|
-0.0021854927f, -0.0040446519f, -0.0050731091f, -0.0051368262f,
|
|
-0.0043492747f, -0.0030247966f, -0.0015805414f, -0.0004167487f,
|
|
0.0001905957f, 0.0001560330f, -0.0004059321f, -0.0012274564f,
|
|
-0.0019738661f, -0.0023437972f, -0.0021544494f, -0.0013891652f,
|
|
-0.0001962396f, 0.0011580344f, 0.0023666159f, 0.0031663042f,
|
|
0.0034059854f, 0.0030807181f, 0.0023242755f, 0.0013646274f,
|
|
0.0004567840f, -0.0001872172f, -0.0004518110f, -0.0003412629f,
|
|
0.0000315873f, 0.0004858414f, 0.0008292156f, 0.0009141328f,
|
|
0.0006777111f, 0.0001555675f, -0.0005327554f, -0.0012209388f,
|
|
-0.0017439270f, -0.0019829034f, -0.0018949887f, -0.0015204528f,
|
|
-0.0009669091f, -0.0003763382f, 0.0001148717f, 0.0004114493f,
|
|
0.0004809200f, 0.0003565040f, 0.0001218157f, -0.0001170745f,
|
|
-0.0002635755f, -0.0002573906f, -0.0000891793f, 0.0002000953f,
|
|
0.0005338281f, 0.0008233662f, 0.0009933667f, 0.0010017974f,
|
|
0.0008494657f, 0.0005772283f, 0.0002525572f, -0.0000500718f,
|
|
-0.0002691589f, -0.0003710739f, -0.0003562318f, -0.0002555907f,
|
|
-0.0001192216f, -0.0000007155f, 0.0000579911f, 0.0000372882f,
|
|
-0.0000564585f, -0.0001946865f, -0.0003362076f, -0.0004395107f,
|
|
-0.0004742560f, -0.0004289345f, -0.0003129410f, -0.0001529389f,
|
|
0.0000150847f, 0.0001556541f, 0.0002428125f, 0.0002660169f,
|
|
0.0002316217f, 0.0001598901f, 0.0000786174f, 0.0000152369f,
|
|
-0.0000104737f, 0.0000087403f, 0.0000662689f, 0.0001439800f,
|
|
0.0002174784f, 0.0002628323f, 0.0002630141f
|
|
};
|
|
#endif
|
|
|
|
/*
|
|
* Data structure for AFSK decoding.
|
|
*/
|
|
AFSKDemodDriver AFSKD1;
|
|
|
|
/*===========================================================================*/
|
|
/* Decoder local variables and types. */
|
|
/*===========================================================================*/
|
|
|
|
/*===========================================================================*/
|
|
/* Decoder local functions. */
|
|
/*===========================================================================*/
|
|
|
|
/**
|
|
* @brief Enables PWM stream from radio.
|
|
* @post The ICU is configured and started.
|
|
* @post The ports and timers for CCA input are configured.
|
|
*
|
|
* @param[in] myDriver pointer to a @p AFSKDemodDriver structure
|
|
*
|
|
* @api
|
|
*/
|
|
void pktEnablePWM(AFSKDemodDriver *myDriver) {
|
|
|
|
chDbgAssert(myDriver->icudriver != NULL, "no ICU driver");
|
|
|
|
/* Set callback for squelch events. */
|
|
palSetLineCallback(LINE_CCA, (palcallback_t)pktRadioCCAInput,
|
|
myDriver->icudriver);
|
|
|
|
pktICUStart(myDriver->icudriver);
|
|
|
|
/* Enabling events on both edges of CCA.*/
|
|
palEnableLineEvent(LINE_CCA, PAL_EVENT_MODE_BOTH_EDGES);
|
|
|
|
myDriver->icustate = PKT_PWM_READY;
|
|
}
|
|
|
|
/**
|
|
* @brief Disables PWM stream from radio.
|
|
* @post The ICU capture is stopped.
|
|
* @post The port for CCA input is disabled.
|
|
* @post The PWM buffer reference is removed.
|
|
* @post The ICU remains ready to be restarted.
|
|
*
|
|
* @param[in] myDriver pointer to a @p AFSKDemodDriver structure
|
|
* @api
|
|
*/
|
|
void pktDisablePWM(AFSKDemodDriver *myDriver) {
|
|
|
|
chDbgAssert(myDriver->icudriver != NULL, "no ICU driver");
|
|
|
|
chSysLock();
|
|
|
|
/* Close the PWM stream. */
|
|
pktClosePWMChannelI(myDriver->icudriver, 0, PWM_TERM_DECODE_STOP);
|
|
|
|
/* Stop ICU capture. */
|
|
icuStopCaptureI(myDriver->icudriver);
|
|
|
|
/* Stop any timeouts in ICU handling. */
|
|
pktStopAllICUTimersS(myDriver->icudriver);
|
|
|
|
/* Disable CCA port event. */
|
|
palDisableLineEventI(LINE_CCA);
|
|
|
|
myDriver->icustate = PKT_PWM_STOP;
|
|
|
|
chSysUnlock();
|
|
}
|
|
|
|
/**
|
|
* @brief Check the symbol timing.
|
|
*
|
|
* @param[in] myDriver pointer to a @p AFSKDemodDriver structure
|
|
*
|
|
* @return status indicating if symbol decoding should take place.
|
|
* @retval true decoding should run before getting next PWM entry.
|
|
* @retval false continue immediately with next PWM data entry.
|
|
*
|
|
* @api
|
|
*/
|
|
bool pktCheckAFSKSymbolTime(AFSKDemodDriver *myDriver) {
|
|
/*
|
|
* Each decoder filter is setup at init with a sample source.
|
|
* This is set in the filter control structure.
|
|
*/
|
|
|
|
switch(AFSK_DECODE_TYPE) {
|
|
case AFSK_DSP_QCORR_DECODE: {
|
|
|
|
/*
|
|
* Check if symbol decode should be run now.
|
|
*/
|
|
return (get_qcorr_symbol_timing(myDriver));
|
|
}
|
|
|
|
case AFSK_DSP_FCORR_DECODE: {
|
|
|
|
}
|
|
|
|
default: {
|
|
break;
|
|
}
|
|
} /* end switch. */
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* @brief Update the symbol timing PLL.
|
|
*
|
|
* @param[in] myDriver pointer to a @p AFSKDemodDriver structure
|
|
*
|
|
* @api
|
|
*/
|
|
void pktUpdateAFSKSymbolPLL(AFSKDemodDriver *myDriver) {
|
|
/*
|
|
* Increment PLL timing.
|
|
*/
|
|
|
|
switch(AFSK_DECODE_TYPE) {
|
|
case AFSK_DSP_QCORR_DECODE: {
|
|
|
|
/*
|
|
*
|
|
*/
|
|
|
|
update_qcorr_pll(myDriver);
|
|
break;
|
|
}
|
|
|
|
case AFSK_DSP_FCORR_DECODE: {
|
|
|
|
}
|
|
|
|
default: {
|
|
break;
|
|
}
|
|
} /* end switch. */
|
|
return;
|
|
}
|
|
|
|
/**
|
|
* @brief Processes PWM into a decimated time line for AFSK decoding.
|
|
* @notes The decimated entries are filtered through a BPF.
|
|
*
|
|
* @param[in] myDriver pointer to a @p AFSKDemodDriver structure
|
|
*
|
|
* @return status of operations.
|
|
* @retval true no error occurred so decimation can continue at next data.
|
|
* @retval false an error occurred and decimation should be aborted.
|
|
*
|
|
* @api
|
|
*/
|
|
bool pktProcessAFSK(AFSKDemodDriver *myDriver, min_pwmcnt_t current_tone[]) {
|
|
/* Start working on new input data now. */
|
|
uint8_t i = 0;
|
|
for(i = 0; i < (sizeof(min_pwm_counts_t) / sizeof(min_pwmcnt_t)); i++) {
|
|
myDriver->decimation_accumulator += current_tone[i];
|
|
while(myDriver->decimation_accumulator >= 0) {
|
|
|
|
/*
|
|
* The decoder will process a converted binary sample.
|
|
* The PWM binary is converted to a q31 +/- sample value.
|
|
* The sample is passed to pre-filtering (i.e. BPF) as its next input.
|
|
*/
|
|
(void)pktAddAFSKFilterSample(myDriver, !(i & 1));
|
|
|
|
/*
|
|
* Process the sample at the output side of the pre-filter.
|
|
* The filter returns true if its output is now valid.
|
|
*/
|
|
if(pktProcessAFSKFilteredSample(myDriver)) {
|
|
/* Filter is ready so decoding can commence. */
|
|
if(pktCheckAFSKSymbolTime(myDriver)) {
|
|
/* A symbol is ready to decode. */
|
|
if(!pktDecodeAFSKSymbol(myDriver))
|
|
/* Unable to store character - buffer full. */
|
|
return false;
|
|
}
|
|
pktUpdateAFSKSymbolPLL(myDriver);
|
|
}
|
|
myDriver->decimation_accumulator -= myDriver->decimation_size;
|
|
} /* End while. Accumulator has underflowed. */
|
|
} /* End for. */
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* @brief Add a sample to the decoder filter input.
|
|
* @notes The decimated entries are filtered through a BPF.
|
|
*
|
|
* @param[in] myDriver pointer to an @p AFSKDemodDriver structure.
|
|
* @param[in] binary binary data from the PWM.
|
|
*
|
|
* @api
|
|
*/
|
|
void pktAddAFSKFilterSample(AFSKDemodDriver *myDriver, bit_t binary) {
|
|
switch(AFSK_DECODE_TYPE) {
|
|
case AFSK_DSP_QCORR_DECODE: {
|
|
(void)push_qcorr_sample(myDriver, binary);
|
|
break;
|
|
}
|
|
|
|
case AFSK_DSP_FCORR_DECODE: {
|
|
//(void)push_fcorr_sample(myDriver, binary);
|
|
break;
|
|
}
|
|
|
|
default: {
|
|
break;
|
|
}
|
|
} /* End switch. */
|
|
}
|
|
|
|
/**
|
|
* @brief Process the input sample through the IQ correlation.
|
|
* @notes There are 4 filters that are run (I & Q for Mark and Space)
|
|
*
|
|
* @param[in] myDriver pointer to an @p AFSKDemodDriver structure.
|
|
*
|
|
* @return filter status
|
|
* @retval true the filter output is valid.
|
|
* @retval false the filter output in not yet valid.
|
|
*
|
|
* @api
|
|
*/
|
|
bool pktProcessAFSKFilteredSample(AFSKDemodDriver *myDriver) {
|
|
/*
|
|
* Each decoder filter is setup at init with a sample source.
|
|
* This is set in the filter control structure.
|
|
*/
|
|
|
|
switch(AFSK_DECODE_TYPE) {
|
|
case AFSK_DSP_QCORR_DECODE: {
|
|
|
|
/*
|
|
* Next perform the fixed point correlator update.
|
|
* Result is updated MARK and SPACE bins.
|
|
*
|
|
*/
|
|
return process_qcorr_output(myDriver);
|
|
}
|
|
|
|
case AFSK_DSP_FCORR_DECODE: {
|
|
|
|
}
|
|
|
|
default: {
|
|
break;
|
|
}
|
|
} /* end switch. */
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* @brief Decode AFSK symbol into an HDLC bit.
|
|
* @notes Called at symbol ready time as determined by decoders.
|
|
*
|
|
* @param[in] myDriver pointer to an @p AFSKDemodDriver structure.
|
|
*
|
|
* @return status of operation
|
|
* @retval true - success
|
|
* @retval false - an error occurred in processing (buffer full)
|
|
*
|
|
* @api
|
|
*/
|
|
bool pktDecodeAFSKSymbol(AFSKDemodDriver *myDriver) {
|
|
/*
|
|
* Called when a symbol timeline is ready.
|
|
* Called from normal thread level.
|
|
*/
|
|
|
|
switch(AFSK_DECODE_TYPE) {
|
|
|
|
case AFSK_DSP_QCORR_DECODE: {
|
|
/* Tone analysis is done per sample in QCORR. */
|
|
qcorr_decoder_t *decoder = myDriver->tone_decoder;
|
|
myDriver->tone_freq = decoder->current_demod;
|
|
break;
|
|
} /* End case AFSK_DSP_QCORR_DECODE. */
|
|
|
|
case AFSK_DSP_FCORR_DECODE: {
|
|
/* TODO: Tone analysis is done per sample in FCORR. */
|
|
break;
|
|
} /* End case AFSK_DSP_FCORR_DECODE. */
|
|
|
|
case AFSK_NULL_DECODE: {
|
|
/*
|
|
* Do nothing (used when in debug capture mode).
|
|
*/
|
|
return true;
|
|
} /* End case AFSK_NULL_DECODE. */
|
|
} /* End switch. */
|
|
|
|
/* After tone detection generate an HDLC bit. */
|
|
return pktExtractHDLCfromAFSK(myDriver);
|
|
} /* End function. */
|
|
|
|
/**
|
|
* @brief Reset the AFSK decoder and filter.
|
|
* @notes Called at completion of packet reception.
|
|
* @post Selected tone decoder and common AFSK data is initialized.
|
|
*
|
|
* @param[in] myDriver pointer to an @p AFSKDemodDriver structure.
|
|
*
|
|
* @api
|
|
*/
|
|
void pktResetAFSKDecoder(AFSKDemodDriver *myDriver) {
|
|
/*
|
|
* Called when a decode stream has completed.
|
|
* Called from normal thread level.
|
|
*/
|
|
|
|
/* Reset the decoder data.*/
|
|
myDriver->frame_state = FRAME_SEARCH;
|
|
myDriver->prior_freq = TONE_NONE;
|
|
myDriver->bit_index = 0;
|
|
myDriver->decimation_accumulator = 0;
|
|
|
|
/* Set the hdlc bits to all ones. */
|
|
myDriver->hdlc_bits = (int32_t)-1;
|
|
|
|
switch(AFSK_DECODE_TYPE) {
|
|
|
|
case AFSK_DSP_QCORR_DECODE: {
|
|
/* Reset QCORR. */
|
|
(void)reset_qcorr_all(myDriver);
|
|
break;
|
|
}
|
|
|
|
case AFSK_DSP_FCORR_DECODE: {
|
|
/* TODO: Reset FCORR. */
|
|
break;
|
|
}
|
|
|
|
case AFSK_NULL_DECODE: {
|
|
/*
|
|
* Do nothing (used when in debug capture mode).
|
|
*/
|
|
break;
|
|
} /* End case AFSK_NULL_DECODE. */
|
|
} /* End switch. */
|
|
}
|
|
|
|
/**
|
|
* @brief Creates an AFSK channel which decodes PWM data from the radio.
|
|
* @note The si radio has no AFSK decoding capability.
|
|
* @note The PWM RX_DATA from the radio is decoded as AFSK by the uC.
|
|
*
|
|
* @post An ICU and GPIO ports for the Radio are attached and initialized.
|
|
* @post A dynamic object FIFO is created for buffering radio PWM data.
|
|
* @post Buffers are posted to demodulator where decoding takes place.
|
|
* @post Multiple PWM sessions may be queued by the Radio for demodulation.
|
|
*
|
|
* @param[in] pktHandler pointer to a @p PKTDriver structure
|
|
* @param[in] radio radio ID.
|
|
*
|
|
* @return pointer to AFSK driver object.
|
|
* @retval NULL if initialization failed.
|
|
*
|
|
* @api
|
|
*/
|
|
AFSKDemodDriver *pktCreateAFSKDecoder(packet_svc_t *pktHandler) {
|
|
|
|
chDbgAssert(pktHandler != NULL, "no packet handler");
|
|
|
|
AFSKDemodDriver *myDriver = &AFSKD1;
|
|
|
|
/*
|
|
* Initialize the decoder event object.
|
|
*/
|
|
chEvtObjectInit(pktGetEventSource(myDriver));
|
|
|
|
/* Set the link from demod driver to the packet driver. */
|
|
myDriver->packet_handler = pktHandler;
|
|
|
|
/* The radio associated with this AFSK driver. */
|
|
radio_unit_t rid = myDriver->packet_handler->radio_config.radio_id;
|
|
|
|
/* Create a PWM FIFO name for this radio. */
|
|
chsnprintf(myDriver->pwm_fifo_name, sizeof(myDriver->pwm_fifo_name),
|
|
"%s%02i", PKT_PWM_QUEUE_PREFIX, rid);
|
|
|
|
/* Create the dynamic objects FIFO for the PWM data queue. */
|
|
myDriver->the_pwm_fifo = chFactoryCreateObjectsFIFO(myDriver->pwm_fifo_name,
|
|
sizeof(radio_cca_fifo_t),
|
|
NUMBER_PWM_FIFOS, sizeof(msg_t));
|
|
|
|
chDbgAssert(myDriver->the_pwm_fifo != NULL, "failed to create PWM FIFO");
|
|
|
|
if(myDriver->the_pwm_fifo == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
/* Get the objects FIFO . */
|
|
myDriver->pwm_fifo_pool = chFactoryGetObjectsFIFO(myDriver->the_pwm_fifo);
|
|
|
|
/* Indicate no buffer allocated. */
|
|
myDriver->active_radio_object = NULL;
|
|
myDriver->active_demod_object = NULL;
|
|
|
|
/* Attach and initialize the ICU PWM system. */
|
|
myDriver->icudriver = pktAttachICU(pktHandler->radio_config.radio_id);
|
|
|
|
/* Set the link from ICU driver to AFSK demod driver. */
|
|
myDriver->icudriver->link = myDriver;
|
|
myDriver->icustate = PKT_PWM_READY;
|
|
|
|
/* Create the packet buffer name. */
|
|
chsnprintf(myDriver->decoder_name, sizeof(myDriver->decoder_name),
|
|
"%s%02i", PKT_AFSK_THREAD_NAME_PREFIX, rid);
|
|
|
|
myDriver->decoder_thd = chThdCreateFromHeap(NULL,
|
|
THD_WORKING_AREA_SIZE(PKT_AFSK_DECODER_WA_SIZE),
|
|
myDriver->decoder_name,
|
|
NORMALPRIO - 10,
|
|
pktAFSKDecoder,
|
|
myDriver);
|
|
|
|
chDbgAssert(myDriver->decoder_thd != NULL,
|
|
"error in decoder thread creation");
|
|
|
|
if(myDriver->decoder_thd == NULL) {
|
|
chFactoryReleaseObjectsFIFO(myDriver->the_pwm_fifo);
|
|
return NULL;
|
|
}
|
|
|
|
myDriver->decimation_size = ((pwm_accum_t)ICU_COUNT_FREQUENCY
|
|
/ (pwm_accum_t)AFSK_BAUD_RATE)
|
|
/ (pwm_accum_t)SYMBOL_DECIMATION;
|
|
|
|
#if PRE_FILTER_GEN_COEFF == TRUE
|
|
|
|
gen_fir_bpf((float32_t)PRE_FILTER_LOW / (float32_t)FILTER_SAMPLE_RATE,
|
|
(float32_t)PRE_FILTER_HIGH / (float32_t)FILTER_SAMPLE_RATE,
|
|
pre_filter_coeff_f32,
|
|
PRE_FILTER_NUM_TAPS,
|
|
TD_WINDOW_NONE);
|
|
#endif
|
|
|
|
#if MAG_FILTER_GEN_COEFF == TRUE
|
|
|
|
gen_fir_lpf((float32_t)MAG_FILTER_HIGH / (float32_t)FILTER_SAMPLE_RATE,
|
|
mag_filter_coeff_f32,
|
|
MAG_FILTER_NUM_TAPS,
|
|
TD_WINDOW_NONE);
|
|
#endif
|
|
|
|
return myDriver;
|
|
}
|
|
|
|
/**
|
|
* @brief Release AFSK resources.
|
|
*
|
|
* @post The ICU is stopped.
|
|
* @post The dynamic object FIFO for PWM is released.
|
|
*
|
|
* @param[in] myDriver pointer to a @p AFSKDemodDriver structure
|
|
*
|
|
* @api
|
|
*/
|
|
void pktReleaseAFSKDecoder(AFSKDemodDriver *myDriver) {
|
|
chDbgAssert(myDriver != NULL, "no AFSK driver");
|
|
chDbgAssert(myDriver->the_pwm_fifo != NULL, "no CCA FIFO");
|
|
chDbgAssert(myDriver->icudriver != NULL, "no ICU driver");
|
|
|
|
/* Stop PWM queue. */
|
|
pktDisablePWM(myDriver);
|
|
|
|
/* Detach ICU from this AFSK driver. */
|
|
pktDetachICU(myDriver->icudriver);
|
|
|
|
chFactoryReleaseObjectsFIFO(myDriver->the_pwm_fifo);
|
|
myDriver->the_pwm_fifo = NULL;
|
|
myDriver->pwm_fifo_pool = NULL;
|
|
}
|
|
|
|
|
|
/*===========================================================================*/
|
|
/* AFSK Decoder thread. */
|
|
/*===========================================================================*/
|
|
|
|
THD_FUNCTION(pktAFSKDecoder, arg) {
|
|
|
|
/*
|
|
* Setup pointers to control structure and resources.
|
|
*/
|
|
AFSKDemodDriver *myDriver = arg;
|
|
packet_svc_t *myHandler = myDriver->packet_handler;
|
|
|
|
/* No active packet object. */
|
|
myHandler->active_packet_object = NULL;
|
|
|
|
/* Activity LED blink rate scaling variable. */
|
|
uint16_t led_count = 0;
|
|
|
|
#define DECODER_WAIT_TIME 200U /* 200mS. */
|
|
#define DECODER_IDLE_TIME 2000U /* 2000uS. */
|
|
#define DECODER_POLL_TIME 10U /* 10mS. */
|
|
#define DECODER_LED_RATE_POLL 100U /* 1000uS. */
|
|
#define DECODER_ACTIVE_TIMEOUT 5U /* 5mS. */
|
|
#define DECODER_SUSPEND_TIME 2000U /* 2000uS. */
|
|
#define DECODER_LED_RATE_SUSPEND 250U /* Blink at 250mS during suspend. */
|
|
|
|
/* Set thread priority to different level when decoding./ */
|
|
#define DECODER_RUN_PRIORITY NORMALPRIO+10
|
|
|
|
#if AFSK_DECODE_TYPE == AFSK_DSP_QCORR_DECODE
|
|
init_qcorr_decoder(myDriver);
|
|
#endif
|
|
|
|
/* Save the priority that calling thread gave us. */
|
|
tprio_t decoder_idle_priority = chThdGetPriorityX();
|
|
|
|
/* Setup LED for decoder blinker. */
|
|
pktSetLineModeDecoderLED();
|
|
|
|
pktWriteDecoderLED(PAL_HIGH);
|
|
|
|
/* Acknowledge open then wait for start or close of decoder. */
|
|
pktAddEventFlags(myDriver, DEC_OPEN_EXEC);
|
|
myDriver->decoder_state = DECODER_WAIT;
|
|
while(true) {
|
|
switch(myDriver->decoder_state) {
|
|
|
|
case DECODER_WAIT: {
|
|
/*
|
|
* Wait for start or close event.
|
|
*/
|
|
eventmask_t evt = chEvtWaitAnyTimeout(DEC_COMMAND_START,
|
|
TIME_MS2I(DECODER_WAIT_TIME));
|
|
if(evt) {
|
|
pktEnablePWM(myDriver);
|
|
myDriver->decoder_state = DECODER_RESET;
|
|
pktAddEventFlags(myDriver, DEC_START_EXEC);
|
|
break;
|
|
}
|
|
evt = chEvtGetAndClearEvents(DEC_COMMAND_CLOSE);
|
|
if(evt) {
|
|
pktAddEventFlags(myDriver, DEC_CLOSE_EXEC);
|
|
pktReleaseAFSKDecoder(myDriver);
|
|
myDriver->decoder_state = DECODER_TERMINATED;
|
|
pktWriteDecoderLED(PAL_LOW);
|
|
chThdExit(MSG_OK);
|
|
/* Something went wrong if we arrive here. */
|
|
chSysHalt("ThdExit");
|
|
}
|
|
pktWriteDecoderLED(PAL_TOGGLE);
|
|
continue;
|
|
}
|
|
|
|
case DECODER_TERMINATED:
|
|
/* Something went wrong if we arrive here. */
|
|
chSysHalt("ThdExit");
|
|
|
|
case DECODER_IDLE: {
|
|
/*
|
|
* Check for stop event.
|
|
*/
|
|
eventmask_t evt = chEvtGetAndClearEvents(DEC_COMMAND_STOP);
|
|
if(evt) {
|
|
pktDisablePWM(myDriver);
|
|
myDriver->decoder_state = DECODER_WAIT;
|
|
pktAddEventFlags(myDriver, DEC_STOP_EXEC);
|
|
break;
|
|
}
|
|
myDriver->decoder_state = DECODER_POLL;
|
|
break;
|
|
} /* End case DECODER_IDLE. */
|
|
|
|
case DECODER_POLL: {
|
|
radio_cca_fifo_t *myRadioFIFO;
|
|
msg_t fifo_msg = chFifoReceiveObjectTimeout(myDriver->pwm_fifo_pool,
|
|
(void *)&myRadioFIFO,
|
|
TIME_MS2I(DECODER_POLL_TIME));
|
|
if(fifo_msg != MSG_OK) {
|
|
|
|
if(++led_count >= DECODER_LED_RATE_POLL) {
|
|
/* Toggle decoder LED. */
|
|
pktWriteDecoderLED(PAL_TOGGLE);
|
|
led_count = 0;
|
|
}
|
|
/* No FIFO object posted so loop again. */
|
|
myDriver->decoder_state = DECODER_IDLE;
|
|
break;
|
|
}
|
|
/* Check if PWM queue object released in RESET state. */
|
|
chDbgCheck(myDriver->active_demod_object == NULL);
|
|
|
|
/* Set current PWM queue object. */
|
|
myDriver->active_demod_object = myRadioFIFO;
|
|
|
|
/* Check if prior packet buffer released. */
|
|
chDbgCheck(myHandler->active_packet_object == NULL);
|
|
|
|
/* Get a packet buffer. */
|
|
dyn_objects_fifo_t *pkt_fifo =
|
|
chFactoryFindObjectsFIFO(myHandler->pbuff_name);
|
|
chDbgAssert(pkt_fifo != NULL, "unable to find packet fifo");
|
|
|
|
/* The factory reference count is increased. */
|
|
objects_fifo_t *pkt_buffer_pool = chFactoryGetObjectsFIFO(pkt_fifo);
|
|
chDbgAssert(pkt_buffer_pool != NULL, "no packet fifo list");
|
|
|
|
chSysLock();
|
|
/* TODO: Does this really need to be locked? */
|
|
/* Get a buffer and have it initialized ready for use. */
|
|
pkt_data_object_t *myPktBuffer = pktTakeDataBufferS(myHandler,
|
|
pkt_buffer_pool);
|
|
|
|
if(myPktBuffer == NULL) {
|
|
chSysUnlock();
|
|
pktAddEventFlags(myHandler, EVT_AX25_NO_BUFFER);
|
|
myDriver->active_demod_object->status |= EVT_AX25_NO_BUFFER;
|
|
myDriver->decoder_state = DECODER_ERROR;
|
|
break;
|
|
}
|
|
chSysUnlock();
|
|
/*
|
|
* Fill it with a pattern for debug.
|
|
* TODO: Make this a diagnostic conditional.
|
|
*/
|
|
//memset(myPktBuffer->buffer, 0x55, myPktBuffer->buffer_size);
|
|
|
|
myDriver->decoder_state = DECODER_ACTIVE;
|
|
|
|
#if AFSK_DEBUG_TYPE == AFSK_PWM_DATA_CAPTURE_DEBUG
|
|
char buf[80];
|
|
int out = chsnprintf(buf, sizeof(buf),"\r\n======= START ===========\r\n");
|
|
chnWrite(pkt_out, (uint8_t *)buf, out);
|
|
#endif
|
|
/* Increase thread priority. */
|
|
(void)chThdSetPriority(DECODER_RUN_PRIORITY);
|
|
/* Turn on the decoder LED. */
|
|
pktWriteDecoderLED(PAL_HIGH);
|
|
break;
|
|
} /* End case DECODER_SESSION_POLL. */
|
|
|
|
case DECODER_ACTIVE: {
|
|
/*
|
|
* We have a packet being processed.
|
|
* Wait for PWM data from shared queue.
|
|
*/
|
|
input_queue_t *myQueue =
|
|
&myDriver->active_demod_object->radio_pwm_queue;
|
|
|
|
chDbgAssert(myQueue != NULL, "no queue assigned");
|
|
|
|
byte_packed_pwm_t data;
|
|
size_t n = iqReadTimeout(myQueue, data.bytes,
|
|
sizeof(packed_pwm_counts_t),
|
|
TIME_MS2I(DECODER_ACTIVE_TIMEOUT));
|
|
/* TODO: Timeout to be calculated from SYMBOL time x (8?). */
|
|
|
|
if(n == sizeof(packed_pwm_counts_t)) {
|
|
array_min_pwm_counts_t radio;
|
|
pktUnpackPWMData(data, &radio);
|
|
|
|
#if AFSK_DEBUG_TYPE == AFSK_PWM_DATA_CAPTURE_DEBUG
|
|
char buf[80];
|
|
int out = chsnprintf(buf, sizeof(buf), "%i, %i\r\n",
|
|
radio.pwm.impulse, radio.pwm.valley);
|
|
chnWrite(pkt_out, (uint8_t *)buf, out);
|
|
#endif
|
|
|
|
/* look for "in band" signal in radio data. */
|
|
if(radio.pwm.impulse == 0) {
|
|
switch(radio.pwm.valley) {
|
|
case PWM_TERM_DECODE_ENDED:
|
|
case PWM_TERM_DECODE_STOP:
|
|
case PWM_TERM_CCA_CLOSE: {
|
|
/* End of data flag from PWM. */
|
|
myDriver->decoder_state = DECODER_CLOSE;
|
|
continue; /* From this case. */
|
|
} /* End case 0. */
|
|
|
|
case PWM_TERM_ICU_OVERFLOW:
|
|
case PWM_TERM_NO_RESOURCE:
|
|
case PWM_TERM_QUEUE_FULL: {
|
|
/* Buffer overrun flag from PWM.
|
|
* PWM side has set the global event for this.
|
|
*/
|
|
myDriver->decoder_state = DECODER_ERROR;
|
|
continue; /* From this case. */
|
|
} /* End case 1. */
|
|
|
|
default: {
|
|
/* Unknown flag from PWM. */
|
|
pktAddEventFlags(myHandler, EVT_PWM_UNKNOWN_INBAND);
|
|
myDriver->active_demod_object->status |= EVT_PWM_UNKNOWN_INBAND;
|
|
myDriver->decoder_state = DECODER_ERROR;
|
|
continue; /* From this case. */
|
|
} /* End case default. */
|
|
} /* End switch. */
|
|
} /* End if in-band. */
|
|
|
|
/*
|
|
* Process the AFSK into HDLC bit and AX25 data.
|
|
*/
|
|
if(!pktProcessAFSK(myDriver, radio.array)) {
|
|
/* AX25 character decoded but buffer is full.
|
|
* Status set and event sent by HDLC processor.
|
|
*/
|
|
myDriver->decoder_state = DECODER_ERROR;
|
|
break; /* From this case. */
|
|
}
|
|
|
|
/* Check for change of frame state. */
|
|
switch(myDriver->frame_state) {
|
|
case FRAME_SEARCH:
|
|
pktWriteDecoderLED(PAL_TOGGLE);
|
|
continue;
|
|
case FRAME_OPEN:
|
|
case FRAME_DATA:
|
|
pktWriteDecoderLED(PAL_HIGH);
|
|
continue;
|
|
case FRAME_RESET:
|
|
case FRAME_CLOSE: {
|
|
myDriver->decoder_state = DECODER_CLOSE;
|
|
continue; /* From this case. */
|
|
}
|
|
} /* End switch. */
|
|
} /* End data == sizeof PWM. */
|
|
/* PWM data timeout. */
|
|
pktAddEventFlags(myHandler, EVT_PWM_STREAM_TIMEOUT);
|
|
myDriver->active_demod_object->status |= EVT_PWM_STREAM_TIMEOUT;
|
|
myDriver->decoder_state = DECODER_TIMEOUT;
|
|
break;
|
|
} /* End case DECODER_ACTIVE. */
|
|
|
|
case DECODER_CLOSE: {
|
|
myDriver->decoder_state = DECODER_SUSPEND;
|
|
break;
|
|
} /* End case DECODER_CLOSE. */
|
|
|
|
case DECODER_TIMEOUT: {
|
|
myDriver->decoder_state = DECODER_SUSPEND;
|
|
break;
|
|
} /* End case DECODER_TIMEOUT. */
|
|
|
|
/* This case is set when an error status. */
|
|
case DECODER_ERROR: {
|
|
pktAddEventFlags(myHandler, EVT_DECODER_ERROR);
|
|
myDriver->active_demod_object->status |= EVT_DECODER_ERROR;
|
|
myDriver->decoder_state = DECODER_SUSPEND;
|
|
break;
|
|
} /* End case DECODER_ERROR. */
|
|
|
|
case DECODER_RESET: {
|
|
/*
|
|
* Return PWM FIFO to pool if there is one active.
|
|
*/
|
|
chSysLock();
|
|
radio_cca_fifo_t *myFIFO = myDriver->active_demod_object;
|
|
if(myFIFO != NULL) {
|
|
|
|
/* Wait for queue object to be released by PWM. */
|
|
(void)chBSemWaitS(&myFIFO->sem);
|
|
|
|
myDriver->active_demod_object = NULL;
|
|
chFifoReturnObjectI(myDriver->pwm_fifo_pool, myFIFO);
|
|
|
|
}
|
|
chSysUnlock();
|
|
/* Reset the correlation decoder and its filters. */
|
|
pktResetAFSKDecoder(myDriver);
|
|
|
|
/* Turn off blue LED and reset time interval. */
|
|
pktWriteDecoderLED(PAL_LOW);
|
|
led_count = 0;
|
|
|
|
(void)chThdSetPriority(decoder_idle_priority);
|
|
|
|
/* Set decoder back to idle. */
|
|
myDriver->decoder_state = DECODER_IDLE;
|
|
break;
|
|
} /* End case DECODER_RESET. */
|
|
|
|
case DECODER_SUSPEND: {
|
|
if(myHandler->active_packet_object != NULL) {
|
|
|
|
/*
|
|
* Indicate AFSK decode done & lock the PWM queue.
|
|
*/
|
|
eventflags_t evtf = EVT_AFSK_DECODE_DONE | EVT_PWM_QUEUE_LOCK;
|
|
myDriver->active_demod_object->status |= evtf;
|
|
|
|
/* Copy latest status into packet buffer object. */
|
|
myHandler->active_packet_object->status =
|
|
myDriver->active_demod_object->status;
|
|
|
|
/* Dispatch the packet buffer object and get AX25 events. */
|
|
evtf |= pktDispatchReceivedBuffer(myHandler->active_packet_object);
|
|
|
|
/* Forget the packet object. */
|
|
myHandler->active_packet_object = NULL;
|
|
|
|
/*
|
|
* Send events then update PWM/demod object status.
|
|
* (the PWM input side doesn't care about AX25 events actually...)
|
|
*/
|
|
pktAddEventFlags(myHandler, evtf);
|
|
myDriver->active_demod_object->status |= evtf;
|
|
|
|
#if AFSK_DEBUG_TYPE == AFSK_PWM_DATA_CAPTURE_DEBUG
|
|
event_listener_t p_listener;
|
|
chEvtRegisterMaskWithFlags(
|
|
chnGetEventSource ((SerialDriver *)pkt_out),
|
|
&p_listener, DEC_DIAG_OUT_END,
|
|
CHN_TRANSMISSION_END);
|
|
char buf[80];
|
|
int out = chsnprintf(buf, sizeof(buf),
|
|
"\r\n======= END (%s) =========\r\n",
|
|
(myDriver->active_demod_object->status & EVT_AFSK_INVALID_FRAME)
|
|
? "invalid frame"
|
|
: (myDriver->active_demod_object->status & EVT_AX25_FRAME_RDY)
|
|
? "good CRC" : "bad CRC");
|
|
chnWrite(pkt_out, (uint8_t *)buf, out);
|
|
eventflags_t clear;
|
|
do {
|
|
clear = chEvtWaitAnyTimeout(DEC_DIAG_OUT_END, TIME_MS2I(100));
|
|
} while(clear != 0);
|
|
chEvtUnregister(chnGetEventSource ((SerialDriver *)pkt_out),
|
|
&p_listener);
|
|
#endif
|
|
} /* Active packet object != NULL. */
|
|
#if SUSPEND_HANDLING == NO_SUSPEND
|
|
myDriver->decoder_state = DECODER_RESET;
|
|
break;
|
|
#endif
|
|
/*
|
|
* Only exit suspend on posted event.
|
|
*/
|
|
eventmask_t evtf;
|
|
do {
|
|
evtf = chEvtWaitAnyTimeout(DEC_SUSPEND_EXIT,
|
|
TIME_US2I(DECODER_SUSPEND_TIME));
|
|
if(++led_count >= DECODER_LED_RATE_SUSPEND) {
|
|
pktWriteDecoderLED(PAL_TOGGLE);
|
|
led_count = 0;
|
|
}
|
|
} while(evtf == 0);
|
|
/*
|
|
* Post processing done.
|
|
* Reset the decoder and get ready for next packet.
|
|
*/
|
|
myDriver->decoder_state = DECODER_RESET;
|
|
break;
|
|
} /* End case DECODER_SUSPEND. */
|
|
} /* End switch on decoder state. */
|
|
} /* End thread while(true). */
|
|
}
|
|
|
|
/** @} */
|