TRX Host Driver
TRX Host Driver WiSUN MDR

The TRX Host Driver supports the usage of WiSUN Modulation and Data Rate (MDR) negotiation. This page details how to utilize these features and give examples. Several WiSUN MDR examples are provided alongside the TRX Host driver including the rfMdrRx, rfMdrTx, rfMdrTxWithCca, and rfDiagnostics examples.

Frequency Deltas

When a command is provided to the TRX, a frequency calibration operation occurs prior to submission to the radio. The time required to calibrate is non-trivial and only required if the frequency changes more than +/- 2MHz compared to the last calibrated frequency.

For this reason, the TRX provides the ability to specify a command's frequency as a "delta" of the last calibrated frequency. This concept is central to supporting WiSUN MDR on the TRX and is to reduce delay between time critical operations on the device. An example of this can be found in the MDR Transmission section.

Initialization and Configuration Loading

To use WiSUN MDR, the TRX will need to be initialized as described in Initializing the Driver then WiSUN specific configurations are required to be loaded before submitting any MDR related commands.

Loading the WiSUN RF Settings and WiSUN MDR Delta Table

Two RF configuration settings must be loaded to the TRX for WiSUN MDR:

  1. WiSUN PHY Configuration Settings
  2. WiSUN MDR Delta Table

These settings can be generated from Smart RF Studio.

The WiSUN PHY Configuration Settings must always be a single array containing all desired PHYs for a given region. The TRX does not support MDR when the WiSUN PHY settings are split across multiple configurations.

The WiSUN MDR Delta table provides a mapping of frequency deltas for the payload transmission compared to the frequency of the base phy header. The definition of this table is region specific and requires the following information to properly identify:

  1. The base PHY that the MDR header is to be transmitted on
  2. A WiSUN MDR Mapping Table

The WiSUN MDR Mapping Table is embedded within the Combined WiSUN PHY Configuration Settings.

The TRX Host driver examples that contain MDR support provide an example of this process for reference. Below is an additional code snippet showcasing how to identify and load these settings. Further details can be found in the [Loading PHY Configurations](@ ref trx-host-driver-loading-configurations) section of this user guide.

Example

#include <stdint.h>
#include <ti/drivers/dpl/SemaphoreP.h>
// LRF register configurations
#include <ti/trx/rfconfig/LP_EM_CC1307R_CC1190/rcl_settings_wisun.h>
extern const uint32_t LRF_CC1190_frontendRegConfig_wisun[];
extern const uint32_t LRF_CC1190_frontendRegConfig_wisun_byteCount;
extern const uint32_t LRF_CC1190_mainRegConfig_wisun[];
extern const uint32_t LRF_CC1190_mainRegConfig_wisun_byteCount;
#define FE_CONFIG_SIZE (LRF_CC1190_frontendRegConfig_wisun_byteCount)
#define FE_CONFIG_PTR ((uint8_t *)LRF_CC1190_frontendRegConfig_wisun)
#define RF_CONFIG_SIZE (LRF_CC1190_mainRegConfig_wisun_byteCount)
#define RF_CONFIG_PTR ((uint8_t *)LRF_CC1190_mainRegConfig_wisun)
/* TRX RF Header files */
#include <ti/trx/TRX.h>
#include <ti/trx/wisun/wisun_delta_tables_JP.h>
//--------------------------------------------------------------------------
// Select PHY for the MDR Header (Modem and Option Mask)
//--------------------------------------------------------------------------
#define MDR_HEADER_OPTION_MASK TRX_PHY_FEATURE_FSK_MODE_2B_WISUN
// Select modem for the MDR Header is always TRX_RadioCommand_Modem_FSK
#define MDR_HEADER_MODEM TRX_RadioCommand_Modem_FSK
// Select MDR Header channel
#define MDR_HEADER_CHANNEL 10
SemaphoreP_Handle configSemaphore;
static void configCallback(TRX_Host_Handle handle, uintptr_t pConfigData,
TRX_Request *request, uint64_t events, uintptr_t arg)
{
// If this loops, something went wrong when a TRX_Request_ConfigStore was sent to the TRX
while(TRX_EventLastStatusError & events);
{
// Would typically post a semaphore being pended on elsewhere
SemaphoreP_post(configSemaphore);
}
// If status needs to be checked:
// TRX_Request_LastStatus *pReqLastStatus = (TRX_Request_LastStatus *)request;
// TRX_CommandStatus cmdStatus = pReqLastStatus->status;
}
static uint8_t getMdrHeaderIdx(TRX_WisunMdrMappingTable *map)
{
// Find the PHY index in the mapping table
uint8_t mdrHeaderIdx;
for(mdrHeaderIdx = 0; mdrHeaderIdx < map->numEntries; mdrHeaderIdx++)
{
if(map->mappingTable[mdrHeaderIdx].modem == MDR_HEADER_MODEM)
{
if(map->mappingTable[mdrHeaderIdx].optionMask == MDR_HEADER_OPTION_MASK)
{
break;
}
}
}
return(mdrHeaderIdx);
}
static int16_t getMdrHeaderChannelPlanId(TRX_WisunMdrMappingTable *map, uint8_t mdrHeaderIdx)
{
uint8_t mdrHeaderChannelPlanIdIdx = map->mappingTable[mdrHeaderIdx].channelPlanIdIdx;
int16_t mdrHeaderChannelPlanId = get_wisun_channelPlanId_from_channelPlanIdIdx_JP(mdrHeaderChannelPlanIdIdx);
return(mdrHeaderChannelPlanId);
}
int main(void)
{
// Init the semaphores
SemaphoreP_Params semParamsConfig;
SemaphoreP_Params_init(&semParamsConfig);
configSemaphore = SemaphoreP_create(0, &semParamsConfig);
//--------------------------------------------------------------------------
TRX_Host_Params params = {
.generalCb = generalCallback,
.arg = (uintptr_t)NULL
};
// Init the RF driver and load the configuration
TRX_Host_Handle rf_handle = TRX_Host_open(&params);
// Load the WiSUN PHY Configuration Settings
status = TRX_Host_storeConfig(rf_handle, RF_CONFIG_ID, RF_CONFIG_PTR, RF_CONFIG_SIZE,
0xDEADBEEF, true, configCallback, TRX_EventConfigStoreComplete);
while(TRX_Host_Success != status);
SemaphoreP_pend(configSemaphore, SemaphoreP_WAIT_FOREVER);
// Load the PA table
status = TRX_Host_storeConfig(rf_handle, TRX_CONFIG_ID_FRONTEND, FE_CONFIG_PTR, FE_CONFIG_SIZE,
0xFEFEFEFE, true, configCallback, TRX_EventConfigStoreComplete);
while(TRX_Host_Success != status);
SemaphoreP_pend(configSemaphore, SemaphoreP_WAIT_FOREVER);
//--------------------------------------------------------------------------
// Get a pointer to the WiSUN MDR Mapping Table (part of LRF_mainRegConfig_wisun)
uint32_t *pData = (uint32_t *)RF_CONFIG_PTR;
//--------------------------------------------------------------------------
//--------------------------------------------------------------------------
// CONFIG for MDR HEADER
uint8_t mdrHeaderIdx = getMdrHeaderIdx(map);
int16_t mdrHeaderChannelPlanId = getMdrHeaderChannelPlanId(map, mdrHeaderIdx);
int32_t mdrHeaderRxFrequency = get_wisun_frequency_from_channel_JP(mdrHeaderChannelPlanId, MDR_HEADER_CHANNEL);
//--------------------------------------------------------------------------
//--------------------------------------------------------------------------
// Load the WiSUN MDR Delta Table
const uint32_t *newDeltaConfig = get_wisun_delta_table_JP(mdrHeaderChannelPlanId);
uint16_t deltaTableLength = (((uint16_t)(newDeltaConfig[0] & 0x0FFF)) * sizeof(uint32_t)) + sizeof(uint32_t);
status = TRX_Host_storeConfig(rf_handle, DELTA_TABLE_CONFIG_ID, (uint8_t *)newDeltaConfig, deltaTableLength,
0xBABABABA, true, configCallback, TRX_EventConfigStoreComplete);
while(TRX_Host_Success != status);
SemaphoreP_pend(configSemaphore, SemaphoreP_WAIT_FOREVER);
// Continue with sending commands...
}

MDR Transmission

WiSUN MDR transmission using the TRX always uses a chain of at least two TX commands. It may also include a channel sensing command (CS) prior to each TX to ensure the channel is idle before transmitting. This leaves two options for the command chain topology:

  1. TX (Header) –> TX (Payload)
  2. CS –> TX (Header) –> CS –> TX (Payload)

The duration of the channel sensing and channel to sense is dependent on the WiSUN spec. Several pieces of information must be determined to create these commands including:

  1. Frequency of the first TX (Header)
  2. Identifier of the PHY the payload is transmitted on as per WiSUN spec
  3. Frequency of the second TX (Payload) as per WiSUN spec

Each of the commands in a chain need to have a frequency set to them. The frequencies are based on the Channel/Frequency Mapping done in the region specific delta tables. If the TX frequencies are not set properly, then the RX side will not properly process the incoming PPDU Header and switch to the new channel. Gathering this information can be complex but examples are provided as well as the following code snippet.

#include <stdint.h>
#include <ti/drivers/dpl/SemaphoreP.h>
// LRF register configurations
#include <ti/trx/rfconfig/LP_EM_CC1307R_CC1190/rcl_settings_wisun.h>
extern const uint32_t LRF_CC1190_frontendRegConfig_wisun[];
extern const uint32_t LRF_CC1190_frontendRegConfig_wisun_byteCount;
extern const uint32_t LRF_CC1190_mainRegConfig_wisun[];
extern const uint32_t LRF_CC1190_mainRegConfig_wisun_byteCount;
#define FE_CONFIG_SIZE (LRF_CC1190_frontendRegConfig_wisun_byteCount)
#define FE_CONFIG_PTR ((uint8_t *)LRF_CC1190_frontendRegConfig_wisun)
#define RF_CONFIG_SIZE (LRF_CC1190_mainRegConfig_wisun_byteCount)
#define RF_CONFIG_PTR ((uint8_t *)LRF_CC1190_mainRegConfig_wisun)
// TRX RF Header files
#include <ti/trx/TRX.h>
#include <ti/trx/wisun/wisun_delta_tables_JP.h>
// Select PHY for the MDR Header (Modem and Option Mask)
#define MDR_HEADER_OPTION_MASK TRX_PHY_FEATURE_FSK_MODE_2B_WISUN
// Select modem for the MDR Header is always TRX_RadioCommand_Modem_FSK
#define MDR_HEADER_MODEM TRX_RadioCommand_Modem_FSK
// Select MDR Header channel
#define MDR_HEADER_CHANNEL 10
// Select PHY for the MDR Packet
#define MDR_PACKET_OPTION_MASK TRX_PHY_FEATURE_OFDM_OPTION_2_WISUN
// Select modem for the MDR Packet
#define MDR_PACKET_MODEM TRX_RadioCommand_Modem_OFDM
// Select the rate for the MDR Packet
#define MDR_PACKET_RATE TRX_PayloadHeader_SunOFDM_Rate_MCS5
// Application specific defines
#define RSSI_THRESHOLD (-80) // RSSI threshold used for CS. In this example, the same threshold is used for both the
// MDR Header and the MDR Packet, regardless of PHY
// ID of the PHY configuration on the TRX. Can be anything in the range 1, 15 inclusive
#define RF_CONFIG_ID (1U)
#define DELTA_TABLE_CONFIG_ID (2U)
// ID of the MDR header and packet data payload on the TRX. Can be anything in the range 0, 15 inclusive
#define MDR_HEADER_STREAM_ID (0U)
#define MDR_PACKET_STREAM_ID (1U)
// Slots for the MDR commands on the TRX. Can be anything in the range 0, 5 inclusive
#define CMD_CS_MDR_HEADER_SLOT (0U)
#define CMD_TX_MDR_HEADER_SLOT (1U)
#define CMD_CS_MDR_PACKET_SLOT (2U)
#define CMD_TX_MDR_PACKET_SLOT (3U)
#define TX_PAYLOAD_LENGTH (10U) // Number of payload bytes to be sent
#define TX_PACKET_LENGTH (sizeof(TRX_PayloadHeader) + TX_PAYLOAD_LENGTH) // Do not change
// TX packets
uint8_t txMdrHeader[sizeof(TRX_PayloadHeader)] = {0, 0, 0, 0}; // TRX_PayloadHeader
// Init the Header to 0
uint8_t txMdrPacket[TX_PACKET_LENGTH] = {0, 0, 0, 0}; // TRX_PayloadHeader + payload
// Init the Header to 0
static uint8_t getMdrHeaderIdx(TRX_WisunMdrMappingTable *map)
{
// Find the PHY index in the mapping table
uint8_t mdrHeaderIdx;
for(mdrHeaderIdx = 0; mdrHeaderIdx < map->numEntries; mdrHeaderIdx++)
{
if(map->mappingTable[mdrHeaderIdx].modem == MDR_HEADER_MODEM)
{
if(map->mappingTable[mdrHeaderIdx].optionMask == MDR_HEADER_OPTION_MASK)
{
break;
}
}
}
return(mdrHeaderIdx);
}
static uint8_t getMdrPacketIdx(TRX_WisunMdrMappingTable *map)
{
// Find the PHY index in the mapping table (part of LRF_mainRegConfig_wisun)
uint8_t mdrPacketIdx;
for(mdrPacketIdx = 0U; mdrPacketIdx < map->numEntries; mdrPacketIdx++)
{
if(map->mappingTable[mdrPacketIdx].modem == MDR_PACKET_MODEM)
{
if(map->mappingTable[mdrPacketIdx].optionMask == MDR_PACKET_OPTION_MASK)
{
break;
}
}
}
return(mdrPacketIdx);
}
static int16_t getMdrHeaderChannelPlanId(TRX_WisunMdrMappingTable *map, uint8_t mdrHeaderIdx)
{
uint8_t mdrHeaderChannelPlanIdIdx = map->mappingTable[mdrHeaderIdx].channelPlanIdIdx;
int16_t mdrHeaderChannelPlanId = get_wisun_channelPlanId_from_channelPlanIdIdx_JP(mdrHeaderChannelPlanIdIdx);
return(mdrHeaderChannelPlanId);
}
static int16_t getMdrPacketChannelPlanId(TRX_WisunMdrMappingTable *map, uint8_t mdrPacketIdx)
{
uint8_t mdrPacketChannelPlanIdIdx = map->mappingTable[mdrPacketIdx].channelPlanIdIdx;
int16_t mdrPacketChannelPlanId = get_wisun_channelPlanId_from_channelPlanIdIdx_JP(mdrPacketChannelPlanIdIdx);
return(mdrPacketChannelPlanId);
}
static uint32_t getMdrPacketTxFrequency(int16_t mdrPacketChannelPlanId, int32_t mdrHeaderTxFrequency)
{
// Find min. and max. channel number to be able to find the mdrPacketTxFrequency
uint16_t minMdrPacketChan;
uint16_t maxMdrPacketChan;
if (mdrPacketChannelPlanId == 21)
{
minMdrPacketChan = 9U; // WiSUN MDR Spec
maxMdrPacketChan = 37U; // WiSUN MDR Spec
}
if (mdrPacketChannelPlanId == 22)
{
minMdrPacketChan = 4U; // WiSUN MDR Spec
maxMdrPacketChan = 17U; // WiSUN MDR Spec
}
if (mdrPacketChannelPlanId == 23)
{
minMdrPacketChan = 3U; // WiSUN MDR Spec
maxMdrPacketChan = 11U; // WiSUN MDR Spec
}
if (mdrPacketChannelPlanId == 24)
{
minMdrPacketChan = 2U; // WiSUN MDR Spec
maxMdrPacketChan = 8U; // WiSUN MDR Spec
}
uint32_t newFreq = WISUN_FREQ_INVALID;
for(int16_t i = minMdrPacketChan; i <= maxMdrPacketChan; i++)
{
newFreq = get_wisun_frequency_from_channel_JP(mdrPacketChannelPlanId, i);
if((mdrHeaderTxFrequency < newFreq) && (WISUN_FREQ_INVALID != newFreq))
{
uint32_t prevFreq = get_wisun_frequency_from_channel_JP(mdrPacketChannelPlanId, i-1);
if((newFreq - mdrHeaderTxFrequency) > (mdrHeaderTxFrequency - prevFreq))
{
newFreq = prevFreq;
}
break;
}
}
return(newFreq);
}
static void setupCmdMdrCsHdr(TRX_Request_CommandStore *pCmdMdrCsHeader, int32_t mdrHeaderTxFrequency)
{
//--------------------------------------------------------------------------
// Create the CS command for the MDR Header
pCmdMdrCsHeader->slot = CMD_CS_MDR_HEADER_SLOT;
pCmdMdrCsHeader->slot_on_false = CMD_TX_MDR_HEADER_SLOT; // Chain to the MDR Header TX on Channel Idle
pCmdMdrCsHeader->enable_on_false = true; // Chain to the MDR Header TX on Channel Idle
pCmdMdrCsHeader->enable_on_true = false;
pCmdMdrCsHeader->enable_on_compare = false;
pCmdMdrCsHeader->trigger = Command_Trigger_Immediate;
pCmdMdrCsHeader->allow_delay = false;
pCmdMdrCsHeader->params.cs.modem = MDR_HEADER_MODEM;
pCmdMdrCsHeader->params.cs.frequency = mdrHeaderTxFrequency;
pCmdMdrCsHeader->params.cs.phy0.config_id = RF_CONFIG_ID;
pCmdMdrCsHeader->params.cs.phy0.option_mask = MDR_HEADER_OPTION_MASK;
pCmdMdrCsHeader->params.cs.phy1.config_id = 0U;
pCmdMdrCsHeader->params.cs.phy1.option_mask = 0U;
pCmdMdrCsHeader->params.cs.phy2.config_id = 0U;
pCmdMdrCsHeader->params.cs.phy2.option_mask = 0U;
pCmdMdrCsHeader->params.cs.mode = TRX_CarrierSense_Mode_Energy; // Channel busy if energy above threshold
pCmdMdrCsHeader->params.cs.rssi_override = RSSI_THRESHOLD;
pCmdMdrCsHeader->params.cs.rssi_window = 5U;
pCmdMdrCsHeader->params.cs.rssi_count = 5U;
pCmdMdrCsHeader->params.cs.exit_condition = TRX_CarrierSense_ExitCondition_WaitForBusy; // Finish on channel busy or timeout
// Time, in microseconds, after which CS command is finished regardless of results.
// If this is not long enough, the command will return IDLE at timeout even if the RSSI is not valid yet.
// The time it takes for CS to be valid varies with the different PHYs
// This example uses the same timeouts as rfDiagnostics
if(pCmdMdrCsHeader->params.cs.phy0.option_mask == TRX_PHY_FEATURE_FSK_MODE_2B_WISUN)
{
pCmdMdrCsHeader->params.cs.timeout = 276U; // 51.3 + 96 + 128 = 275.3 // Same as rfDiagnostics
}
else // TRX_PHY_FEATURE_FSK_MODE_4B_WISUN
{
pCmdMdrCsHeader->params.cs.timeout = 217U; // 40.2 + 48 + 128 = 216.2 // Same as rfDiagnostics
}
}
static void setupCmdMdrTxHdr(TRX_Request_CommandStore *pCmdMdrTxHeader, uint16_t newPhyId)
{
//--------------------------------------------------------------------------
// Create the TX command for the MDR Header (only supported on an FSK PHY)
pCmdMdrTxHeader->slot = CMD_TX_MDR_HEADER_SLOT;
pCmdMdrTxHeader->cmd_id = TRX_RadioCommand_Transmit;
pCmdMdrTxHeader->enable_on_true = true; // Run next CS command when first TX has finished
pCmdMdrTxHeader->slot_on_true = CMD_CS_MDR_PACKET_SLOT;
pCmdMdrTxHeader->enable_on_false = false;
pCmdMdrTxHeader->enable_on_compare = false;
pCmdMdrTxHeader->allow_delay = false;
pCmdMdrTxHeader->params.tx.stream_id = MDR_HEADER_STREAM_ID;
pCmdMdrTxHeader->params.tx.phy0.config_id = RF_CONFIG_ID;
pCmdMdrTxHeader->params.tx.phy0.option_mask = MDR_HEADER_OPTION_MASK;
pCmdMdrTxHeader->params.tx.phy1.config_id = 0U;
pCmdMdrTxHeader->params.tx.phy1.option_mask = 0U;
pCmdMdrTxHeader->params.tx.phy2.config_id = 0U;
pCmdMdrTxHeader->params.tx.phy2.option_mask = 0U;
pCmdMdrTxHeader->params.tx.frequency = 0U; // Set this to 0 as this will make it equal to the mdrHeaderTxFrequency
// but without having to calibrate;
pCmdMdrTxHeader->params.tx.modem = MDR_HEADER_MODEM;
pCmdMdrTxHeader->params.tx.power.dBm = 10;
pCmdMdrTxHeader->params.tx.power.fraction = 0;
((TRX_PayloadHeader_SunFSK *)txMdrHeader)->length = 0U; // No Payload
((TRX_PayloadHeader_SunFSK *)txMdrHeader)->modulation = TRX_PayloadHeader_Modulation_FSK; // 0U = FSK
((TRX_PayloadHeader_SunFSK *)txMdrHeader)->mode_switch = 1U; // 1U = mode switch enabled
((TRX_PayloadHeader_SunFSK *)txMdrHeader)->fcs_mode = 0U; // 0U = CRC32; 1U = CRC16
((TRX_PayloadHeader_SunFSK *)txMdrHeader)->whitening = 1U; // 1U = Whitening enabled
((TRX_PayloadHeader_SunFSK *)txMdrHeader)->newPhyId = (newPhyId | MDR_PACKET_RATE);
}
static void setupCmdMdrCsPacket(TRX_Request_CommandStore *pCmdMdrCsPacket, int32_t mdrPacketTxFrequency)
{
//--------------------------------------------------------------------------
// Create the CS command for the MDR Header
pCmdMdrCsPacket->slot = CMD_CS_MDR_PACKET_SLOT;
pCmdMdrCsPacket->slot_on_false = CMD_TX_MDR_PACKET_SLOT; // Chain to the MDR Packet TX on Channel Idle
pCmdMdrCsPacket->enable_on_false = true; // Chain to the MDR Packet TX on Channel Idle
pCmdMdrCsPacket->enable_on_true = false;
pCmdMdrCsPacket->enable_on_compare = false;
pCmdMdrCsPacket->allow_delay = false;
pCmdMdrCsPacket->params.cs.modem = MDR_PACKET_MODEM;
pCmdMdrCsPacket->params.cs.frequency = mdrPacketTxFrequency;
pCmdMdrCsPacket->params.cs.phy0.config_id = RF_CONFIG_ID;
pCmdMdrCsPacket->params.cs.phy0.option_mask = MDR_PACKET_OPTION_MASK;
pCmdMdrCsPacket->params.cs.phy1.config_id = 0U;
pCmdMdrCsPacket->params.cs.phy1.option_mask = 0U;
pCmdMdrCsPacket->params.cs.phy2.config_id = 0U;
pCmdMdrCsPacket->params.cs.phy2.option_mask = 0U;
pCmdMdrCsPacket->params.cs.mode = TRX_CarrierSense_Mode_Energy; // Channel busy if energy above threshold
pCmdMdrCsPacket->params.cs.rssi_override = RSSI_THRESHOLD;
pCmdMdrCsPacket->params.cs.corr_window = 0U;
pCmdMdrCsPacket->params.cs.corr_count = 0U;
pCmdMdrCsPacket->params.cs.exit_condition = TRX_CarrierSense_ExitCondition_WaitForBusy; // Finish on channel busy or timeout
if(pCmdMdrCsPacket->params.cs.modem == TRX_RadioCommand_Modem_FSK)
{
pCmdMdrCsPacket->params.cs.rssi_window = 5U;
pCmdMdrCsPacket->params.cs.rssi_count = 5U;
if(pCmdMdrCsPacket->params.cs.phy0.option_mask == TRX_PHY_FEATURE_FSK_MODE_2B_WISUN)
{
pCmdMdrCsPacket->params.cs.timeout = 276U; // 51.3 + 96 + 128 = 275.3 // Same as rfDiagnostics
}
else // TRX_PHY_FEATURE_FSK_MODE_4B_WISUN
{
pCmdMdrCsPacket->params.cs.timeout = 217U; // 40.2 + 48 + 128 = 216.2 // Same as rfDiagnostics
}
}
else // TRX_RadioCommand_Modem_OFDM
{
pCmdMdrCsPacket->params.cs.rssi_window = 3U;
pCmdMdrCsPacket->params.cs.rssi_count = 3U;
switch(pCmdMdrCsPacket->params.cs.phy0.option_mask)
{
case TRX_PHY_FEATURE_OFDM_OPTION_2_WISUN:
pCmdMdrCsPacket->params.cs.timeout = 193U; // Same as rfDiagnostics
break;
case TRX_PHY_FEATURE_OFDM_OPTION_3_WISUN:
pCmdMdrCsPacket->params.cs.timeout = 193U; // Same as rfDiagnostics
break;
case TRX_PHY_FEATURE_OFDM_OPTION_4_WISUN:
pCmdMdrCsPacket->params.cs.timeout = 193; // Same as rfDiagnostics
default:
break;
}
}
}
static void setupCmdMdrTxPacket(TRX_Request_CommandStore *pCmdMdrTxPacket, int32_t mdrPacketTxFrequency)
{
//--------------------------------------------------------------------------
// Create the TX command for the MDR Packet
pCmdMdrTxPacket->slot = CMD_TX_MDR_PACKET_SLOT;
pCmdMdrTxPacket->cmd_id = TRX_RadioCommand_Transmit;
pCmdMdrTxPacket->enable_on_true = false;
pCmdMdrTxPacket->enable_on_false = false;
pCmdMdrTxPacket->enable_on_compare = false;
pCmdMdrTxPacket->allow_delay = false;
pCmdMdrTxPacket->params.tx.stream_id = MDR_PACKET_STREAM_ID;
pCmdMdrTxPacket->params.tx.phy0.config_id = RF_CONFIG_ID;
pCmdMdrTxPacket->params.tx.phy0.option_mask = MDR_PACKET_OPTION_MASK;
pCmdMdrTxPacket->params.tx.phy1.config_id = 0U;
pCmdMdrTxPacket->params.tx.phy1.option_mask = 0U;
pCmdMdrTxPacket->params.tx.phy2.config_id = 0U;
pCmdMdrTxPacket->params.tx.phy2.option_mask = 0U;
pCmdMdrTxPacket->params.tx.frequency = mdrPacketTxFrequency;
pCmdMdrTxPacket->params.tx.modem = MDR_PACKET_MODEM;
pCmdMdrTxPacket->params.tx.power.dBm = 10;
pCmdMdrTxPacket->params.tx.power.fraction = 0;
// Update the PHY header of the MDR Packet
if(pCmdMdrTxPacket->params.tx.modem == TRX_RadioCommand_Modem_FSK)
{
((TRX_PayloadHeader_SunFSK *)txMdrPacket)->length = TX_PAYLOAD_LENGTH; // This length excludes the header
((TRX_PayloadHeader_SunFSK *)txMdrPacket)->mode_switch = 0U; // 0U = No mode switch
((TRX_PayloadHeader_SunFSK *)txMdrPacket)->fcs_mode = 0U; // 0U = CRC32; 1U = CRC16
((TRX_PayloadHeader_SunFSK *)txMdrPacket)->whitening = 1U; // 1U = Whitening enabled
((TRX_PayloadHeader_SunFSK *)txMdrPacket)->newPhyId = 0U;
}
else if(pCmdMdrTxPacket->params.tx.modem == TRX_RadioCommand_Modem_OFDM)
{
((TRX_PayloadHeader_SunOFDM *)txMdrPacket)->length = TX_PAYLOAD_LENGTH; // This length excludes the header
((TRX_PayloadHeader_SunOFDM *)txMdrPacket)->rate = MDR_PACKET_RATE;
((TRX_PayloadHeader_SunOFDM *)txMdrPacket)->scrambler = 0U;
((TRX_PayloadHeader_SunOFDM *)txMdrPacket)->newPhyId = 0U;
}
else
{
// Unsupported modem
while(1);
}
}
int main(void)
{
// Initialize TRX and load configurations...
//--------------------------------------------------------------------------
// Get a pointer to the mapping table (part of LRF_mainRegConfig_wisun)
uint32_t *pData = (uint32_t *)RF_CONFIG_PTR;
//--------------------------------------------------------------------------
//--------------------------------------------------------------------------
// CONFIG for MDR HEADER
uint8_t mdrHeaderIdx = getMdrHeaderIdx(map);
int16_t mdrHeaderChannelPlanId = getMdrHeaderChannelPlanId(map, mdrHeaderIdx);
int32_t mdrHeaderTxFrequency = get_wisun_frequency_from_channel_JP(mdrHeaderChannelPlanId, MDR_HEADER_CHANNEL);
//--------------------------------------------------------------------------
//--------------------------------------------------------------------------
// CONFIG for MDR PACKET
uint8_t mdrPacketIdx = getMdrPacketIdx(map);
int16_t mdrPacketChannelPlanId = getMdrPacketChannelPlanId(map, mdrPacketIdx);
uint32_t mdrPacketTxFrequency = getMdrPacketTxFrequency(mdrPacketChannelPlanId, mdrHeaderTxFrequency);
int32_t mdrPacketDeltaTxFrequency = (int32_t)(mdrPacketTxFrequency - mdrHeaderTxFrequency); // This the frequency of the MDR packet is
// given as a delta from the mdrHeaderTxFrequency to avoid
// having to calibrate
//--------------------------------------------------------------------------
//--------------------------------------------------------------------------
// CMD setup
TRX_Request_CommandStore cmdCsMdrHeader;
TRX_Request_CommandStore cmdTxMdrHeader;
TRX_Request_CommandStore cmdCsMdrPacket;
TRX_Request_CommandStore cmdTxMdrPacket;
uint16_t newPhyId = map->mappingTable[mdrPacketIdx].mdrByte;
setupCmdMdrCsHdr(&(cmdCsMdrHeader), mdrHeaderTxFrequency);
setupCmdMdrTxHdr(&(cmdTxMdrHeader), newPhyId);
setupCmdMdrCsPacket(&(cmdCsMdrPacket), mdrPacketTxFrequency);
setupCmdMdrTxPacket(&(cmdTxMdrPacket), mdrPacketTxFrequency);
//--------------------------------------------------------------------------
// Store and submit the commands...
}

MDR Reception

WiSUN MDR reception using the TRX always uses a single RX command with MDR enabled. The switching from the base PHY (header) to the new phy (payload) is done automatically on the TRX as well as the change in frequency if required.

For this automatic switch to be possible, the following configurations must be loaded to to the TRX as described in Initialization and Configuration Loading:

  1. WiSUN PHY Configuration Settings
  2. WiSUN MDR Delta Table

Several pieces of information are also required to create the RX command:

  1. Frequency of the first RX (Header)
  2. Base index of the RX channel in the WiSUN MDR Delta Table

Gathering this information can be complex but examples are provided as well as the following code snippet.

// TI Drivers
#include <ti/drivers/GPIO.h>
#include <ti/drivers/dpl/SemaphoreP.h>
// Board Header files
#include "ti_drivers_config.h"
// TRX RF Header files
#include <ti/trx/TRX.h>
#include <ti/trx/wisun/wisun_delta_tables_JP.h>
// Select PHY for the MDR Header (Modem and Option Mask)
#define MDR_HEADER_OPTION_MASK TRX_PHY_FEATURE_FSK_MODE_2B_WISUN
// Select modem for the MDR Header is always TRX_RadioCommand_Modem_FSK
#define MDR_HEADER_MODEM TRX_RadioCommand_Modem_FSK
// Select MDR Header channel
#define MDR_HEADER_CHANNEL 10
// Application specific defines
#define RX_PAYLOAD_LENGTH (10U) // Maximum payload in a receive operation
#define REPEAT_MODE (1U) // 1: Enable
// 0: Disable
// Derived defines (not to be changed)
// The data received will contain both timestamp (4B) and RSSI (1B) information
#define RSSI_SIZE_BYTES (1U)
#define TIMESTAMP_SIZE_BYTES (4U)
#define RX_PACKET_LENGTH (sizeof(TRX_PayloadHeader) + RX_PAYLOAD_LENGTH + RSSI_SIZE_BYTES + TIMESTAMP_SIZE_BYTES)
// ID of the PHY configuration on the TRX. Can be anything in the range 1, 15 inclusive
#define RF_CONFIG_ID (1U)
#define DELTA_TABLE_CONFIG_ID (2U)
// ID of the MDR packet data payload on the TRX. Can be anything in the range 0, 15 inclusive
#define MDR_PACKET_STREAM_ID (0U)
// Slots for the MDR commands on the TRX. Can be anything in the range 0, 5 inclusive
#define CMD_RX_MDR_PACKET_SLOT (0U)
// LRF register configurations
#include <ti/trx/rfconfig/LP_EM_CC1307R_CC1190/rcl_settings_wisun.h>
extern const uint32_t LRF_CC1190_frontendRegConfig_wisun[];
extern const uint32_t LRF_CC1190_frontendRegConfig_wisun_byteCount;
extern const uint32_t LRF_CC1190_mainRegConfig_wisun[];
extern const uint32_t LRF_CC1190_mainRegConfig_wisun_byteCount;
#define FE_CONFIG_SIZE (LRF_CC1190_frontendRegConfig_wisun_byteCount)
#define FE_CONFIG_PTR ((uint8_t *)LRF_CC1190_frontendRegConfig_wisun)
#define RF_CONFIG_SIZE (LRF_CC1190_mainRegConfig_wisun_byteCount)
#define RF_CONFIG_PTR ((uint8_t *)LRF_CC1190_mainRegConfig_wisun)
// Receive buffer
uint8_t rxBuffer[RX_PACKET_LENGTH] = {0};
// Buffers for the different part of the packet
uint8_t rxHeader[sizeof(TRX_PayloadHeader)];
uint8_t rxPayload[RX_PAYLOAD_LENGTH];
uint8_t rxRssi[RSSI_SIZE_BYTES];
uint8_t rxTimestamp[TIMESTAMP_SIZE_BYTES];
uint16_t eventSyncFrameDetected = 0U;
static uint8_t getMdrHeaderIdx(TRX_WisunMdrMappingTable *map)
{
// Find the PHY index in the mapping table
uint8_t mdrHeaderIdx;
for(mdrHeaderIdx = 0; mdrHeaderIdx < map->numEntries; mdrHeaderIdx++)
{
if(map->mappingTable[mdrHeaderIdx].modem == MDR_HEADER_MODEM)
{
if(map->mappingTable[mdrHeaderIdx].optionMask == MDR_HEADER_OPTION_MASK)
{
break;
}
}
}
return(mdrHeaderIdx);
}
static int16_t getMdrHeaderChannelPlanId(TRX_WisunMdrMappingTable *map, uint8_t mdrHeaderIdx)
{
uint8_t mdrHeaderChannelPlanIdIdx = map->mappingTable[mdrHeaderIdx].channelPlanIdIdx;
int16_t mdrHeaderChannelPlanId = get_wisun_channelPlanId_from_channelPlanIdIdx_JP(mdrHeaderChannelPlanIdIdx);
return(mdrHeaderChannelPlanId);
}
static void setupCmdMdrRxHdr(TRX_Request_CommandStore *pCmdMdrRxHeader, int32_t mdrHeaderRxFrequency, int16_t baseIdx)
{
//--------------------------------------------------------------------------
// Create the RX command
pCmdMdrRxHeader->slot = CMD_RX_MDR_PACKET_SLOT;
pCmdMdrRxHeader->cmd_id = TRX_RadioCommand_Receive;
pCmdMdrRxHeader->enable_on_true = false;
pCmdMdrRxHeader->enable_on_false = false;
pCmdMdrRxHeader->enable_on_compare = false;
pCmdMdrRxHeader->trigger = Command_Trigger_Immediate;
pCmdMdrRxHeader->allow_delay = false;
pCmdMdrRxHeader->params.rx.phy0.config_id = RF_CONFIG_ID;
pCmdMdrRxHeader->params.rx.phy0.option_mask = MDR_HEADER_OPTION_MASK;
pCmdMdrRxHeader->params.rx.phy1.config_id = DELTA_TABLE_CONFIG_ID;
pCmdMdrRxHeader->params.rx.phy1.option_mask = (uint16_t)baseIdx;
pCmdMdrRxHeader->params.rx.phy2.config_id = 0U;
pCmdMdrRxHeader->params.rx.phy2.option_mask = 0U;
pCmdMdrRxHeader->params.rx.stream_id = MDR_PACKET_STREAM_ID;
pCmdMdrRxHeader->params.rx.frequency = mdrHeaderRxFrequency;
pCmdMdrRxHeader->params.rx.modem = MDR_HEADER_MODEM;
pCmdMdrRxHeader->params.rx.enable_mdr = true;
pCmdMdrRxHeader->params.rx.stream_early = true; // When this is enabled, RX packets will start
// being streamed over SPI during the reception
// of the data rather than after the packet has
// been fully received.
pCmdMdrRxHeader->params.rx.timeout = 0U;
pCmdMdrRxHeader->params.rx.repeat = REPEAT_MODE;
}
static void sfdCallback(uint_least8_t index)
{
GPIO_write(CONFIG_GPIO_RLED, 1);
eventSyncFrameDetected++; // Debug
GPIO_write(CONFIG_GPIO_RLED, 0);
}
int main(void)
{
// Initialize TRX and load configurations...
//--------------------------------------------------------------------------
// Get a pointer to the mapping table (part of LRF_mainRegConfig_wisun)
uint32_t *pData = (uint32_t *)RF_CONFIG_PTR;
//--------------------------------------------------------------------------
//--------------------------------------------------------------------------
// CONFIG for MDR HEADER
uint8_t mdrHeaderIdx = getMdrHeaderIdx(map);
int16_t mdrHeaderChannelPlanId = getMdrHeaderChannelPlanId(map, mdrHeaderIdx);
int32_t mdrHeaderRxFrequency = get_wisun_frequency_from_channel_JP(mdrHeaderChannelPlanId, MDR_HEADER_CHANNEL);
//--------------------------------------------------------------------------
//--------------------------------------------------------------------------
// CMD setup
TRX_Request_CommandStore cmdMdrRxHeader;
int16_t baseIdx = get_wisun_delta_table_baseindex_JP(mdrHeaderChannelPlanId, MDR_HEADER_CHANNEL);
setupCmdMdrRxHdr(&cmdMdrRxHeader, mdrHeaderRxFrequency, baseIdx);
//--------------------------------------------------------------------------
//--------------------------------------------------------------------------
// SFD enable on TRX
GPIO_setConfig(CONFIG_GPIO_SFD, GPIO_CFG_IN_PU | GPIO_CFG_IN_INT_RISING);
// Install Button callback
GPIO_setCallback(CONFIG_GPIO_SFD, sfdCallback);
// Enable interrupts
GPIO_enableInt(CONFIG_GPIO_SFD);
TRX_DioConfig sfdDioConfig = {
.dio2 = DIO2_UNCHANGED,
.dio3 = DIO3_UNCHANGED,
.dio4 = DIO4_UNCHANGED,
.dio5 = DIO5_UNCHANGED,
.dio6 = DIO6_UNCHANGED,
.dio7 = DIO7_UNCHANGED,
};
status = TRX_Host_utilDioSetup(rf_handle, sfdDioConfig, NULL);
while(TRX_Host_Success != status);
//--------------------------------------------------------------------------
// Store and submit the command...
}

Parsing the RX Command for the PHY

To identify the PHY that the MDR RX was received on, the header can be parsed to find the correct PHY. The modem and rate can be acquired by directly reading the header, but the optionMask requires post-processing the newPhyId from the header. Once the newPhyId is retrieved, this can be used to acquire the optionMask used for the payload that was received which will help determine what correct PHY.

Here is an example of how to parse the RX payload:

static void rxCallback(TRX_Host_Handle handle, uintptr_t pCmdOrData,
TRX_Request *request, uint64_t events, uintptr_t arg)
{
TRX_CommandStatus cmdStatus;
{
// If status needs to be checked:
// TRX_Request_LastStatus *pReqLastStatus = (TRX_Request_LastStatus *)request;
// TRX_CommandStatus cmdStatus = pReqLastStatus->status;
eventError++;
SemaphoreP_post(rxSemaphore);
}
//--------------------------------------------------------------------------
// Get a pointer to the mapping table (part of LRF_mainRegConfig_wisun)
uint32_t *pData = (uint32_t *)RF_CONFIG_PTR;
//--------------------------------------------------------------------------
//--------------------------------------------------------------------------
// CONFIG for MDR HEADER
uint8_t mdrHeaderIdx = getMdrHeaderIdx(map);
int16_t mdrHeaderChannelPlanId = getMdrHeaderChannelPlanId(map, mdrHeaderIdx);
int32_t mdrHeaderRxFrequency = get_wisun_frequency_from_channel_JP(mdrHeaderChannelPlanId, MDR_HEADER_CHANNEL);
//--------------------------------------------------------------------------
{
{
// The RX buffer wasn't big enough
eventRxBufFull++; // Debug
}
else
{
// +----------+---------+----------+----------------+
// | hdr (4B) | payload | rssi(1B) | timestamp (4B) |
// +----------+---------+----------+----------------+
if((uintptr_t)NULL != pCmdOrData)
{
// Read the packet, obtaining the header information, the payload,
// the RSSI and the timestamp and copying them into buffers for
// easy access and reading.
TRX_PayloadHeader *header = (TRX_PayloadHeader *)pCmdOrData;
uint8_t *startOfPayload = (uint8_t *)header + sizeof(TRX_PayloadHeader);
uint8_t *endOfPayload = (uint8_t *)header + header->length + sizeof(TRX_PayloadHeader);
int8_t *rssi = (int8_t *)endOfPayload;
uint32_t *timestamp = (uint32_t *)(endOfPayload + sizeof(int8_t));
memcpy(rxHeader, header, sizeof(TRX_PayloadHeader));
memcpy(rxPayload, startOfPayload, header->length);
memcpy(rxRssi, rssi, RSSI_SIZE_BYTES);
memcpy(rxTimestamp, timestamp, TIMESTAMP_SIZE_BYTES);
packetReceived++;
GPIO_toggle(CONFIG_GPIO_GLED);
}
rate = 0;
{
mdrByte = (uint8_t)(((TRX_PayloadHeader_SunFSK *)((TRX_PayloadHeader *)pCmdOrData))->newPhyId);
}
{
mdrByte = (uint8_t)(((TRX_PayloadHeader_SunOFDM *)((TRX_PayloadHeader *)pCmdOrData))->newPhyId);
rate = (uint8_t)(((TRX_PayloadHeader_SunOFDM *)((TRX_PayloadHeader *)pCmdOrData))->rate);
// Undo OR of rate into mdrByte
mdrByte = mdrByte ^ rate;
}
{
// Get optionMask to determine if it is FSK2B or FSK4B
if(mdrByte != 0)
{
uint8_t i;
for(i = 0; i < map->numEntries; i++)
{
if(mdrByte == map->mappingTable[i].mdrByte)
{
optionMask = map->mappingTable[i].optionMask;
// optionMask = 0: FSK2B
// optionMask = 1: FSK4B
break;
}
}
}
}
else if(modem == TRX_RadioCommand_Modem_OFDM)
{
// Get optionMask to determine if it is OFDMo2, OFMDo3 or OFDMo4
if(mdrByte != 0)
{
uint8_t i;
for(i = 0; i < map->numEntries; i++)
{
if(mdrByte == map->mappingTable[i].mdrByte)
{
optionMask = map->mappingTable[i].optionMask;
// optionMask = 0: OFDMo2, rate = x: MCSx
// optionMask = 2: OFDMo3, rate = x: MCSx
// optionMask = 4: OFDMo4, rate = x: MCSx
break;
}
}
}
}
}
}
{
SemaphoreP_post(rxSemaphore);
eventLastCmdDone++; // Debug
// Debugging
TRX_Request_CommandStatus *pCmdStatusRequest = (TRX_Request_CommandStatus *)request;
TRX_CommandStatus cmdStatus = pCmdStatusRequest->status;
switch(cmdStatus)
{
// Command ended gracefully because the set timeout expired
commandStatus_GracefulStopTimeout++; // Debug
break;
// Command ended because graceful stop command was sent
commandStatus_GracefulStopApi++; // Debug
break;
// Command ended gracefully because another command was scheduled
commandStatus_GracefulStopScheduling++; // Debug
break;
// Command ended because hard stop command was sent
commandStatus_HardStopApi++; // Debug
break;
// Command ended hard because another command was scheduled
commandStatus_HardStopScheduling++; // Debug
break;
default:
break;
}
}
if (TRX_EventCmdStatus & events)
{
// If status needs to be checked:
// TRX_Request_CommandStatus *pCmdStatusRequest = (TRX_Request_CommandStatus *)request;
// TRX_CommandStatus cmdStatus = pCmdStatusRequest->status;
eventCmdDone++; // Debug
}
}