kopia lustrzana https://github.com/DL7AD/pecanpico10
1027 wiersze
32 KiB
C
1027 wiersze
32 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 rxpwm.c
|
|
* @brief PWM data handler for radio.
|
|
* @brief the ICU driver is used to capture PWM data.
|
|
*
|
|
* @addtogroup channels
|
|
* @details The Radio PWM is a subsystem that will:
|
|
* - Respond to the CCA (squelch) interrupt from the radio NIRQ pin.
|
|
* - Receive PWM format AFSK data from the si446x radio.
|
|
* - Buffer data in a shared stream between the radio and the decoder.
|
|
* - Handle multiple sequential streams through a FIFO mechanism.
|
|
*
|
|
* @pre This subsystem requires an extended ICU data structure.
|
|
* see halconf.h for the configuration.
|
|
* @note
|
|
* @{
|
|
*/
|
|
|
|
#include "pktconf.h"
|
|
#include "pktradio.h"
|
|
|
|
/*===========================================================================*/
|
|
/* Module local definitions. */
|
|
/*===========================================================================*/
|
|
|
|
/*===========================================================================*/
|
|
/* Module exported variables. */
|
|
/*===========================================================================*/
|
|
|
|
/*===========================================================================*/
|
|
/* Module local types. */
|
|
/*===========================================================================*/
|
|
|
|
/*===========================================================================*/
|
|
/* Module local variables. */
|
|
/*===========================================================================*/
|
|
|
|
/*===========================================================================*/
|
|
/* Module local functions. */
|
|
/*===========================================================================*/
|
|
|
|
/*===========================================================================*/
|
|
/* Module exported functions. */
|
|
/*===========================================================================*/
|
|
|
|
/**
|
|
* @brief Attaches decoder to radio hardware according radio config.
|
|
* @post The PWM ICU is configured and started for a specified radio.
|
|
* @post The ports and timers for CCA input are configured.
|
|
*
|
|
* @param[in] radio_id radio being started.
|
|
*
|
|
* @return Pointer to assigned ICUDriver object.
|
|
*
|
|
* @api
|
|
*/
|
|
ICUDriver *pktAttachRadio(const radio_unit_t radio) {
|
|
/*
|
|
* Initialize the association between the radio and the PWM IO.
|
|
*/
|
|
ICUDriver *myICU = pktLLDradioAttachStream(radio);
|
|
|
|
chDbgAssert(myICU != NULL, "no ICU driver");
|
|
|
|
icuObjectInit(myICU);
|
|
|
|
/* Initialise the ICU PWM timers. */
|
|
chVTObjectInit(&myICU->cca_timer);
|
|
chVTObjectInit(&myICU->pwm_timer);
|
|
chVTObjectInit(&myICU->jam_timer);
|
|
|
|
/* Setup the squelch LED. */
|
|
pktLLDradioConfigIndicator(radio, PKT_INDICATOR_SQUELCH);
|
|
pktLLDradioUpdateIndicator(radio, PKT_INDICATOR_SQUELCH, PAL_LOW);
|
|
|
|
|
|
/* Setup the overflow LED. */
|
|
pktLLDradioConfigIndicator(radio, PKT_INDICATOR_OVERFLOW);
|
|
pktLLDradioUpdateIndicator(radio, PKT_INDICATOR_OVERFLOW, PAL_LOW);
|
|
|
|
|
|
/* Setup the no FIFO LED. */
|
|
pktLLDradioConfigIndicator(radio, PKT_INDICATOR_FIFO);
|
|
pktLLDradioUpdateIndicator(radio, PKT_INDICATOR_FIFO, PAL_LOW);
|
|
|
|
|
|
/* Setup the no buffer LED. */
|
|
pktLLDradioConfigIndicator(radio, PKT_INDICATOR_NO_BUFF);
|
|
pktLLDradioUpdateIndicator(radio, PKT_INDICATOR_NO_BUFF, PAL_LOW);
|
|
|
|
/* Setup the PWM error LED. */
|
|
pktLLDradioConfigIndicator(radio, PKT_INDICATOR_PWM_ERROR);
|
|
pktLLDradioUpdateIndicator(radio, PKT_INDICATOR_PWM_ERROR, PAL_LOW);
|
|
|
|
/* If using PWM mirror to output to a diagnostic port. */
|
|
//pktSetGPIOlineMode(LINE_PWM_MIRROR, PAL_MODE_OUTPUT_PUSHPULL);
|
|
|
|
return myICU;
|
|
}
|
|
|
|
/**
|
|
* @brief Detaches the Radio from the PWM handlers.
|
|
* @post The PWM ICU is stopped.
|
|
* @post The GPIO for CCA input is disabled.
|
|
* @post The GPIO for LED indicators are disabled.
|
|
*
|
|
* @param[in] radio radio attached to this PWM handler
|
|
*
|
|
* @api
|
|
*/
|
|
void pktDetachRadio(const radio_unit_t radio) {
|
|
|
|
packet_svc_t *myHandler = pktGetServiceObject(radio);
|
|
AFSKDemodDriver *myDemod = (AFSKDemodDriver *)myHandler->rx_link_control;
|
|
|
|
chDbgAssert(myDemod != NULL, "no demod linked");
|
|
|
|
/*
|
|
* Stop the ICU.
|
|
*/
|
|
icuStop(myDemod->icudriver);
|
|
|
|
/*
|
|
* Detach the radio from the PWM handlers.
|
|
*/
|
|
pktLLDradioDetachStream(radio);
|
|
myDemod->icudriver = NULL;
|
|
|
|
/* Disable the squelch LED. */
|
|
pktLLDradioDeconfigIndicator(radio, PKT_INDICATOR_SQUELCH);
|
|
|
|
/* Disable overflow LED. */
|
|
pktLLDradioDeconfigIndicator(radio, PKT_INDICATOR_OVERFLOW);
|
|
|
|
/* Disable no FIFO LED. */
|
|
pktLLDradioDeconfigIndicator(radio, PKT_INDICATOR_FIFO);
|
|
|
|
/* If using PWM mirror disable diagnostic port. */
|
|
//pktUnsetGPIOlineMode(LINE_PWM_MIRROR);
|
|
}
|
|
|
|
/**
|
|
* @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] radio radio attached to this PWM handler
|
|
*
|
|
* @api
|
|
*/
|
|
void pktEnableRadioStream(const radio_unit_t radio) {
|
|
|
|
packet_svc_t *myHandler = pktGetServiceObject(radio);
|
|
|
|
/* Is the AFSK decoder active? */
|
|
if(myHandler->rx_state != PACKET_RX_ENABLED)
|
|
return;
|
|
|
|
AFSKDemodDriver *myDemod = (AFSKDemodDriver *)myHandler->rx_link_control;
|
|
chDbgAssert(myDemod != NULL, "no link controller");
|
|
chDbgAssert(myDemod->icudriver != NULL, "no ICU driver");
|
|
|
|
switch(myDemod->icustate) {
|
|
case PKT_PWM_INIT: {
|
|
/* Enable CCA callback. */
|
|
const ICUConfig *icucfg = pktLLDradioStreamEnable(radio,
|
|
(palcallback_t)pktRadioCCAInput);
|
|
|
|
/* Start ICU and start capture. */
|
|
icuStart(myDemod->icudriver, icucfg);
|
|
icuStartCapture(myDemod->icudriver);
|
|
myDemod->icustate = PKT_PWM_READY;
|
|
return;
|
|
|
|
}
|
|
case PKT_PWM_STOP: {
|
|
/* Enable CCA callback. */
|
|
pktLLDradioStreamEnable(radio, (palcallback_t)pktRadioCCAInput);
|
|
|
|
/* Start ICU capture. */
|
|
icuStartCapture(myDemod->icudriver);
|
|
myDemod->icustate = PKT_PWM_READY;
|
|
return;
|
|
}
|
|
|
|
case PKT_PWM_READY:
|
|
return;
|
|
|
|
case PKT_PWM_ACTIVE: {
|
|
chDbgAssert(false, "wrong PWM state");
|
|
return;
|
|
}
|
|
} /* End switch. */
|
|
}
|
|
|
|
/**
|
|
* @brief Disables PWM stream from radio.
|
|
* @post The PWM channel is closed.
|
|
* @post All PWM related timers are stopped.
|
|
* @post The port for CCA input is disabled.
|
|
* @post The ICU capture is stopped.
|
|
* @post The ICU remains ready for capture to be restarted.
|
|
*
|
|
* @param[in] radio radio attached to this PWM handler
|
|
*
|
|
* @api
|
|
*/
|
|
void pktDisableRadioStream(const radio_unit_t radio) {
|
|
|
|
packet_svc_t *myHandler = pktGetServiceObject(radio);
|
|
|
|
/* Is the AFSK decoder active? */
|
|
if(myHandler->rx_state != PACKET_RX_ENABLED)
|
|
return;
|
|
AFSKDemodDriver *myDemod = (AFSKDemodDriver *)myHandler->rx_link_control;
|
|
chDbgAssert(myDemod != NULL, "no link controller");
|
|
chDbgAssert(myDemod->icudriver != NULL, "no ICU driver");
|
|
|
|
switch(myDemod->icustate) {
|
|
case PKT_PWM_ACTIVE: {
|
|
/* PWM incoming active. */
|
|
|
|
chSysLock();
|
|
/* Disable CCA line event. */
|
|
pktLLDradioStreamDisableI(radio);
|
|
|
|
/* Stop any timeouts in ICU PWM handling. */
|
|
pktStopAllICUtimersI(myDemod->icudriver);
|
|
|
|
/*
|
|
* Close the PWM stream.
|
|
* Disable ICU notifications.
|
|
* Stop ICU capture.
|
|
* Post in-band PWM message.
|
|
*
|
|
*/
|
|
pktClosePWMchannelI(myDemod->icudriver, EVT_NONE, PWM_TERM_PWM_STOP);
|
|
|
|
myDemod->icustate = PKT_PWM_STOP;
|
|
|
|
/*
|
|
* Reschedule to avoid a "priority order violation".
|
|
*/
|
|
chSchRescheduleS();
|
|
chSysUnlock();
|
|
return;
|
|
}
|
|
|
|
case PKT_PWM_STOP:
|
|
/* Stream is already stopped. */
|
|
return;
|
|
|
|
case PKT_PWM_INIT: {
|
|
chDbgAssert(false, "wrong PWM state");
|
|
return;
|
|
}
|
|
|
|
case PKT_PWM_READY: {
|
|
/*
|
|
* PWM incoming is enabled but not active.
|
|
* Capture and notifications are not enabled.
|
|
*/
|
|
|
|
chSysLock();
|
|
/* Disable CCA line event. */
|
|
pktLLDradioStreamDisableI(radio);
|
|
|
|
/* Stop any timeouts in ICU PWM handling. */
|
|
pktStopAllICUtimersI(myDemod->icudriver);
|
|
|
|
/* Stop ICU capture. */
|
|
icuStopCaptureI(myDemod->icudriver);
|
|
myDemod->icustate = PKT_PWM_STOP;
|
|
|
|
chSysUnlock();
|
|
return;
|
|
}
|
|
} /* End switch. */
|
|
}
|
|
|
|
|
|
/**
|
|
* @brief Timer callback when CCA trailing edge de-glitch period expires.
|
|
* @notes If CCA is still asserted then PWM capture will continue.
|
|
* @notes If CCA is not asserted then PWM capture will be closed.
|
|
*
|
|
* @param[in] myICU pointer to a @p ICUDriver structure
|
|
*
|
|
* @api
|
|
*/
|
|
void pktRadioJammingReset(ICUDriver *myICU) {
|
|
chSysLockFromISR();
|
|
AFSKDemodDriver *myDemod = myICU->link;
|
|
chDbgAssert(myDemod->icudriver != NULL, "no ICU driver");
|
|
|
|
if(myDemod->icustate == PKT_PWM_STOP) {
|
|
chSysUnlockFromISR();
|
|
return;
|
|
}
|
|
|
|
packet_svc_t *myHandler = myDemod->packet_handler;
|
|
/* Send event broadcast. */
|
|
pktAddEventFlagsI(myHandler, EVT_PWM_JAMMING_RESET);
|
|
chSysUnlockFromISR();
|
|
return;
|
|
}
|
|
|
|
/**
|
|
* @brief Terminates the PWM stream from the ICU.
|
|
* @post The ICU notification (callback) is stopped.
|
|
* @post An in-band reason code flag is written to the PWM queue.
|
|
* @post If the queue is full the optional LED is lit.
|
|
*
|
|
* @param[in] myICU pointer to a @p ICUDriver structure
|
|
* @param[in] event flags to be set as to why the channel is closed.
|
|
*
|
|
* @api
|
|
*/
|
|
void pktClosePWMchannelI(ICUDriver *myICU, eventflags_t evt, pwm_code_t reason) {
|
|
/* Stop posting data and write end marker. */
|
|
AFSKDemodDriver *myDemod = myICU->link;
|
|
packet_svc_t *myHandler = myDemod->packet_handler;
|
|
chDbgAssert(myDemod != NULL, "no demod linked");
|
|
|
|
radio_unit_t radio = myHandler->radio;
|
|
|
|
chVTResetI(&myICU->pwm_timer);
|
|
|
|
/*
|
|
* Turn off the squelch LED.
|
|
*/
|
|
pktLLDradioUpdateIndicator(radio, PKT_INDICATOR_SQUELCH, PAL_LOW);
|
|
|
|
/* Stop capture (and stop notifications). */
|
|
icuStopCaptureI(myICU);
|
|
|
|
/* Close can be called when there is no PWM activity. */
|
|
if(myDemod->active_radio_stream != NULL) {
|
|
pktAddEventFlagsI(myHandler, evt);
|
|
if(reason == PWM_TERM_QUEUE_ERROR) {
|
|
myDemod->active_radio_stream->status |= STA_PWM_QUEUE_ERROR;
|
|
} else {
|
|
#if USE_HEAP_PWM_BUFFER == TRUE
|
|
input_queue_t *myQueue =
|
|
&myDemod->active_radio_stream->radio_pwm_queue->queue;
|
|
#if USE_CCM_BASED_PWM_HEAP == TRUE
|
|
pktAssertCCMdynamicCheck(myQueue);
|
|
#endif
|
|
#else
|
|
input_queue_t *myQueue = &myDemod->active_radio_stream->radio_pwm_queue;
|
|
#endif
|
|
/* End of data flag. */
|
|
#if USE_12_BIT_PWM == TRUE
|
|
byte_packed_pwm_t pack = {{PWM_IN_BAND_PREFIX, reason, 0}};
|
|
#else
|
|
byte_packed_pwm_t pack = {{PWM_IN_BAND_PREFIX, reason}};
|
|
#endif
|
|
msg_t qs = pktWritePWMQueueI(myQueue, pack);
|
|
if(qs == MSG_TIMEOUT || qs == MSG_ERROR) {
|
|
/*
|
|
* No space to write in-band flag.
|
|
* This may be due to a pending ICU interrupt?
|
|
* In any case flag the error.
|
|
*/
|
|
if(qs == MSG_ERROR)
|
|
pktLLDradioUpdateIndicator(radio, PKT_INDICATOR_PWM_ERROR, PAL_HIGH);
|
|
|
|
if(qs == MSG_TIMEOUT)
|
|
pktLLDradioUpdateIndicator(radio, PKT_INDICATOR_OVERFLOW, PAL_HIGH);
|
|
|
|
pktAddEventFlagsI(myHandler, EVT_PWM_QUEUE_ERROR);
|
|
myDemod->active_radio_stream->status |= STA_PWM_QUEUE_ERROR;
|
|
} else {
|
|
myDemod->active_radio_stream->status |= STA_PWM_STREAM_CLOSED;
|
|
}
|
|
} /* End if(reason != PWM_TERM_QUEUE_ERROR). */
|
|
|
|
/* Allow the decoder thread to release the stream control object. */
|
|
chBSemSignalI(&myDemod->active_radio_stream->sem);
|
|
|
|
#if USE_HEAP_PWM_BUFFER == TRUE
|
|
/* Remove the PWM object reference. */
|
|
myDemod->active_radio_stream->radio_pwm_queue = NULL;
|
|
#endif
|
|
/* Remove object reference. */
|
|
myDemod->active_radio_stream = NULL;
|
|
} else {
|
|
/* No object. Just send event broadcast. */
|
|
pktAddEventFlagsI(myHandler, (evt | EVT_RAD_STREAM_CLOSE));
|
|
}
|
|
/* Return to ready state (inactive). */
|
|
myDemod->icustate = PKT_PWM_READY;
|
|
}
|
|
|
|
/**
|
|
* @brief Opens the PWM stream from the ICU.
|
|
* @post The ICU notification (callback) is enabled.
|
|
* @post If an error occurs the PWM is not started and state is unchanged.
|
|
* @post If the FIFO is empty the "no FIFO object" LED is lit (if assigned).
|
|
* @post If no error occurs the timers associated with PWM are started.
|
|
* @post The seized FIFO is sent via the queue mailbox.
|
|
* @post The ICU state is set to active.
|
|
*
|
|
* @param[in] myICU pointer to a @p ICUDriver structure
|
|
* @param[in] event flags to be set as to why the channel is opened.
|
|
*
|
|
* @api
|
|
*/
|
|
void pktOpenPWMChannelI(ICUDriver *myICU, eventflags_t evt) {
|
|
AFSKDemodDriver *myDemod = myICU->link;
|
|
packet_svc_t *myHandler = myDemod->packet_handler;
|
|
radio_unit_t radio = myHandler->radio;
|
|
|
|
/* Turn on the squelch LED. */
|
|
pktLLDradioUpdateIndicator(radio, PKT_INDICATOR_SQUELCH, PAL_HIGH);
|
|
|
|
if(myDemod->active_radio_stream != NULL) {
|
|
/* TODO: Work out correct handling. We should not have an open channel.
|
|
* Shouldn't happen unless CCA has not triggered an EXTI trailing edge.
|
|
* For now just flag that an error condition happened.
|
|
*/
|
|
pktClosePWMchannelI(myICU, EVT_PWM_FIFO_REMNANT, PWM_TERM_QUEUE_ERR);
|
|
return;
|
|
}
|
|
/* Normal CCA handling. */
|
|
radio_pwm_fifo_t *myFIFO = chFifoTakeObjectI(myDemod->pwm_fifo_pool);
|
|
if(myFIFO == NULL) {
|
|
myDemod->active_radio_stream = NULL;
|
|
/* No FIFO available.
|
|
* Send an event to any listener.
|
|
* Disable ICU notifications.
|
|
*/
|
|
pktAddEventFlagsI(myHandler, EVT_PWM_FIFO_EMPTY);
|
|
icuDisableNotificationsI(myICU);
|
|
|
|
/*
|
|
* This looks like noise/jamming.
|
|
* Wait for a timeout before allowing new CAA detection.
|
|
* TODO: Keep data on jamming and adjust RSSI threshold?
|
|
*/
|
|
chVTSetI(&myICU->jam_timer, TIME_S2I(PWM_JAMMING_TIMEOUT),
|
|
(vtfunc_t)pktRadioJammingReset, myICU);
|
|
|
|
/* Turn on the FIFO out LED. */
|
|
pktLLDradioUpdateIndicator(radio, PKT_INDICATOR_FIFO, PAL_HIGH);
|
|
return;
|
|
}
|
|
|
|
/* Save the FIFO used for this PWM -> decoder session. */
|
|
myDemod->active_radio_stream = myFIFO;
|
|
|
|
#if USE_HEAP_PWM_BUFFER == TRUE
|
|
/*
|
|
* The linked PWM queue system buffers PWM in chained queue/buffer pool objects.
|
|
* Once CCA is validated PWM buffering commences.
|
|
* A queue/buffer object is taken from the pool.
|
|
* The object is set as the current radio PWM side object.
|
|
* This will be replaced as PWM arrives and the buffer becomes full.
|
|
*
|
|
* As PWM data arrives the memory pool object buffer is filled with PWM data.
|
|
* When the current buffer is full a new object is obtained from the pool.
|
|
* The embedded queue is initialised and points to the objects internal buffer.
|
|
* The new object is chained to the prior buffer object.
|
|
* The pointer is updated to point to the new object
|
|
*
|
|
* The PWM interrupt handler then continues to fill the new buffer.
|
|
*
|
|
* Each memory pool object contains:
|
|
* 1. An embedded input queue object
|
|
* 2. A buffer associated with the input queue
|
|
* 3. A pointer to the next object (or NULL if none)
|
|
*
|
|
*/
|
|
|
|
radio_pwm_object_t *pwm_object = chPoolAllocI(&myDemod->pwm_buffer_pool);
|
|
if(pwm_object == NULL) {
|
|
/*
|
|
* Failed to get a PWM buffer object.
|
|
* Post an event and disable ICU.
|
|
*/
|
|
chFifoReturnObjectI(myDemod->pwm_fifo_pool, myFIFO);
|
|
myDemod->active_radio_stream = NULL;
|
|
pktAddEventFlagsI(myHandler, EVT_PWM_BUFFER_FAIL);
|
|
icuDisableNotificationsI(myICU);
|
|
/* Turn on the PWM buffer out LED. */
|
|
pktLLDradioUpdateIndicator(radio, PKT_INDICATOR_OVERFLOW, PAL_HIGH);
|
|
return;
|
|
}
|
|
|
|
#if USE_CCM_BASED_PWM_HEAP == TRUE
|
|
/* Verify object is in CCM. */
|
|
pktAssertCCMdynamicCheck(pwm_object);
|
|
#endif
|
|
pktLLDradioUpdateIndicator(radio, PKT_INDICATOR_NO_BUFF, PAL_HIGH);
|
|
|
|
/* Save this object as the one currently receiving PWM. */
|
|
myFIFO->radio_pwm_queue = pwm_object;
|
|
#if TRACE_PWM_BUFFER_STATS == TRUE
|
|
myFIFO->in_use = 1;
|
|
myFIFO->sync = 1;
|
|
myFIFO->peak = 0;
|
|
myFIFO->rlsd = 0;
|
|
#endif
|
|
myFIFO->decode_pwm_queue = pwm_object;
|
|
/*
|
|
* Initialize the queue object.
|
|
* Set the user defined link to NULL.
|
|
* Using the embedded link allows removal of the buffer object link field.
|
|
*/
|
|
iqObjectInit(&pwm_object->queue,
|
|
(*pwm_object).buffer.pwm_bytes,
|
|
sizeof(radio_pwm_buffer_t),
|
|
NULL, NULL);
|
|
|
|
#else /* USE_HEAP_PWM_BUFFER != TRUE */
|
|
/* Non linked FIFOs have an embedded input queue with data buffer. */
|
|
iqObjectInit(&myFIFO->radio_pwm_queue,
|
|
myFIFO->packed_buffer.pwm_bytes,
|
|
sizeof(radio_pwm_buffer_t),
|
|
NULL, NULL);
|
|
#endif /* USE_HEAP_PWM_BUFFER == TRUE */
|
|
|
|
/*
|
|
* Initialize stream object release control semaphore.
|
|
* The decoder thread waits on the semaphore before releasing to pool.
|
|
*/
|
|
chBSemObjectInit(&myFIFO->sem, true);
|
|
|
|
/*
|
|
* Set the status of this FIFO.
|
|
* Send the FIFO entry to the decoder thread.
|
|
*/
|
|
chFifoSendObjectI(myDemod->pwm_fifo_pool, myFIFO);
|
|
|
|
/*
|
|
* Start the PWM activity timer.
|
|
* This catches the condition where CCA raises but no RX data appears.
|
|
*/
|
|
chVTSetI(&myICU->pwm_timer, TIME_MS2I(50),
|
|
(vtfunc_t)pktPWMInactivityTimeout, myICU);
|
|
|
|
icuStartCaptureI(myICU);
|
|
icuEnableNotificationsI(myICU);
|
|
pktAddEventFlagsI(myHandler, evt);
|
|
|
|
/* Clear status bits. */
|
|
myFIFO->status = 0;
|
|
|
|
myDemod->icustate = PKT_PWM_ACTIVE;
|
|
}
|
|
|
|
/**
|
|
* @brief Stop all ICU associated timers.
|
|
* @notes Will be called when the packet channel is stopped.
|
|
*
|
|
* @param[in] myICU pointer to a @p ICUDriver structure
|
|
*
|
|
* @iclass
|
|
*/
|
|
void pktStopAllICUtimersI(ICUDriver *myICU) {
|
|
chVTResetI(&myICU->cca_timer);
|
|
chVTResetI(&myICU->pwm_timer);
|
|
chVTResetI(&myICU->jam_timer);
|
|
}
|
|
|
|
/**
|
|
* @brief Timer callback when no PWM data arises from a CCA open.
|
|
* @post The PWM channel will be closed
|
|
*
|
|
* @param[in] myICU pointer to a @p ICUDriver structure
|
|
*
|
|
* @api
|
|
*/
|
|
void pktPWMInactivityTimeout(ICUDriver *myICU) {
|
|
/* Timeout waiting for PWM data from the radio. */
|
|
chSysLockFromISR();
|
|
AFSKDemodDriver *myDemod = myICU->link;
|
|
if(myDemod->active_radio_stream != NULL
|
|
&& myDemod->icustate == PKT_PWM_ACTIVE) {
|
|
pktClosePWMchannelI(myICU, EVT_PWM_NO_DATA, PWM_TERM_NO_DATA);
|
|
}
|
|
chSysUnlockFromISR();
|
|
}
|
|
|
|
/**
|
|
* @brief Timer callback when CCA leading edge de-glitch period expires.
|
|
* @notes If CCA is still asserted then PWM capture will be enabled.
|
|
*
|
|
* @param[in] myICU pointer to a @p ICUDriver structure
|
|
*
|
|
* @api
|
|
*/
|
|
void pktRadioCCALeadTimer(ICUDriver *myICU) {
|
|
chSysLockFromISR();
|
|
AFSKDemodDriver *myDemod = myICU->link;
|
|
chDbgAssert(myDemod->icudriver != NULL, "no ICU driver");
|
|
|
|
packet_svc_t *myHandler = myDemod->packet_handler;
|
|
if(myDemod->icustate == PKT_PWM_STOP || chVTIsArmedI(&myICU->jam_timer)) {
|
|
chSysUnlockFromISR();
|
|
return;
|
|
}
|
|
uint8_t cca = pktLLDradioReadCCAline(myHandler->radio);
|
|
/* CCA de-glitch timer expired. */
|
|
switch(cca) {
|
|
case PAL_LOW: {
|
|
/*
|
|
* CAA has dropped so it is a spike which is ignored.
|
|
*/
|
|
pktAddEventFlagsI(myHandler, EVT_RADIO_CCA_SPIKE);
|
|
break;
|
|
}
|
|
|
|
/* CCA still high so start PWM stream now CCA is validated. */
|
|
case PAL_HIGH: {
|
|
pktOpenPWMChannelI(myICU, EVT_RAD_STREAM_OPEN);
|
|
break;
|
|
}
|
|
}
|
|
chSysUnlockFromISR();
|
|
return;
|
|
}
|
|
|
|
/**
|
|
* @brief Timer callback when CCA trailing edge de-glitch period expires.
|
|
* @notes If CCA is still asserted then PWM capture will continue.
|
|
* @notes If CCA is not asserted then PWM capture will be closed.
|
|
*
|
|
* @param[in] myICU pointer to a @p ICUDriver structure
|
|
*
|
|
* @api
|
|
*/
|
|
void pktRadioCCATrailTimer(ICUDriver *myICU) {
|
|
chSysLockFromISR();
|
|
AFSKDemodDriver *myDemod = myICU->link;
|
|
chDbgAssert(myDemod->icudriver != NULL, "no ICU driver");
|
|
|
|
if(myDemod->icustate == PKT_PWM_STOP || chVTIsArmedI(&myICU->jam_timer)) {
|
|
chSysUnlockFromISR();
|
|
return;
|
|
}
|
|
|
|
packet_svc_t *myHandler = myDemod->packet_handler;
|
|
uint8_t cca = pktLLDradioReadCCAline(myHandler->radio);
|
|
/* CCA de-glitch timer for trailing edge expired. */
|
|
switch(cca) {
|
|
case PAL_LOW: {
|
|
/*
|
|
* The decoder operates asynchronously to and usually slower than PWM.
|
|
* Hence the decoder is responsible for releasing the PWM FIFO object.
|
|
* Prior to releasing the FIFO the decoder waits on the FIFO semaphore.
|
|
* Closing PWM from here sets the FIFO management semaphore.
|
|
* This caters for the case where the decoder terminates stream processing first.
|
|
* This may happen if noise produces a long string of data.
|
|
*/
|
|
pktClosePWMchannelI(myICU, EVT_NONE, PWM_TERM_CCA_CLOSE);
|
|
break;
|
|
}
|
|
|
|
case PAL_HIGH: {
|
|
/* CCA is active again so leave PWM open. */
|
|
pktAddEventFlagsI(myHandler, EVT_RADIO_CCA_GLITCH);
|
|
break;
|
|
}
|
|
}
|
|
chSysUnlockFromISR();
|
|
return;
|
|
}
|
|
|
|
/**
|
|
* @brief GPIO callback when CCA edge transitions.
|
|
* @notes Both edges are de-glitched by the CCA timer.
|
|
* @notes Called from ISR level.
|
|
*
|
|
* @param[in] myICU pointer to a @p ICUDriver structure
|
|
*
|
|
* @isr
|
|
*/
|
|
void pktRadioCCAInput(ICUDriver *myICU) {
|
|
chSysLockFromISR();
|
|
AFSKDemodDriver *myDemod = myICU->link;
|
|
chDbgAssert(myDemod->icudriver != NULL, "no ICU driver");
|
|
|
|
if(myDemod->icustate == PKT_PWM_STOP || chVTIsArmedI(&myICU->jam_timer)) {
|
|
chSysUnlockFromISR();
|
|
return;
|
|
}
|
|
packet_svc_t *myHandler = myDemod->packet_handler;
|
|
uint8_t cca = pktLLDradioReadCCAline(myHandler->radio);
|
|
/* CCA changed. */
|
|
switch(cca) {
|
|
case PAL_LOW: {
|
|
if(myDemod->icustate == PKT_PWM_ACTIVE) {
|
|
/* CCA trailing edge glitch handling.
|
|
* Start timer and check if CCA remains low before closing PWM.
|
|
*
|
|
* De-glitch for 8 AFSK bit times.
|
|
*/
|
|
chVTSetI(&myICU->cca_timer, TIME_US2I(833 * 8),
|
|
(vtfunc_t)pktRadioCCATrailTimer, myICU);
|
|
}
|
|
/* Idle state. */
|
|
break;
|
|
} /* End case PAL_LOW. */
|
|
|
|
case PAL_HIGH: {
|
|
if(chVTIsArmedI(&myICU->cca_timer)) {
|
|
/* CAA has been re-asserted during trailing edge timer. */
|
|
chVTResetI(&myICU->cca_timer);
|
|
break;
|
|
}
|
|
/* Else this is a leading edge of CCA for a new packet. */
|
|
/* De-glitch for 16 AFSK bit times. */
|
|
chVTSetI(&myICU->cca_timer,
|
|
TIME_US2I(833 * 16),
|
|
(vtfunc_t)pktRadioCCALeadTimer, myICU);
|
|
break;
|
|
}
|
|
} /* End switch. */
|
|
chSysUnlockFromISR();
|
|
return;
|
|
}
|
|
|
|
#if LINE_PWM_MIRROR != PAL_NOLINE
|
|
/**
|
|
* @brief Width callback from ICU driver.
|
|
* @notes Called at ISR level.
|
|
*
|
|
* @param[in] myICU pointer to a @p ICUDriver structure
|
|
*
|
|
* @api
|
|
*/
|
|
void pktRadioICUWidth(ICUDriver *myICU) {
|
|
(void)myICU;
|
|
//pktWriteGPIOline(LINE_PWM_MIRROR, PAL_LOW);
|
|
}
|
|
#endif
|
|
|
|
/**
|
|
* @brief Period callback from ICU driver.
|
|
* @notes Called at ISR level.
|
|
*
|
|
* @param[in] myICU pointer to a @p ICUDriver structure
|
|
*
|
|
* @api
|
|
*/
|
|
void pktRadioICUPeriod(ICUDriver *myICU) {
|
|
/* ICU data structure is extended with...
|
|
* - a pointer to the decoder control.
|
|
* - timers used in ICU.
|
|
*
|
|
* See halconf.h for the definition.
|
|
*/
|
|
//pktWriteGPIOline(LINE_PWM_MIRROR, PAL_HIGH);
|
|
|
|
AFSKDemodDriver *myDemod = myICU->link;
|
|
chDbgAssert(myDemod->icudriver != NULL, "no ICU driver");
|
|
|
|
if(myDemod->icustate != PKT_PWM_ACTIVE)
|
|
return;
|
|
|
|
chSysLockFromISR();
|
|
/*
|
|
* On period clear the ICU activity watchdog timer.
|
|
* i.e. Once radio data appears a "no data" timeout is invalidated.
|
|
*/
|
|
chVTResetI(&myICU->pwm_timer);
|
|
|
|
if(myDemod->active_radio_stream == NULL) {
|
|
/*
|
|
* Arrive here when we are running but not buffering.
|
|
* The ICU has been stopped and PWM aborted.
|
|
*/
|
|
chSysUnlockFromISR();
|
|
return;
|
|
}
|
|
/*
|
|
* Check if decoding has already finished while ICU is still active.
|
|
* The decoder terminates a frame on the first trailing HDLC flag.
|
|
* If CPU is fast (FPU enabled) it might finish decode before PWM stops.
|
|
* A long sequence of trailing HDLC flags or junk after a frame close
|
|
* flag may cause trailing PWM activity.
|
|
*
|
|
*/
|
|
if((myDemod->active_radio_stream->status & STA_AFSK_DECODE_DONE) != 0) {
|
|
pktClosePWMchannelI(myICU, EVT_NONE, PWM_ACK_DECODE_END);
|
|
chSysUnlockFromISR();
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Check if the the decoder encountered an error condition.
|
|
* This will happen when no AX25 buffer is available or overflows.
|
|
* Close the PWM stream and wait for next radio CCA.
|
|
*/
|
|
if((myDemod->active_radio_stream->status & STA_AFSK_DECODE_RESET) != 0) {
|
|
pktClosePWMchannelI(myICU, EVT_NONE, PWM_ACK_DECODE_ERROR);
|
|
chSysUnlockFromISR();
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Check if impulse ICU value is zero and thus invalid.
|
|
*/
|
|
if(icuGetWidthX(myICU) == 0) {
|
|
pktClosePWMchannelI(myICU, EVT_NONE, PWM_TERM_ICU_ZERO);
|
|
chSysUnlockFromISR();
|
|
return;
|
|
}
|
|
|
|
/* Write ICU data to PWM queue. */
|
|
msg_t qs = pktQueuePWMDataI(myICU);
|
|
|
|
/* Switch on PWM write result. */
|
|
switch(qs) {
|
|
case MSG_OK: {
|
|
/* PWM write OK. */
|
|
chSysUnlockFromISR();
|
|
return;
|
|
}
|
|
|
|
case MSG_RESET: {
|
|
#if USE_HEAP_PWM_BUFFER == TRUE
|
|
/* Get another queue/buffer object. */
|
|
radio_pwm_object_t *pwm_object = chPoolAllocI(&myDemod->pwm_buffer_pool);
|
|
if(pwm_object != NULL) {
|
|
#if USE_CCM_BASED_PWM_HEAP == TRUE
|
|
pktAssertCCMdynamicCheck(pwm_object);
|
|
#endif
|
|
/*
|
|
* Initialise the new queue/buffer object.
|
|
* The next link is set to NULL.
|
|
*/
|
|
iqObjectInit(&pwm_object->queue,
|
|
(*pwm_object).buffer.pwm_bytes,
|
|
sizeof(radio_pwm_buffer_t),
|
|
NULL, NULL);
|
|
|
|
/* Link the new object in read sequence after the prior object. */
|
|
radio_pwm_object_t *myObject =
|
|
myDemod->active_radio_stream->radio_pwm_queue;
|
|
|
|
qSetLink(&myObject->queue, pwm_object);
|
|
#if TRACE_PWM_BUFFER_STATS == TRUE
|
|
/* Update statistics. */
|
|
myDemod->active_radio_stream->in_use++;
|
|
uint8_t out = (myDemod->active_radio_stream->in_use
|
|
- myDemod->active_radio_stream->rlsd);
|
|
if(out > myDemod->active_radio_stream->peak)
|
|
myDemod->active_radio_stream->peak = out;
|
|
#endif
|
|
/* Write the in-band queue swap message to the current object. */
|
|
|
|
#if USE_12_BIT_PWM == TRUE
|
|
byte_packed_pwm_t pack = {{PWM_IN_BAND_PREFIX, PWM_INFO_QUEUE_SWAP, 0}};
|
|
#else
|
|
byte_packed_pwm_t pack = {{PWM_IN_BAND_PREFIX, PWM_INFO_QUEUE_SWAP}};
|
|
#endif
|
|
|
|
/* Write the swap message to the old queue. */
|
|
msg_t qs = pktWritePWMQueueI(&myObject->queue, pack);
|
|
|
|
/* Set the new object as the active PWM queue/buffer. */
|
|
myDemod->active_radio_stream->radio_pwm_queue = pwm_object;
|
|
|
|
/* Write the PWM data to the new buffer. */
|
|
qs = pktQueuePWMDataI(myICU);
|
|
chDbgAssert(qs == MSG_OK, "PWM initial write to empty buffer failed");
|
|
chSysUnlockFromISR();
|
|
return;
|
|
} /* End pwm_object != NULL. */
|
|
/* No next PWM stream buffer object available. */
|
|
#endif /* USE_HEAP_PWM_BUFFER == TRUE */
|
|
/*
|
|
* No Next PWM stream buffer available.
|
|
* Queue has space for one entry only.
|
|
* Close channel and write in-band message indicating queue full.
|
|
*/
|
|
radio_unit_t radio = myDemod->packet_handler->radio;
|
|
|
|
pktLLDradioUpdateIndicator(radio, PKT_INDICATOR_OVERFLOW, PAL_HIGH);
|
|
pktClosePWMchannelI(myICU, EVT_PWM_QUEUE_FULL, PWM_TERM_QUEUE_FULL);
|
|
/*
|
|
* This looks like noise/jamming.
|
|
* Wait for a timeout before allowing new CAA detection.
|
|
* TODO: Keep data on jamming and adjust RSSI threshold?
|
|
*/
|
|
chVTSetI(&myICU->jam_timer, TIME_S2I(PWM_JAMMING_TIMEOUT),
|
|
(vtfunc_t)pktRadioJammingReset, myICU);
|
|
chSysUnlockFromISR();
|
|
return;
|
|
} /* End case. */
|
|
|
|
case MSG_TIMEOUT: {
|
|
chDbgAssert(qs != MSG_OK, "PWM queue unexpectedly full");
|
|
pktClosePWMchannelI(myICU, EVT_PWM_QUEUE_ERROR, PWM_TERM_QUEUE_ERROR);
|
|
chSysUnlockFromISR();
|
|
return;
|
|
} /* End case. */
|
|
|
|
case MSG_ERROR: {
|
|
chDbgAssert(qs != MSG_OK, "PWM initial write to empty buffer failed");
|
|
chSysUnlockFromISR();
|
|
return;
|
|
} /* End case. */
|
|
|
|
default:
|
|
chDbgAssert(false, "PWM invalid return code from PWM queue write");
|
|
} /* End switch. */
|
|
}
|
|
|
|
/**
|
|
* @brief Overflow callback from ICU driver.
|
|
* @notes Called at ISR level.
|
|
* @notes This indicates PWM data that is outside AFSK timing bounds.
|
|
*
|
|
* @param[in] myICU pointer to a @p ICUDriver structure
|
|
*
|
|
* @api
|
|
*/
|
|
void pktRadioICUOverflow(ICUDriver *myICU) {
|
|
chSysLockFromISR();
|
|
AFSKDemodDriver *myDemod = myICU->link;
|
|
/* packet_svc_t *myHandler = myDemod->packet_handler;
|
|
pktAddEventFlagsI(myHandler, EVT_ICU_OVERFLOW);*/
|
|
if(myDemod->active_radio_stream != NULL) {
|
|
/* Close the channel and stop ICU notifications. */
|
|
pktClosePWMchannelI(myICU, EVT_NONE, PWM_TERM_ICU_OVERFLOW);
|
|
} else {
|
|
/* Just stop the ICU notification. */
|
|
icuDisableNotificationsI(myICU);
|
|
}
|
|
chSysUnlockFromISR();
|
|
}
|
|
|
|
/**
|
|
* @brief Converts ICU data and posts to the PWM queue.
|
|
* @pre The ICU driver is linked to a demod driver (pointer to driver).
|
|
* @details Byte values of packed PWM data are written into an input queue.
|
|
*
|
|
* @param[in] myICU pointer to the ICU driver structure
|
|
*
|
|
* @return The operation status.
|
|
* @retval MSG_OK The PWM data has been queued.
|
|
* @retval MSG_TIMEOUT The queue is already full.
|
|
* @retval MSG_RESET Queue has one slot left and the data is not an in-band.
|
|
* @retval MSG_ERROR The PWM queue chunk size is incorrect.
|
|
*
|
|
* @iclass
|
|
*/
|
|
msg_t pktQueuePWMDataI(ICUDriver *myICU) {
|
|
|
|
chDbgCheckClassI();
|
|
|
|
AFSKDemodDriver *myDemod = myICU->link;
|
|
chDbgAssert(myDemod != NULL, "no linked demod driver");
|
|
|
|
#if USE_HEAP_PWM_BUFFER == TRUE
|
|
input_queue_t *myQueue =
|
|
&myDemod->active_radio_stream->radio_pwm_queue->queue;
|
|
#if USE_CCM_BASED_PWM_HEAP == TRUE
|
|
pktAssertCCMdynamicCheck(myQueue);
|
|
#endif
|
|
#else
|
|
input_queue_t *myQueue = &myDemod->active_radio_stream->radio_pwm_queue;
|
|
#endif
|
|
chDbgAssert(myQueue != NULL, "no queue assigned");
|
|
|
|
byte_packed_pwm_t pack;
|
|
pktConvertICUtoPWM(myICU, &pack);
|
|
return pktWritePWMQueueI(myQueue, pack);
|
|
}
|
|
|
|
|
|
/**
|
|
* @brief Write PWM data into input queue.
|
|
* @note This function deals with PWM data packed in sequential bytes.
|
|
*
|
|
* @param[in] queue pointer to an input queue object.
|
|
* @param[in] pack PWM packed data object.
|
|
*
|
|
* @return The operation status.
|
|
* @retval MSG_OK The PWM entry has been queued.
|
|
* @retval MSG_RESET One slot remains which is reserved for an in-band signal.
|
|
* @retval MSG_TIMEOUT The queue is full for normal PWM data writes.
|
|
* @retval MSG_ERROR The queue chunk size has become incorrect.
|
|
*
|
|
*
|
|
* @api
|
|
*/
|
|
msg_t pktWritePWMQueueI(input_queue_t *queue,
|
|
byte_packed_pwm_t pack) {
|
|
|
|
size_t empty = iqGetEmptyI(queue);
|
|
|
|
if(empty % sizeof(byte_packed_pwm_t) != 0) {
|
|
chDbgAssert(false, "invalid PWM chunk size");
|
|
return MSG_ERROR;
|
|
}
|
|
|
|
/* Check if there is only one slot left. */
|
|
if(empty == sizeof(byte_packed_pwm_t)) {
|
|
array_min_pwm_counts_t data;
|
|
pktUnpackPWMData(pack, &data);
|
|
if(data.pwm.impulse != PWM_IN_BAND_PREFIX)
|
|
return MSG_RESET;
|
|
}
|
|
|
|
if(empty < sizeof(byte_packed_pwm_t))
|
|
return MSG_TIMEOUT;
|
|
|
|
/* Data is normal PWM or an in-band. */
|
|
for(uint8_t b = 0; b < sizeof(pack.bytes); b++) {
|
|
iqPutI(queue, pack.bytes[b]);
|
|
}
|
|
return MSG_OK;
|
|
}
|
|
/** @} */
|