/***************************************************************************//**
* @file
* @brief This is the base test application. It handles basic RAIL configuration
* as well as transmit, receive, and various debug modes.
*******************************************************************************
* # License
* Copyright 2020 Silicon Laboratories Inc. www.silabs.com
*******************************************************************************
*
* SPDX-License-Identifier: Zlib
*
* The licensor of this software is Silicon Laboratories Inc.
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
* arising from the use of this software.
*
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software
* in a product, an acknowledgment in the product documentation would be
* appreciated but is not required.
* 2. Altered source versions must be plainly marked as such, and must not be
* misrepresented as being the original software.
* 3. This notice may not be removed or altered from any source distribution.
*
******************************************************************************/
#include
#include
#include
#include "rail.h"
#include "rail_ieee802154.h"
#include "rail_mfm.h"
#include "rail_zwave.h"
#include "sl_rail_util_init.h"
#include "sl_rail_util_protocol.h"
#include "sl_rail_util_ant_div.h"
#include "buffer_pool_allocator_config.h"
#include "em_chip.h"
#include "em_core.h"
#include "em_rmu.h"
#include "em_emu.h"
#include "gpiointerrupt.h"
#include "response_print.h"
#include "buffer_pool_allocator.h"
#include "circular_queue.h"
#include "app_common.h"
#include "app_trx.h"
#ifdef RAILAPP_RMR
#include "railapp_rmr.h"
#endif
#if defined(SL_COMPONENT_CATALOG_PRESENT)
#include "sl_component_catalog.h"
#endif
#if defined(SL_CATALOG_POWER_MANAGER_PRESENT)
#include "sl_power_manager.h"
#endif
#include "sl_rail_test_config.h"
#ifndef STATIC_ASSERT
#ifdef __ICCARM__
#define STATIC_ASSERT(__condition, __errorstr) \
static_assert(__condition, __errorstr)
#elif defined(__GNUC__)
#define STATIC_ASSERT(__condition, __errorstr) \
_Static_assert(__condition, __errorstr)
#else
#define STATIC_ASSERT(__condition, __errorstr)
#endif
#endif//STATIC_ASSERT
STATIC_ASSERT(sizeof(RailAppEvent_t) <= 36,
"Adjust BUFFER_POOL_ALLOCATOR_BUFFER_SIZE_MAX per sizeof(RailAppEvent_t) growth");
// Includes for Silicon Labs-only, internal testing
#ifdef RPC_TESTING
#include "vs_rpc.h"
#else
#define RPC_Server_Init()
#define RPC_Server_Tick()
#endif
// Add a way to override the default setting for printingEnabled
#if defined(SL_RAIL_UTIL_PRINTING_DEFAULT) && !(SL_RAIL_UTIL_PRINTING_DEFAULT)
#define RAIL_PRINTING_DEFAULT_BOOL false
#else
#define RAIL_PRINTING_DEFAULT_BOOL true
#endif
// Add a way to override the default setting for skipCalibrations
#if defined(SL_RAIL_UTIL_SKIP_CALIBRATIONS_DEFAULT) && (SL_RAIL_UTIL_SKIP_CALIBRATIONS_DEFAULT)
#define RAIL_SKIP_CALIBRATIONS_BOOL true
#else
#define RAIL_SKIP_CALIBRATIONS_BOOL false
#endif
// External control and status variables
Counters_t counters = { 0 };
bool receiveModeEnabled = false;
#if SL_RAIL_UTIL_INIT_INST0_ENABLE && SL_RAIL_UTIL_INIT_TRANSITIONS_INST0_ENABLE
RAIL_RadioState_t rxSuccessTransition = SL_RAIL_UTIL_INIT_TRANSITION_INST0_RX_SUCCESS;
#else
RAIL_RadioState_t rxSuccessTransition = RAIL_RF_STATE_IDLE;
#endif
uint8_t logLevel = PERIPHERAL_ENABLE | ASYNC_RESPONSE;
int32_t txCount = 0;
uint32_t continuousTransferPeriod = SL_RAIL_TEST_CONTINUOUS_TRANSFER_PERIOD;
bool enableRandomTxDelay = false;
uint32_t txAfterRxDelay = 0;
int32_t txCancelDelay = -1;
RAIL_StopMode_t txCancelMode = RAIL_STOP_MODES_NONE; // Use RAIL_Idle()
bool skipCalibrations = RAIL_SKIP_CALIBRATIONS_BOOL;
bool afterRxCancelAck = false;
bool afterRxUseTxBufferForAck = false;
bool schRxStopOnRxEvent = false;
uint32_t rssiDoneCount = 0; // HW rssi averaging
float averageRssi = -128;
bool printTxAck = false;
RAIL_VerifyConfig_t configVerify = { 0 };
int16_t lqiOffset = 0;
const char buildDateTime[] = __DATE__ " " __TIME__;
bool rxHeld = false;
volatile bool rxProcessHeld = false;
volatile uint32_t packetsHeld = 0U;
// Internal app state variables
static uint32_t startTransmitCounter = 0;
uint32_t internalTransmitCounter = 0;
volatile uint32_t ccaSuccesses = 0;
volatile uint32_t sentAckPackets = 0;
// Globals accessed in an interrupt context declared volatile.
static volatile bool receivingPacket = false;
volatile RAIL_Events_t lastTxStatus = 0;
volatile RAIL_Events_t lastTxAckStatus = 0;
volatile uint32_t failPackets = 0;
volatile uint32_t failAckPackets = 0;
static volatile bool packetTx = false;
static volatile bool finishTxSequence = false;
static volatile bool finishTxAckSequence = false;
RAIL_ScheduleTxConfig_t nextPacketTxTime = {
0,
RAIL_TIME_ABSOLUTE,
RAIL_SCHEDULED_TX_DURING_RX_POSTPONE_TX
};
Queue_t railAppEventQueue;
volatile uint32_t eventsMissed = 0U;
static RAIL_Time_t txStartTime;
// Variable which holds the receive frequency offset for the period of time
// between when the frequency offset is measured (in either the
// RAIL_EVENT_RX_SYNC1_DETECT event or the RAIL_EVENT_RX_SYNC2_DETECT event)
// until reception of the packet is completed or aborted.
RAIL_FrequencyOffset_t rxFreqOffset = RAIL_FREQUENCY_OFFSET_INVALID;
// Variable which contains data of the most recently
// received beam packet, and a bool to specify whether
// or not one was received.
ZWaveBeamData_t beamData = {
.channelIndex = RAIL_CHANNEL_HOPPING_INVALID_INDEX
};
volatile bool beamReceived = false;
static volatile uint32_t railTimerExpireTime = 0;
static volatile uint32_t railTimerConfigExpireTime = 0;
static volatile bool railTimerExpired = false;
static volatile bool calibrateRadio = false;
bool volatile newTxError = false;
static volatile bool rxAckTimeout = false;
static volatile uint32_t ackTimeoutDuration = 0;
RAIL_Events_t enablePrintEvents = RAIL_EVENTS_NONE;
bool printRxErrorPackets = false;
bool printRxFreqOffsetData = false;
bool printingEnabled = RAIL_PRINTING_DEFAULT_BOOL;
// Names of RAIL_EVENT defines. This should align with rail_types.h
const char* eventNames[] = RAIL_EVENT_STRINGS;
uint8_t numRailEvents = COUNTOF(eventNames);
// Channel Hopping configuration structures
#if RAIL_FEAT_CHANNEL_HOPPING
uint32_t channelHoppingBufferSpace[CHANNEL_HOPPING_BUFFER_SIZE];
uint32_t *channelHoppingBuffer = channelHoppingBufferSpace;
#endif
// Channel Variable
uint16_t channel = 0;
// Generic
RAIL_Handle_t railHandle;
// Make default packet work with the length-decoding schemes of 802.15.4, BLE,
// and Z-Wave
uint8_t txData[SL_RAIL_TEST_MAX_PACKET_LENGTH] = {
0x0F, 0x0E, 0x11, 0x22, 0x33, 0x44, 0x55, 0x0F,
0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE,
};
uint16_t txDataLen = 16;
static union {
// Used to align this buffer as needed
RAIL_FIFO_ALIGNMENT_TYPE align[SL_RAIL_TEST_TX_BUFFER_SIZE / RAIL_FIFO_ALIGNMENT];
uint8_t fifo[SL_RAIL_TEST_TX_BUFFER_SIZE];
} txFifo;
uint8_t ackData[RAIL_AUTOACK_MAX_LENGTH] = {
0x0F, 0x0E, 0xF1, 0xE2, 0xD3, 0xC4, 0xB5, 0x0F,
0x97, 0x88, 0x79, 0x6A, 0x5B, 0x4C, 0x3D, 0x2E,
};
uint8_t ackDataLen = 16;
// Static RAIL callbacks
static void RAILCb_RssiAverageDone(RAIL_Handle_t railHandle);
uint8_t RAILCb_ConvertLqi(uint8_t lqi, int8_t rssi);
static bool RAILCb_QueueOverflow(const Queue_t *queue, void *data);
// Structures that hold default TX & RX Options
RAIL_TxOptions_t txOptions = RAIL_TX_OPTIONS_DEFAULT;
RAIL_RxOptions_t rxOptions = RAIL_RX_OPTIONS_DEFAULT;
// Data Management
RAIL_DataConfig_t railDataConfig = {
.txSource = SL_RAIL_UTIL_INIT_DATA_FORMAT_INST0_TX_SOURCE,
.rxSource = SL_RAIL_UTIL_INIT_DATA_FORMAT_INST0_RX_SOURCE,
.txMethod = SL_RAIL_UTIL_INIT_DATA_FORMAT_INST0_TX_MODE,
.rxMethod = SL_RAIL_UTIL_INIT_DATA_FORMAT_INST0_RX_MODE,
};
// Called during main loop
void sendPacketIfPending(void);
void finishTxSequenceIfPending(void);
void changeAppModeIfPending(void);
void printNewTxError(void);
void checkTimerExpiration(void);
void processPendingCalibrations(void);
void printAckTimeout(void);
static void heldRxProcess(void)
{
if (!rxProcessHeld) {
return;
}
CORE_DECLARE_IRQ_STATE;
CORE_ENTER_CRITICAL();
uint32_t packetsHeldLocal = packetsHeld;
if (packetsHeldLocal == 0) {
rxProcessHeld = false;
}
RAIL_RxPacketHandle_t packetHandle
= processRxPacket(railHandle, RAIL_RX_PACKET_HANDLE_OLDEST_COMPLETE);
CORE_EXIT_CRITICAL();
if (packetHandle == RAIL_RX_PACKET_HANDLE_INVALID) {
if (packetsHeldLocal > 0) {
responsePrintError("heldRxProcess", 0xEF, "Unexpected invalid packetHandle");
}
} else {
RAIL_ReleaseRxPacket(railHandle, packetHandle);
CORE_ENTER_CRITICAL();
packetsHeld--;
CORE_EXIT_CRITICAL();
if (packetsHeldLocal == 0) {
responsePrintError("heldRxProcess", 0xEF, "Unexpected valid packetHandle");
}
}
}
// Copy src bytes into txData array starting at offset, and update txDataLength.
static void changeTxPayload(uint32_t offset,
uint8_t *src,
uint16_t srcLen,
uint16_t pktLen)
{
if (offset + srcLen <= sizeof(txData)) { // be sure it fits
memcpy(&txData[offset], &src[0], srcLen);
txDataLen = pktLen;
} else {
responsePrintError("changeTxPayload",
0xF0,
"New packet data overflows the txData buffer size.");
while (1) ;
}
}
// Function called from sl_system_init before the main super loop.
void sl_rail_test_internal_app_init(void)
{
// Get RAIL handle.
railHandle = sl_rail_util_get_handle(SL_RAIL_UTIL_HANDLE_INST0);
// Get proprietary channel config index, if support enabled.
if (SL_RAIL_UTIL_INIT_RADIO_CONFIG_SUPPORT_INST0_ENABLE) {
configIndex = SL_RAIL_UTIL_INIT_PROTOCOL_PROPRIETARY_INST0_INDEX;
}
// Initialize the RPC Server
RPC_Server_Init();
// Grab the reset cause
uint32_t resetCause = RMU_ResetCauseGet();
RMU_ResetCauseClear(); // So resetCause is rational and not an accumulated mess
// Release GPIOs that were held by EM4h to ensure proper startup
EMU_UnlatchPinRetention();
// Initialize hardware for application
appHalInit();
// Initialize txOptions & rxOptions
sl_rail_util_ant_div_init_tx_options(&txOptions);
sl_rail_util_ant_div_init_rx_options(&rxOptions);
// Make sure the response printer mirrors the default printingEnabled state
responsePrintEnable(printingEnabled);
// Print app initialization information.
RAILTEST_PRINTF("\n");
responsePrint("reset", "App:%s,Built:%s", SL_RAIL_TEST_APP_NAME, buildDateTime);
// Set TX FIFO, and verify that the size is correct
uint16_t fifoSize = RAIL_SetTxFifo(railHandle, txFifo.fifo, 0, SL_RAIL_TEST_TX_BUFFER_SIZE);
if (fifoSize != SL_RAIL_TEST_TX_BUFFER_SIZE) {
while (1) ;
}
(void) RAIL_GetChannel(railHandle, &channel);
// Register an LQI conversion callback.
RAIL_ConvertLqi(railHandle, &RAILCb_ConvertLqi);
RAIL_ConfigRxOptions(railHandle, RAIL_RX_OPTIONS_ALL, rxOptions);
// Initialize the queue we use for tracking packets
if (!queueInit(&railAppEventQueue, BUFFER_POOL_ALLOCATOR_POOL_SIZE)) {
while (1) ;
}
queueOverflow(&railAppEventQueue, &RAILCb_QueueOverflow);
#ifdef _SILICON_LABS_32B_SERIES_1
if (resetCause & RMU_RSTCAUSE_EM4RST) {
responsePrint("sleepWoke", "EM:4%c,SerialWakeup:No,RfSensed:%s",
(((EMU->EM4CTRL & EMU_EM4CTRL_EM4STATE)
== EMU_EM4CTRL_EM4STATE_EM4S) ? 's' : 'h'),
RAIL_IsRfSensed(railHandle) ? "Yes" : "No");
// Always turn off RfSense when waking back up from EM4
(void) RAIL_StartRfSense(railHandle, RAIL_RFSENSE_OFF, 0, NULL);
}
#elif (_SILICON_LABS_32B_SERIES_2_CONFIG == 2)
if (resetCause & EMU_RSTCAUSE_EM4) {
responsePrint("sleepWoke", "EM:4s,SerialWakeup:No,RfSensed:%s",
RAIL_IsRfSensed(railHandle) ? "Yes" : "No");
// Always turn off RfSense when waking back up from EM4
(void) RAIL_StartRfSense(railHandle, RAIL_RFSENSE_OFF, 0, NULL);
}
#else
(void) resetCause;
#endif
// Fill out the default TX packet with a useful pattern so it's not
// all zeros if user extends the TX length.
for (unsigned int i = txDataLen; i < sizeof(txData); i++) {
txData[i] = i;
}
// Change the default TX packet payload to be protocol-specific.
if (SL_RAIL_UTIL_PROTOCOL_IS_IEEE802154_2G4(SL_RAIL_UTIL_INIT_PROTOCOL_INST0_DEFAULT)) {
// Default 802.15.4 2.4 GHz packet.
uint8_t changes[] = { 0x0c, 0x63, 0x88, 0xbe, 0xff, 0xff, 0xff, 0xff,
0x11, 0x22, 0x04 };
// Change 11 bytes at offset 0 of txData, and new packet length is 11.
changeTxPayload(0, &changes[0], sizeof(changes), 11);
} else if (SL_RAIL_UTIL_PROTOCOL_IS_IEEE802154_GB868(SL_RAIL_UTIL_INIT_PROTOCOL_INST0_DEFAULT)) {
// Default 802.15.4 2.4 GB868 packet.
uint8_t changes[] = { 0x18, 0x28 };
// Change 2 bytes at offset 0 of txData, and new packet length is 20.
changeTxPayload(0, &changes[0], sizeof(changes), 20);
} else if (SL_RAIL_UTIL_PROTOCOL_IS_ZWAVE(SL_RAIL_UTIL_INIT_PROTOCOL_INST0_DEFAULT)) {
// Default Z-Wave packet.
uint8_t changes[] = { 0x20 };
// Change 1 byte at offset 7 of txData, and new packet length is 200.
changeTxPayload(7, &changes[0], sizeof(changes), 200);
} else {
// Use the default packet that already has length-decoding schemes for
// 802.15.4, BLE, and Z-Wave.
}
// Initialize autoack data
RAIL_WriteAutoAckFifo(railHandle, ackData, ackDataLen);
// RX isn't validated yet so lets not go into receive just yet
RAIL_StartRx(railHandle, channel, NULL); // Start in receive mode
receiveModeEnabled = true;
}
// Function called from sl_system_process_action within the main super loop.
void sl_rail_test_internal_app_process_action(void)
{
RPC_Server_Tick();
// Change app mode first so that any new actions can take effect this loop
changeAppModeIfPending();
rfSensedCheck();
sendPacketIfPending();
printNewTxError();
finishTxSequenceIfPending();
printRailAppEvents();
checkTimerExpiration();
processPendingCalibrations();
printAckTimeout();
heldRxProcess();
}
/******************************************************************************
* RAIL Callback Implementation
*****************************************************************************/
// Override weak function called by callback sli_rail_util_on_rf_ready.
void sl_rail_util_on_rf_ready(RAIL_Handle_t railHandle)
{
(void)railHandle;
LedSet(0);
LedSet(1);
}
static bool RAILCb_QueueOverflow(const Queue_t *queue, void *data)
{
(void)queue;
// The event queue is overflowing, and I want to overwrite the oldest event
// pointer (in favor of the newer event information), so I need to free
// the memory associated with that old, event pointer here.
memoryFree(data);
return true; // allow the overwrite
}
void RAILCb_SwTimerExpired(RAIL_Handle_t railHandle)
{
railTimerExpireTime = RAIL_GetTime();
railTimerConfigExpireTime = RAIL_GetTimer(railHandle);
railTimerExpired = true;
}
void RAILCb_TimerExpired(RAIL_Handle_t railHandle)
{
if (inAppMode(NONE, NULL)) {
if (abortRxDelay != 0) {
RAIL_Idle(railHandle, RAIL_IDLE_ABORT, true);
} else {
railTimerExpireTime = RAIL_GetTime();
railTimerConfigExpireTime = RAIL_GetTimer(railHandle);
railTimerExpired = true;
}
} else if (currentAppMode() == PER) {
#if defined (SL_RAIL_TEST_PER_PORT) && defined(SL_RAIL_TEST_PER_PIN)
GPIO_PinOutToggle(SL_RAIL_TEST_PER_PORT, SL_RAIL_TEST_PER_PIN);
counters.perTriggers += GPIO_PinOutGet(SL_RAIL_TEST_PER_PORT, SL_RAIL_TEST_PER_PIN);
perCount -= GPIO_PinOutGet(SL_RAIL_TEST_PER_PORT, SL_RAIL_TEST_PER_PIN);
#endif // SL_RAIL_TEST_PER_PORT && SL_RAIL_TEST_PER_PIN
if (perCount < 1) {
#if defined (SL_RAIL_TEST_PER_PORT) && defined(SL_RAIL_TEST_PER_PIN)
GPIO_PinOutClear(SL_RAIL_TEST_PER_PORT, SL_RAIL_TEST_PER_PIN);
#endif // SL_RAIL_TEST_PER_PORT && SL_RAIL_TEST_PER_PIN
enableAppMode(PER, false, NULL);
} else {
RAIL_SetTimer(railHandle, perDelay, RAIL_TIME_DELAY, &RAILCb_TimerExpired);
}
} else {
pendPacketTx();
}
}
uint8_t RAILCb_ConvertLqi(uint8_t lqi, int8_t rssi)
{
(void)rssi;
// Put any custom LQI conversion code here.
// In this application, lqiOffset is between -255 and 255 but LQI is uint8_t:
int16_t newLqi = lqiOffset + lqi;
if (newLqi < 0) {
newLqi = 0; // uint8_t min
} else if (newLqi > 0xFF) {
newLqi = 0xFF; // uint8_t max
}
return (uint8_t)newLqi;
}
static void RAILCb_RssiAverageDone(RAIL_Handle_t railHandle)
{
void *rssiHandle = memoryAllocate(sizeof(RailAppEvent_t));
RailAppEvent_t *rssi = (RailAppEvent_t *)memoryPtrFromHandle(rssiHandle);
if (rssi == NULL) {
eventsMissed++;
return;
}
rssi->type = AVERAGE_RSSI;
rssi->rssi.rssi = RAIL_GetAverageRssi(railHandle);
queueAdd(&railAppEventQueue, rssiHandle);
rssiDoneCount++;
}
// Override weak function called by callback RAILCb_AssertFailed.
void sl_rail_util_on_assert_failed(RAIL_Handle_t railHandle, uint32_t errorCode)
{
(void)railHandle;
static const char* railErrorMessages[] = RAIL_ASSERT_ERROR_MESSAGES;
const char *errorMessage = "Unknown";
// If this error code is within the range of known error messages then use
// the appropriate error message.
if (errorCode < (sizeof(railErrorMessages) / sizeof(char*))) {
errorMessage = railErrorMessages[errorCode];
}
// Print a message about the assert that triggered
responsePrint("assert",
"code:%d,message:%s",
errorCode,
errorMessage);
// Reset the chip since an assert is a fatal error
NVIC_SystemReset();
}
// Override weak function called by callback sli_rail_util_on_event.
void sl_rail_util_on_event(RAIL_Handle_t railHandle, RAIL_Events_t events)
{
enqueueEvents(events);
if (events & RAIL_EVENT_CAL_NEEDED) {
calibrateRadio = true;
}
if (events & RAIL_EVENT_RSSI_AVERAGE_DONE) {
RAILCb_RssiAverageDone(railHandle);
}
// RX Events
if (events & RAIL_EVENT_RX_TIMING_DETECT) {
counters.timingDetect++;
}
if (events & RAIL_EVENT_RX_TIMING_LOST) {
counters.timingLost++;
}
if (events & RAIL_EVENT_RX_PREAMBLE_LOST) {
counters.preambleLost++;
}
if (events & RAIL_EVENT_RX_PREAMBLE_DETECT) {
counters.preambleDetect++;
}
if (events & (RAIL_EVENT_RX_SYNC1_DETECT | RAIL_EVENT_RX_SYNC2_DETECT)) {
receivingPacket = true;
counters.syncDetect++;
rxFifoPrep();
if (printRxFreqOffsetData) {
rxFreqOffset = RAIL_GetRxFreqOffset(railHandle);
}
if (abortRxDelay != 0) {
RAIL_SetTimer(railHandle, abortRxDelay, RAIL_TIME_DELAY, &RAILCb_TimerExpired);
}
}
if (events & RAIL_EVENT_IEEE802154_DATA_REQUEST_COMMAND) {
if (RAIL_IEEE802154_IsEnabled(railHandle)) {
counters.dataRequests++;
RAILCb_IEEE802154_DataRequestCommand(railHandle);
}
}
#if RAIL_FEAT_ZWAVE_SUPPORTED
if (events & RAIL_EVENT_ZWAVE_BEAM) {
if (RAIL_ZWAVE_IsEnabled(railHandle)) {
counters.rxBeams++;
RAILCb_ZWAVE_BeamFrame(railHandle);
}
}
#endif //RAIL_FEAT_ZWAVE_SUPPORTED
if (events & RAIL_EVENT_RX_FIFO_ALMOST_FULL) {
RAILCb_RxFifoAlmostFull(railHandle);
}
if (events & RAIL_EVENT_RX_FIFO_FULL) {
if (rxHeld) {
rxProcessHeld = true; // Try to avoid overflow by processing held packets
}
counters.rxFifoFull++;
}
if (events & (RAIL_EVENT_RX_FIFO_OVERFLOW
| RAIL_EVENT_RX_ADDRESS_FILTERED
| RAIL_EVENT_RX_PACKET_ABORTED
| RAIL_EVENT_RX_FRAME_ERROR
| RAIL_EVENT_RX_PACKET_RECEIVED)) {
// All of the above events cause a packet to not be received
receivingPacket = false;
if (events & RAIL_EVENT_RX_PACKET_RECEIVED) {
RAILCb_RxPacketReceived(railHandle);
}
if (rxFifoManual && (railDataConfig.rxMethod != PACKET_MODE)) {
(void)RAIL_HoldRxPacket(railHandle);
}
if (events & RAIL_EVENT_RX_FIFO_OVERFLOW) {
counters.rxOfEvent++;
RAILCb_RxPacketAborted(railHandle);
}
if (events & RAIL_EVENT_RX_ADDRESS_FILTERED) {
counters.addrFilterEvent++;
RAILCb_RxPacketAborted(railHandle);
}
if (events & RAIL_EVENT_RX_PACKET_ABORTED) {
counters.rxFail++;
RAILCb_RxPacketAborted(railHandle);
}
if (events & RAIL_EVENT_RX_FRAME_ERROR) {
counters.frameError++;
RAILCb_RxPacketAborted(railHandle);
}
}
if (events & RAIL_EVENT_RX_ACK_TIMEOUT) {
counters.ackTimeout++;
rxAckTimeout = true;
//TODO: packetTime depends on txTimePosition;
// this code assumes default position (PACKET_END).
ackTimeoutDuration = RAIL_GetTime()
- previousTxAppendedInfo.timeSent.packetTime;
}
// End scheduled receive mode if an appropriate end or error event is received
if ((events & (RAIL_EVENT_RX_SCHEDULED_RX_END
| RAIL_EVENT_RX_SCHEDULED_RX_MISSED))
|| ((schRxStopOnRxEvent && inAppMode(RX_SCHEDULED, NULL))
&& (events & (RAIL_EVENT_RX_ADDRESS_FILTERED
| RAIL_EVENT_RX_PACKET_ABORTED
| RAIL_EVENT_RX_FIFO_OVERFLOW
| RAIL_EVENT_RX_FRAME_ERROR)))) {
// N.B. RAIL_EVENT_RX_PACKET_RECEIVED was handled in its callback already
enableAppMode(RX_SCHEDULED, false, NULL);
}
// TX Events
if (events & RAIL_EVENT_TX_START_CCA) {
counters.lbtStartCca++;
}
if (events & RAIL_EVENT_TX_CCA_RETRY) {
counters.lbtRetry++;
}
if (events & RAIL_EVENT_TX_CHANNEL_CLEAR) {
counters.lbtSuccess++;
ccaSuccesses++;
if ((txOptions & RAIL_TX_OPTION_CCA_ONLY) != 0U) {
lastTxStatus = events;
newTxError = true; // This is a 'pretend error'; see printNewTxError()
// This doesn't counters.userTx++;
//@TODO: Should we instead initiate an immediate transmit here?
scheduleNextTx();
}
}
if (events & RAIL_EVENT_MFM_TX_BUFFER_DONE) {
if (RAIL_MFM_IsEnabled(railHandle)) {
counters.userTx++;
}
}
if (events & RAIL_EVENT_TX_STARTED) {
counters.userTxStarted++;
(void) RAIL_GetTxTimePreambleStart(railHandle, RAIL_TX_STARTED_BYTES,
&txStartTime);
}
if (events & (RAIL_EVENT_TX_ABORTED
| RAIL_EVENT_TX_BLOCKED
| RAIL_EVENT_TX_UNDERFLOW
| RAIL_EVENT_TX_CHANNEL_BUSY
| RAIL_EVENT_TX_SCHEDULED_TX_MISSED)) {
lastTxStatus = events;
newTxError = true;
failPackets++;
scheduleNextTx();
// Increment counters for TX events
if (events & RAIL_EVENT_TX_ABORTED) {
counters.userTxAborted++;
}
if (events & RAIL_EVENT_TX_BLOCKED) {
counters.userTxBlocked++;
}
if (events & RAIL_EVENT_TX_UNDERFLOW) {
counters.userTxUnderflow++;
}
}
// Put this here too so that we do these things twice
// in the case that an ack and a non ack failure have
// been queued up
if (events & (RAIL_EVENT_TXACK_ABORTED
| RAIL_EVENT_TXACK_BLOCKED
| RAIL_EVENT_TXACK_UNDERFLOW)) {
lastTxAckStatus = events;
failAckPackets++;
pendFinishTxAckSequence();
// Increment counters for TXACK events
if (events & RAIL_EVENT_TXACK_ABORTED) {
counters.ackTxAborted++;
}
if (events & RAIL_EVENT_TXACK_BLOCKED) {
counters.ackTxBlocked++;
}
if (events & RAIL_EVENT_TXACK_UNDERFLOW) {
counters.ackTxUnderflow++;
}
}
if (events & RAIL_EVENT_TX_FIFO_ALMOST_EMPTY) {
RAILCb_TxFifoAlmostEmpty(railHandle);
}
if (events & RAIL_EVENT_TX_PACKET_SENT) {
counters.userTx++;
RAILCb_TxPacketSent(railHandle, false);
}
if (events & RAIL_EVENT_TXACK_PACKET_SENT) {
counters.ackTx++;
RAILCb_TxPacketSent(railHandle, true);
}
if (events & RAIL_EVENT_RX_CHANNEL_HOPPING_COMPLETE) {
RAILCb_RxChannelHoppingComplete(railHandle);
}
if (events & RAIL_EVENT_PA_PROTECTION) {
counters.paProtect++;
}
}
volatile bool allowPowerManagerSleep = false;
// This app-level function overrides a weak power manager implementation and
// gets called in sl_power_manager_handler.c.
// By default prevent the RAILtest App from sleeping and
// keep CLI responsive.
bool app_is_ok_to_sleep(void)
{
return allowPowerManagerSleep;
}
/******************************************************************************
* Application Helper Functions
*****************************************************************************/
void processPendingCalibrations(void)
{
// Only calibrate the radio when not currently transmitting or in a
// transmit mode. Also don't try to calibrate while receiving a packet
bool calsInMode = inAppMode(NONE, NULL);
if (calibrateRadio && calsInMode && !skipCalibrations && !receivingPacket) {
RAIL_CalMask_t pendingCals = RAIL_GetPendingCal(railHandle);
counters.calibrations++;
calibrateRadio = false;
// Perform the necessary calibrations and don't save the results
if (pendingCals & RAIL_CAL_TEMP_VCO) {
RAIL_CalibrateTemp(railHandle);
}
// Disable the radio if we have to do IRCAL
if (pendingCals & RAIL_CAL_ONETIME_IRCAL) {
RAIL_Idle(railHandle, RAIL_IDLE_ABORT, false);
RAIL_CalibrateIr(railHandle, NULL);
if (receiveModeEnabled) {
RAIL_StartRx(railHandle, channel, NULL);
}
}
}
}
RAIL_FrequencyOffset_t getRxFreqOffset(void)
{
RAIL_FrequencyOffset_t retVal = rxFreqOffset;
rxFreqOffset = RAIL_FREQUENCY_OFFSET_INVALID;
return retVal;
}
void checkTimerExpiration(void)
{
if (railTimerExpired) {
railTimerExpired = false;
uint32_t paramRailTimerExpireTime = railTimerExpireTime;
responsePrint("timerCb",
"TimerExpiredCallback:%u,ConfiguredExpireTime:%u",
paramRailTimerExpireTime,
railTimerConfigExpireTime);
}
}
void printNewTxError(void)
{
RAIL_Events_t paramLastTxStatus;
if (newTxError) {
newTxError = false;
if (lastTxStatus & RAIL_EVENT_TX_UNDERFLOW) {
if (logLevel & ASYNC_RESPONSE) {
paramLastTxStatus = lastTxStatus;
responsePrint("txPacket",
"txStatus:Error,"
"errorReason:Tx underflow or abort,"
"errorCode:0x%x%08x",
(uint32_t)(paramLastTxStatus >> 32),
(uint32_t)(paramLastTxStatus));
}
}
if (lastTxStatus & RAIL_EVENT_TX_CHANNEL_BUSY) {
if (logLevel & ASYNC_RESPONSE) {
paramLastTxStatus = lastTxStatus;
responsePrint("txPacket",
"txStatus:Error,"
"errorReason:Tx channel busy,"
"errorCode:0x%x%08x",
(uint32_t)(paramLastTxStatus >> 32),
(uint32_t)(paramLastTxStatus));
}
counters.txChannelBusy++;
}
if (lastTxStatus & RAIL_EVENT_TX_CHANNEL_CLEAR) {
if (logLevel & ASYNC_RESPONSE) {
responsePrint("txPacket",
"txStatus:ChannelClear");
}
}
}
}
void printAckTimeout(void)
{
if (rxAckTimeout) {
rxAckTimeout = false;
responsePrint("rxAckTimeout",
"ackTimeoutDuration:%d",
ackTimeoutDuration);
}
}
void changeChannel(uint32_t i)
{
channel = i;
updateGraphics();
// Apply the new channel immediately if you are in receive already
if (receiveModeEnabled
|| ((RAIL_GetRadioState(railHandle) & RAIL_RF_STATE_RX) != 0U)) {
RAIL_Status_t status = RAIL_StartRx(railHandle, channel, NULL);
// Lock up if changing the channel failed since calls to this are supposed
// to be checked for errors
if (status != RAIL_STATUS_NO_ERROR) {
responsePrintError("changeChannel",
0xF0,
"FATAL, call to RAIL_StartRx() failed (%u)", status);
while (1) ;
}
}
}
void pendPacketTx(void)
{
packetTx = true;
}
RAIL_Status_t chooseTxType(void)
{
// Invalidate the previous TX's start time
txStartTime = 0U;
if (currentAppMode() == TX_SCHEDULED || currentAppMode() == SCHTX_AFTER_RX) {
return RAIL_StartScheduledTx(railHandle, channel, txOptions, &nextPacketTxTime, NULL);
} else if (txType == TX_TYPE_LBT) {
return RAIL_StartCcaLbtTx(railHandle, channel, txOptions, lbtConfig, NULL);
} else if (txType == TX_TYPE_CSMA) {
return RAIL_StartCcaCsmaTx(railHandle, channel, txOptions, csmaConfig, NULL);
} else {
return RAIL_StartTx(railHandle, channel, txOptions, NULL);
}
}
void sendPacketIfPending(void)
{
if (packetTx) {
packetTx = false;
uint8_t txStatus;
// Don't decrement in continuous mode
if (currentAppMode() != TX_CONTINUOUS) {
txCount--;
}
// Generate the payload and start transmitting
if (currentAppMode() != TX_UNDERFLOW) { // Force underflows in this mode
// Load packet data before transmitting if manual loading is not enabled
if (!txFifoManual) {
loadTxData(txData, txDataLen);
}
}
txStatus = chooseTxType();
if (txStatus != 0) {
lastTxStatus = txStatus;
failPackets++;
scheduleNextTx(); // No callback will fire, so fake it
} else if (currentAppMode() == TX_CANCEL) {
usDelay(txCancelDelay);
if (txCancelMode == RAIL_STOP_MODES_NONE) {
RAIL_Idle(railHandle, RAIL_IDLE_ABORT, false);
} else {
RAIL_StopTx(railHandle, txCancelMode);
}
}
}
}
void pendFinishTxSequence(void)
{
finishTxSequence = true;
}
void pendFinishTxAckSequence(void)
{
finishTxAckSequence = true;
}
void finishTxSequenceIfPending(void)
{
if (finishTxSequence) {
// Defer finishing to next main-loop iteration if Tx completion
// event snuck in after printNewTxError() but before this call.
if (newTxError) {
return;
}
finishTxSequence = false;
// Compute the number of packets sent
uint32_t sentPackets = internalTransmitCounter - startTransmitCounter;
// Don't log if we didn't send any packets
if ((logLevel & ASYNC_RESPONSE)
&& ((failPackets != 0) || (sentPackets != 0) || (ccaSuccesses != 0))) {
RAIL_Events_t paramLastTxStatus = lastTxStatus;
uint32_t paramFailPackets = failPackets;
// Print the number of sent and failed packets
responsePrint("txEnd",
"txStatus:%s,"
"transmitted:%u,"
"lastTxTime:%u,"
"timePos:%u,"
"lastTxStart:%u,"
"ccaSuccess:%u,"
"failed:%u,"
"lastTxStatus:0x%x%08x,"
"isAck:False",
(paramFailPackets == 0
? "Complete"
: (sentPackets == 0 ? "Error" : "Partial")),
sentPackets,
previousTxAppendedInfo.timeSent.packetTime,
previousTxAppendedInfo.timeSent.timePosition,
txStartTime,
ccaSuccesses,
paramFailPackets,
(uint32_t)(paramLastTxStatus >> 32),
(uint32_t)(paramLastTxStatus));
}
startTransmitCounter = internalTransmitCounter;
failPackets = 0;
ccaSuccesses = 0;
lastTxStatus = 0;
}
if (finishTxAckSequence) {
finishTxAckSequence = false;
if ((logLevel & ASYNC_RESPONSE) && printTxAck) {
RAIL_Events_t paramLastTxAckStatus = lastTxAckStatus;
uint32_t paramFailAckPackets = failAckPackets;
uint32_t paramSentAckPackets = sentAckPackets;
// Print the number of sent and failed packets
responsePrint("txEnd",
"txStatus:%s,"
"transmitted:%u,"
"lastTxTime:%u,"
"timePos:%u,"
"failed:%u,"
"lastTxStatus:0x%x%08x,"
"isAck:True",
paramFailAckPackets == 0
? "Complete"
: (paramSentAckPackets == 0 ? "Error" : "Partial"),
paramSentAckPackets,
previousTxAckAppendedInfo.timeSent.packetTime,
previousTxAckAppendedInfo.timeSent.timePosition,
paramFailAckPackets,
(uint32_t)(paramLastTxAckStatus >> 32),
(uint32_t)(paramLastTxAckStatus));
}
sentAckPackets = 0;
failAckPackets = 0;
lastTxAckStatus = 0;
}
}
void printPacket(char *cmdName,
uint8_t *data,
uint16_t dataLength,
RxPacketData_t *packetData)
{
// Print out a length 0 packet message if no packet was given
if ((data == NULL) && (packetData == NULL)) {
responsePrint(cmdName, "len:0");
return;
}
// If this is an Rx packet print the appended info
responsePrintStart(cmdName);
if (packetData != NULL) {
responsePrintContinue(
"len:%d,timeUs:%u,timePos:%u,crc:%s,filterMask:0x%x,rssi:%d,lqi:%d,phy:%d,"
"isAck:%s,syncWordId:%d,antenna:%d,channelHopIdx:%d",
packetData->dataLength,
packetData->appendedInfo.timeReceived.packetTime,
packetData->appendedInfo.timeReceived.timePosition,
(packetData->appendedInfo.crcPassed) ? "Pass" : "Fail",
packetData->filterMask,
packetData->appendedInfo.rssi,
packetData->appendedInfo.lqi,
packetData->appendedInfo.subPhyId,
packetData->appendedInfo.isAck ? "True" : "False",
packetData->appendedInfo.syncWordId,
packetData->appendedInfo.antennaId,
packetData->appendedInfo.channelHoppingChannelIndex);
if (RAIL_IEEE802154_IsEnabled(railHandle)) {
responsePrintContinue(
"ed154:%u,lqi154:%u",
RAIL_IEEE802154_ConvertRssiToEd(packetData->appendedInfo.rssi),
RAIL_IEEE802154_ConvertRssiToLqi(packetData->appendedInfo.lqi,
packetData->appendedInfo.rssi));
}
if (printRxFreqOffsetData) {
if (packetData->freqOffset == RAIL_FREQUENCY_OFFSET_INVALID) {
responsePrintContinue("freqOffset:Invalid");
} else {
responsePrintContinue("freqOffset:%d", packetData->freqOffset);
}
}
} else {
responsePrintContinue("len:%d", dataLength);
}
if (data != NULL) {
// Manually print out payload bytes iteratively, so that we don't need to
// reserve a RAM buffer. Finish the response here.
RAILTEST_PRINTF("{payload:");
for (int i = 0; i < dataLength; i++) {
RAILTEST_PRINTF(" 0x%.2x", data[i]);
}
RAILTEST_PRINTF("}");
}
RAILTEST_PRINTF("}\n");
}
void enqueueEvents(RAIL_Events_t events)
{
events &= enablePrintEvents;
if (events != RAIL_EVENTS_NONE) {
void *railEventHandle = memoryAllocate(sizeof(RailAppEvent_t));
RailAppEvent_t *railEvent = (RailAppEvent_t *)memoryPtrFromHandle(railEventHandle);
if (railEvent == NULL) {
eventsMissed++;
return;
}
railEvent->type = RAIL_EVENT;
// No need to disable interrupts; this is only called from interrupt
// context and RAIL doesn't support nested interrupts/events.
railEvent->railEvent.timestamp = RAIL_GetTime();
railEvent->railEvent.events[1] = (uint32_t)(events >> 32);
railEvent->railEvent.events[0] = (uint32_t)(events);
queueAdd(&railAppEventQueue, railEventHandle);
}
}
void printRailEvents(RailEvent_t *railEvent)
{
RAIL_Events_t events = (((RAIL_Events_t) railEvent->events[1] << 32)
| railEvent->events[0]);
if ((events >> numRailEvents) != RAIL_EVENTS_NONE) {
responsePrintError("printRailEvents", 0x2,
"Unknown RAIL Events:0x%x%08x",
(uint32_t)(events >> 32),
(uint32_t)(events));
return;
}
for (unsigned int i = 0U; events != RAIL_EVENTS_NONE; i++, events >>= 1) {
if ((events & 1U) != RAIL_EVENTS_NONE) {
responsePrint("event",
"timestamp:%u,eventName:RAIL_EVENT_%s",
railEvent->timestamp,
eventNames[i]);
}
}
}
char *handleToString(RAIL_Handle_t railHandle)
{
(void)railHandle;
return "r";//for RAILtest vs MP configuration
}