kopia lustrzana https://github.com/DL7AD/pecanpico10
585 wiersze
18 KiB
C
585 wiersze
18 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 pktradio.c
|
|
* @brief Radio manager.
|
|
*
|
|
* @addtogroup managers
|
|
* @{
|
|
*/
|
|
|
|
#include "pktconf.h"
|
|
#ifndef PKT_IS_TEST_PROJECT
|
|
#include "radio.h"
|
|
#endif
|
|
#include "si446x.h"
|
|
#include "debug.h"
|
|
|
|
/**
|
|
* @brief Process radio task requests.
|
|
* @notes Task objects posted to the queue are processed per radio.
|
|
* @notes The queue is blocked while the radio driver functions execute.
|
|
* @notes Receive tasks start the receive/decode system which are threads.
|
|
* @notes Transmit tasks should be handled in threads (and are in 446x).
|
|
*
|
|
* @param[in] arg pointer to a @p radio task queue for this thread.
|
|
*
|
|
* @return status (MSG_OK) on exit.
|
|
*
|
|
* @notapi
|
|
*/
|
|
THD_FUNCTION(pktRadioManager, arg) {
|
|
/* When no task in queue use this rate. */
|
|
#define PKT_RADIO_TASK_MANAGER_IDLE_RATE_MS 250
|
|
|
|
/* When a TX task is submitted to radio switch to this rate. */
|
|
#define PKT_RADIO_TASK_MANAGER_TX_RATE_MS 100
|
|
|
|
/* Continue at TX rate for this number of cycles. */
|
|
#define PKT_RADIO_TASK_MANAGER_TX_HYSTERESIS 10
|
|
|
|
packet_svc_t *handler = arg;
|
|
|
|
dyn_objects_fifo_t *the_radio_fifo = handler->the_radio_fifo;
|
|
|
|
chDbgCheck(arg != NULL);
|
|
|
|
sysinterval_t poll_rate = PKT_RADIO_TASK_MANAGER_IDLE_RATE_MS;
|
|
uint8_t poll_hysteresis = 0;
|
|
|
|
objects_fifo_t *radio_queue = chFactoryGetObjectsFIFO(the_radio_fifo);
|
|
|
|
chDbgAssert(radio_queue != NULL, "no queue in radio manager FIFO");
|
|
|
|
/* Run until terminate request and no outstanding TX tasks. */
|
|
while(!(chThdShouldTerminateX() && handler->tx_count == 0)) {
|
|
/* Check for task requests. */
|
|
radio_task_object_t *task_object;
|
|
msg_t fifo_msg = chFifoReceiveObjectTimeout(radio_queue,
|
|
(void *)&task_object,
|
|
TIME_MS2I(poll_rate));
|
|
if(fifo_msg == MSG_TIMEOUT) {
|
|
if(poll_hysteresis == 0)
|
|
poll_rate = PKT_RADIO_TASK_MANAGER_IDLE_RATE_MS;
|
|
else
|
|
--poll_hysteresis;
|
|
continue;
|
|
}
|
|
/* Something to do. */
|
|
|
|
radio_unit_t radio = handler->radio;
|
|
/* Process command. */
|
|
switch(task_object->command) {
|
|
case PKT_RADIO_RX_OPEN: {
|
|
|
|
/* Create the packet management services. */
|
|
if(pktIncomingBufferPoolCreate(radio) == NULL) {
|
|
pktAddEventFlags(handler, (EVT_PKT_BUFFER_MGR_FAIL));
|
|
break;
|
|
}
|
|
/* Create callback manager. */
|
|
if(pktCallbackManagerCreate(radio) == NULL) {
|
|
pktAddEventFlags(handler, (EVT_PKT_CBK_MGR_FAIL));
|
|
pktIncomingBufferPoolRelease(handler);
|
|
break;
|
|
}
|
|
/* Switch on modulation type. */
|
|
switch(task_object->type) {
|
|
case MOD_AFSK: {
|
|
/* Create the AFSK decoder (includes PWM, filters, etc.). */
|
|
AFSKDemodDriver *driver = pktCreateAFSKDecoder(handler);
|
|
handler->link_controller = driver;
|
|
/* If AFSK start failed send event but leave managers running. */
|
|
if(driver == NULL) {
|
|
pktAddEventFlags(handler, (EVT_AFSK_START_FAIL));
|
|
/* pktBufferManagerRelease(handler);
|
|
pktCallbackManagerRelease(handler);*/
|
|
break;
|
|
}
|
|
break;
|
|
} /* End case PKT_RADIO_OPEN. */
|
|
|
|
case MOD_NONE:
|
|
case MOD_2FSK: {
|
|
break;
|
|
}
|
|
break;
|
|
} /* End switch on modulation type. */
|
|
|
|
/* Initialise the radio. */
|
|
Si446x_conditional_init(radio);
|
|
break;
|
|
} /* End case PKT_RADIO_OPEN. */
|
|
|
|
|
|
case PKT_RADIO_RX_START: {
|
|
/* The function switches on mod type so no need for switch here. */
|
|
switch(task_object->type) {
|
|
case MOD_AFSK: {
|
|
pktAcquireRadio(radio, TIME_INFINITE);
|
|
|
|
/* TODO: Move these 446x calls into abstracted LLD. */
|
|
Si446x_setBandParameters(radio,
|
|
task_object->base_frequency,
|
|
task_object->step_hz);
|
|
|
|
Si446x_receiveNoLock(radio,
|
|
task_object->base_frequency,
|
|
task_object->step_hz,
|
|
task_object->channel,
|
|
task_object->squelch,
|
|
MOD_AFSK);
|
|
/* TODO: If decoder is not running error out. */
|
|
|
|
pktStartDecoder(radio);
|
|
handler->rx_active = true;
|
|
/* Allow transmit requests. */
|
|
pktReleaseRadio(radio);
|
|
break;
|
|
} /* End case MOD_AFSK. */
|
|
|
|
case MOD_NONE:
|
|
case MOD_2FSK: {
|
|
break;
|
|
}
|
|
} /* End switch on task_object->type. */
|
|
break;
|
|
} /* End case PKT_RADIO_RX. */
|
|
|
|
case PKT_RADIO_RX_STOP: {
|
|
switch(task_object->type) {
|
|
case MOD_AFSK: {
|
|
pktStopDecoder(handler->radio);
|
|
handler->rx_active = false;
|
|
break;
|
|
} /* End case. */
|
|
|
|
case MOD_NONE:
|
|
case MOD_2FSK: {
|
|
break;
|
|
}
|
|
} /* End switch. */
|
|
break;
|
|
} /* End case PKT_RADIO_RX_STOP. */
|
|
|
|
case PKT_RADIO_TX_SEND: {
|
|
/*
|
|
* TODO: Currently the decoder is not paused.
|
|
* Is it necessary since the RX is not outputting data during TX?
|
|
*/
|
|
|
|
/* TODO: Move all setting of pp params to radio.c */
|
|
packet_t pp = task_object->packet_out;
|
|
/* pp->base_frequency = task_object->base_frequency;
|
|
pp->radio_step = task_object->step_hz;
|
|
pp->radio_chan = task_object->channel;
|
|
pp->radio_pwr = task_object->tx_power;
|
|
pp->cca_rssi = task_object->squelch;*/
|
|
|
|
/* Give each send a sequence number. */
|
|
/* TODO: Put in task object instead? */
|
|
pp->tx_seq = ++handler->radio_tx_config.seq_num;
|
|
|
|
if(pktLLDsendPacket(task_object)) {
|
|
/* TODO: Deprecate this gear shift stuff. */
|
|
handler->tx_count++;
|
|
poll_hysteresis = PKT_RADIO_TASK_MANAGER_TX_HYSTERESIS;
|
|
poll_rate = PKT_RADIO_TASK_MANAGER_TX_RATE_MS;
|
|
/* Send Successfully enqueued.
|
|
* Unlike receive the task object is held by the TX until complete.
|
|
* It is then released in the TX thread release task. */
|
|
continue;
|
|
}
|
|
/* Send failed so release send packet object(s). */
|
|
pktReleaseSendQueue(pp);
|
|
break;
|
|
} /* End case PKT_RADIO_TX. */
|
|
|
|
case PKT_RADIO_RX_CLOSE: {
|
|
event_listener_t el;
|
|
event_source_t *esp;
|
|
thread_t *decoder = NULL;
|
|
switch(task_object->type) {
|
|
case MOD_AFSK: {
|
|
Si446x_disableReceive(radio);
|
|
esp = pktGetEventSource((AFSKDemodDriver *)handler->link_controller);
|
|
pktRegisterEventListener(esp, &el, USR_COMMAND_ACK, DEC_CLOSE_EXEC);
|
|
decoder = ((AFSKDemodDriver *)(handler->link_controller))->decoder_thd;
|
|
|
|
/* Send event to release AFSK resources and terminate thread. */
|
|
chEvtSignal(decoder, DEC_COMMAND_CLOSE);
|
|
|
|
/* Then release common services and thread heap. */
|
|
break;
|
|
}
|
|
|
|
case MOD_NONE:
|
|
case MOD_2FSK: {
|
|
break;
|
|
} /* End case DECODE_FSK. */
|
|
} /* End switch on link_type. */
|
|
if(decoder == NULL)
|
|
/* No decoder processed. */
|
|
break;
|
|
|
|
/* Wait for the decoder to stop. */
|
|
eventflags_t evt;
|
|
do {
|
|
chEvtWaitAny(USR_COMMAND_ACK);
|
|
|
|
/* Wait for correct event at source.
|
|
*/
|
|
evt = chEvtGetAndClearFlags(&el);
|
|
} while (evt != DEC_CLOSE_EXEC);
|
|
pktUnregisterEventListener(esp, &el);
|
|
|
|
/*
|
|
* Release decoder thread heap when it terminates.
|
|
*/
|
|
chThdWait(decoder);
|
|
|
|
/* Release packet services. */
|
|
pktIncomingBufferPoolRelease(handler);
|
|
pktCallbackManagerRelease(handler);
|
|
|
|
/*
|
|
* Signal close completed for this session.
|
|
* Any new open that is queued on the sempahore will be readied.
|
|
*/
|
|
chBSemSignal(&handler->close_sem);
|
|
break;
|
|
} /*end case close. */
|
|
|
|
case PKT_RADIO_TX_THREAD: {
|
|
/* Get thread exit code a free memory. */
|
|
msg_t send_msg = chThdWait(task_object->thread);
|
|
|
|
bool rxok = true;
|
|
/* If no transmissions pending then enable RX or shutdown. */
|
|
if(--handler->tx_count == 0) {
|
|
if(handler->rx_active) {
|
|
rxok = pktLLDresumeReceive(radio);
|
|
} else {
|
|
Si446x_shutdown(radio);
|
|
}
|
|
}
|
|
/* Unlock radio. */
|
|
pktReleaseRadio(radio);
|
|
|
|
if(send_msg != MSG_OK) {
|
|
if(send_msg == MSG_TIMEOUT) {
|
|
TRACE_ERROR("SI > Transmit timeout");
|
|
}
|
|
if(send_msg == MSG_RESET) {
|
|
TRACE_ERROR("SI > Transmit failed to start");
|
|
}
|
|
}
|
|
if(!rxok) {
|
|
TRACE_ERROR("SI > Receive failed to resume after transmit");
|
|
}
|
|
break;
|
|
} /* End case PKT_RADIO_TX_THREAD */
|
|
|
|
} /* End switch on command. */
|
|
/* Perform radio task callback if specified. */
|
|
if(task_object->callback != NULL)
|
|
/* Perform the callback. */
|
|
task_object->callback(task_object);
|
|
/* Return radio task object to free list. */
|
|
chFifoReturnObject(radio_queue, (radio_task_object_t *)task_object);
|
|
} /* End while should terminate(). */
|
|
chThdExit(MSG_OK);
|
|
}
|
|
|
|
|
|
thread_t *pktRadioManagerCreate(radio_unit_t radio) {
|
|
|
|
packet_svc_t *handler = pktGetServiceObject(radio);
|
|
|
|
chDbgAssert(handler != NULL, "invalid radio ID");
|
|
|
|
/* Create the radio manager name. */
|
|
chsnprintf(handler->rtask_name, sizeof(handler->rtask_name),
|
|
"%s%02i", PKT_RADIO_TASK_QUEUE_PREFIX, radio);
|
|
|
|
dyn_objects_fifo_t *the_radio_fifo =
|
|
chFactoryCreateObjectsFIFO(handler->rtask_name,
|
|
sizeof(radio_task_object_t),
|
|
RADIO_TASK_QUEUE_MAX, sizeof(msg_t));
|
|
|
|
chDbgAssert(the_radio_fifo != NULL, "unable to create radio task queue");
|
|
|
|
if(the_radio_fifo == NULL)
|
|
return NULL;
|
|
|
|
handler->the_radio_fifo = the_radio_fifo;
|
|
|
|
dbgPrintf(DBG_INFO, "PKT > radio manager thread created. FIFO @ 0x%x\r\n",
|
|
the_radio_fifo);
|
|
|
|
/* Start the task dispatcher thread. */
|
|
handler->radio_manager = chThdCreateFromHeap(NULL,
|
|
THD_WORKING_AREA_SIZE(PKT_RADIO_MANAGER_WA_SIZE),
|
|
handler->rtask_name,
|
|
NORMALPRIO - 10,
|
|
pktRadioManager,
|
|
handler);
|
|
|
|
chDbgAssert(handler->radio_manager != NULL,
|
|
"unable to create radio task thread");
|
|
|
|
if(handler->radio_manager == NULL) {
|
|
chFactoryReleaseObjectsFIFO(the_radio_fifo);
|
|
return NULL;
|
|
}
|
|
return handler->radio_manager;
|
|
}
|
|
|
|
/**
|
|
* TODO: This needs review. Is it robust enough?
|
|
*/
|
|
void pktRadioManagerRelease(radio_unit_t radio) {
|
|
packet_svc_t *handler = pktGetServiceObject(radio);
|
|
chThdTerminate(handler->radio_manager);
|
|
chThdWait(handler->radio_manager);
|
|
chFactoryReleaseObjectsFIFO(handler->the_radio_fifo);
|
|
}
|
|
|
|
/**
|
|
* @brief Get a radio command task object.
|
|
* @post A task object is returned ready for filling and submission.
|
|
*
|
|
* @param[in] radio radio unit ID.
|
|
* @param[in] timeout maximum time to wait for a task to be submitted.
|
|
* @param[in] rt pointer to a task object pointer.
|
|
*
|
|
* @return Status of the operation.
|
|
* @retval MSG_TIMEOUT an object could not be obtained within the timeout.
|
|
* @retval MSG_OK an object has been fetched.
|
|
*
|
|
* @api
|
|
*/
|
|
msg_t pktGetRadioTaskObject(radio_unit_t radio,
|
|
sysinterval_t timeout,
|
|
radio_task_object_t **rt) {
|
|
|
|
packet_svc_t *handler = pktGetServiceObject(radio);
|
|
|
|
chDbgAssert(handler != NULL, "invalid radio ID");
|
|
|
|
dyn_objects_fifo_t *task_fifo =
|
|
chFactoryFindObjectsFIFO(handler->rtask_name);
|
|
chDbgAssert(task_fifo != NULL, "unable to find radio task fifo");
|
|
|
|
objects_fifo_t *task_queue = chFactoryGetObjectsFIFO(task_fifo);
|
|
chDbgAssert(task_queue != NULL, "no objects fifo list");
|
|
|
|
*rt = chFifoTakeObjectTimeout(task_queue, TIME_MS2I(timeout));
|
|
|
|
if(*rt == NULL) {
|
|
/* Timeout waiting for object. */
|
|
/* Release find reference to the FIFO (decrease reference count). */
|
|
chFactoryReleaseObjectsFIFO(task_fifo);
|
|
return MSG_TIMEOUT;
|
|
}
|
|
(*rt)->handler = handler;
|
|
//(*rt)->radio_id = radio;
|
|
return MSG_OK;
|
|
}
|
|
|
|
/**
|
|
* @brief Submit a radio command to the task manager.
|
|
* @post A task object is created and submitted to the radio manager.
|
|
*
|
|
* @param[in] radio radio unit ID.
|
|
* @param[in] object radio task object to be submitted.
|
|
* @param[in] cb function to call with result (can be NULL).
|
|
*
|
|
* @api
|
|
*/
|
|
void pktSubmitRadioTask(radio_unit_t radio,
|
|
radio_task_object_t *object,
|
|
radio_task_cb_t cb) {
|
|
|
|
packet_svc_t *handler = pktGetServiceObject(radio);
|
|
chDbgAssert(handler != NULL, "invalid radio ID");
|
|
|
|
dyn_objects_fifo_t *task_fifo = handler->the_radio_fifo;
|
|
chDbgAssert(task_fifo != NULL, "no radio task fifo");
|
|
|
|
objects_fifo_t *task_queue = chFactoryGetObjectsFIFO(task_fifo);
|
|
chDbgAssert(task_queue != NULL, "no objects fifo list");
|
|
|
|
/* Populate the object with information from request. */
|
|
|
|
/* TODO: Put command information into queue object.
|
|
* Have to do this so that commands are not overwritten in handler.
|
|
*/
|
|
object->handler = handler;
|
|
object->callback = cb;
|
|
|
|
/*
|
|
* Submit the task to the queue.
|
|
* The task thread will process the request.
|
|
* The task object is returned to the free list.
|
|
* If a callback is specified it is called after the task object is freed.
|
|
*/
|
|
chFifoSendObject(task_queue, object);
|
|
}
|
|
|
|
/**
|
|
* @brief Called by transmit threads to schedule release after completing.
|
|
* @post A thread release task is posted to the radio manager queue.
|
|
*
|
|
* @param[in] radio radio unit ID.
|
|
* @param[in] thread thread reference.
|
|
*
|
|
* @api
|
|
*/
|
|
void pktSignalSendComplete(radio_task_object_t *rto,
|
|
thread_t *thread) {
|
|
|
|
packet_svc_t *handler = rto->handler;
|
|
|
|
radio_unit_t radio = handler->radio;
|
|
/* The handler and radio ID are set in returned object. */
|
|
rto->command = PKT_RADIO_TX_THREAD;
|
|
rto->thread = thread;
|
|
pktSubmitRadioTask(radio, rto, rto->callback);
|
|
}
|
|
|
|
/**
|
|
* @brief Called by transmit threads to schedule release after completing.
|
|
* @post A thread release task is posted to the radio manager queue.
|
|
*
|
|
* @param[in] radio radio unit ID.
|
|
* @param[in] thread thread reference.
|
|
*
|
|
* @api
|
|
*/
|
|
void pktScheduleThreadRelease(radio_unit_t radio,
|
|
thread_t *thread) {
|
|
|
|
packet_svc_t *handler = pktGetServiceObject(radio);
|
|
|
|
chDbgAssert(handler != NULL, "invalid radio ID");
|
|
|
|
radio_task_object_t *rto;
|
|
|
|
/* Block waiting for a task manager object. */
|
|
pktGetRadioTaskObject(radio, TIME_INFINITE, &rto);
|
|
|
|
/* The handler and radio ID are set in returned object. */
|
|
rto->command = PKT_RADIO_TX_THREAD;
|
|
rto->thread = thread;
|
|
pktSubmitRadioTask(radio, rto, NULL);
|
|
}
|
|
|
|
/**
|
|
* @brief Acquire exclusive access to radio.
|
|
* @notes returns when radio unit acquired.
|
|
*
|
|
* @param[in] radio radio unit ID.
|
|
* @param[in] timeout time to wait for acquisition.
|
|
*
|
|
* @return A message specifying the result.
|
|
* @retval MSG_OK if the radio has been successfully acquired.
|
|
* @retval MSG_TIMEOUT if the radio could not be acquired within specified time.
|
|
* @retval MSG_RESET if the radio can not be used due to a system abort.
|
|
*
|
|
* @api
|
|
*/
|
|
msg_t pktAcquireRadio(radio_unit_t radio, sysinterval_t timeout) {
|
|
packet_svc_t *handler = pktGetServiceObject(radio);
|
|
return chBSemWaitTimeout(&handler->radio_sem, timeout);
|
|
}
|
|
|
|
/**
|
|
* @brief Release exclusive access to radio.
|
|
* @notes returns when radio unit is released.
|
|
*
|
|
* @param[in] radio radio unit ID.
|
|
*
|
|
* @api
|
|
*/
|
|
void pktReleaseRadio(radio_unit_t radio) {
|
|
packet_svc_t *handler = pktGetServiceObject(radio);
|
|
chBSemSignal(&handler->radio_sem);
|
|
}
|
|
|
|
/**
|
|
* @brief Compute an operating frequency.
|
|
*
|
|
* @param[in] base_freq Radio base frequency.
|
|
* @param[in] step Radio channel step frequency.
|
|
* @param[in] chan Radio channel number.
|
|
*
|
|
* @api
|
|
*/
|
|
radio_freq_t pktComputeOperatingFrequency(radio_freq_t base_freq,
|
|
channel_hz_t step,
|
|
radio_ch_t chan) {
|
|
return base_freq + (step * chan);
|
|
}
|
|
|
|
/**
|
|
* @brief Send on radio.
|
|
* @notes This is the API interface to the radio LLD.
|
|
* @notes Currently just map directly to 446x driver.
|
|
* @notes In future would implement a lookup and VMT to access radio methods.
|
|
*
|
|
* @param[in] radio radio unit ID.
|
|
*
|
|
* @notapi
|
|
*/
|
|
bool pktLLDsendPacket(radio_task_object_t *rto) {
|
|
switch(rto->type) {
|
|
case MOD_2FSK:
|
|
Si446x_blocSend2FSK(rto);
|
|
break;
|
|
|
|
case MOD_AFSK:
|
|
Si446x_blocSendAFSK(rto);
|
|
break;
|
|
|
|
case MOD_NONE:
|
|
return false;
|
|
} /* End switch on task_object->type. */
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* @brief Resume reception paused by transmit task.
|
|
* @notes This is the API interface to the radio LLD.
|
|
* @notes Currently just map directly to 446x driver.
|
|
* @notes In future would implement a lookup and VMT to access radio methods.
|
|
*
|
|
* @param[in] radio radio unit ID.
|
|
*
|
|
* @return status of the operation
|
|
* @retval true operation succeeded.
|
|
* retval false operation failed.
|
|
*
|
|
* @notapi
|
|
*/
|
|
bool pktLLDresumeReceive(radio_unit_t radio) {
|
|
packet_svc_t *handler = pktGetServiceObject(radio);
|
|
|
|
chDbgAssert(handler != NULL, "invalid radio ID");
|
|
|
|
radio_freq_t freq = handler->radio_rx_config.base_frequency;
|
|
channel_hz_t step = handler->radio_rx_config.step_hz;
|
|
radio_ch_t chan = handler->radio_rx_config.channel;
|
|
radio_squelch_t rssi = handler->radio_rx_config.squelch;
|
|
mod_t mod = handler->radio_rx_config.type;
|
|
return Si4464_resumeReceive(radio, freq, step, chan, rssi, mod);
|
|
}
|
|
|
|
/** @} */
|