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:
- WiSUN PHY Configuration Settings
- 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:
- The base PHY that the MDR header is to be transmitted on
- 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>
#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)
#include <ti/trx/wisun/wisun_delta_tables_JP.h>
#define MDR_HEADER_OPTION_MASK TRX_PHY_FEATURE_FSK_MODE_2B_WISUN
#define MDR_HEADER_MODEM TRX_RadioCommand_Modem_FSK
#define MDR_HEADER_CHANNEL 10
SemaphoreP_Handle configSemaphore;
{
{
SemaphoreP_post(configSemaphore);
}
}
{
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);
}
{
uint8_t mdrHeaderChannelPlanIdIdx = map->
mappingTable[mdrHeaderIdx].channelPlanIdIdx;
int16_t mdrHeaderChannelPlanId = get_wisun_channelPlanId_from_channelPlanIdIdx_JP(mdrHeaderChannelPlanIdIdx);
return(mdrHeaderChannelPlanId);
}
int main(void)
{
SemaphoreP_Params semParamsConfig;
SemaphoreP_Params_init(&semParamsConfig);
configSemaphore = SemaphoreP_create(0, &semParamsConfig);
.generalCb = generalCallback,
.arg = (uintptr_t)NULL
};
SemaphoreP_pend(configSemaphore, SemaphoreP_WAIT_FOREVER);
SemaphoreP_pend(configSemaphore, SemaphoreP_WAIT_FOREVER);
uint32_t *pData = (uint32_t *)RF_CONFIG_PTR;
uint8_t mdrHeaderIdx = getMdrHeaderIdx(map);
int16_t mdrHeaderChannelPlanId = getMdrHeaderChannelPlanId(map, mdrHeaderIdx);
int32_t mdrHeaderRxFrequency = get_wisun_frequency_from_channel_JP(mdrHeaderChannelPlanId, MDR_HEADER_CHANNEL);
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,
SemaphoreP_pend(configSemaphore, SemaphoreP_WAIT_FOREVER);
}
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:
- TX (Header) –> TX (Payload)
- 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:
- Frequency of the first TX (Header)
- Identifier of the PHY the payload is transmitted on as per WiSUN spec
- 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>
#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)
#include <ti/trx/wisun/wisun_delta_tables_JP.h>
#define MDR_HEADER_OPTION_MASK TRX_PHY_FEATURE_FSK_MODE_2B_WISUN
#define MDR_HEADER_MODEM TRX_RadioCommand_Modem_FSK
#define MDR_HEADER_CHANNEL 10
#define MDR_PACKET_OPTION_MASK TRX_PHY_FEATURE_OFDM_OPTION_2_WISUN
#define MDR_PACKET_MODEM TRX_RadioCommand_Modem_OFDM
#define MDR_PACKET_RATE TRX_PayloadHeader_SunOFDM_Rate_MCS5
#define RSSI_THRESHOLD (-80) // RSSI threshold used for CS. In this example, the same threshold is used for both the
#define RF_CONFIG_ID (1U)
#define DELTA_TABLE_CONFIG_ID (2U)
#define MDR_HEADER_STREAM_ID (0U)
#define MDR_PACKET_STREAM_ID (1U)
#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
uint8_t txMdrPacket[TX_PACKET_LENGTH] = {0, 0, 0, 0};
{
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);
}
{
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);
}
{
uint8_t mdrHeaderChannelPlanIdIdx = map->
mappingTable[mdrHeaderIdx].channelPlanIdIdx;
int16_t mdrHeaderChannelPlanId = get_wisun_channelPlanId_from_channelPlanIdIdx_JP(mdrHeaderChannelPlanIdIdx);
return(mdrHeaderChannelPlanId);
}
{
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)
{
uint16_t minMdrPacketChan;
uint16_t maxMdrPacketChan;
if (mdrPacketChannelPlanId == 21)
{
minMdrPacketChan = 9U;
maxMdrPacketChan = 37U;
}
if (mdrPacketChannelPlanId == 22)
{
minMdrPacketChan = 4U;
maxMdrPacketChan = 17U;
}
if (mdrPacketChannelPlanId == 23)
{
minMdrPacketChan = 3U;
maxMdrPacketChan = 11U;
}
if (mdrPacketChannelPlanId == 24)
{
minMdrPacketChan = 2U;
maxMdrPacketChan = 8U;
}
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);
}
{
pCmdMdrCsHeader->
slot = CMD_CS_MDR_HEADER_SLOT;
{
}
else
{
}
}
{
pCmdMdrTxHeader->
slot = CMD_TX_MDR_HEADER_SLOT;
}
{
pCmdMdrCsPacket->
slot = CMD_CS_MDR_PACKET_SLOT;
{
{
}
else
{
}
}
else
{
{
case TRX_PHY_FEATURE_OFDM_OPTION_2_WISUN:
break;
case TRX_PHY_FEATURE_OFDM_OPTION_3_WISUN:
break;
case TRX_PHY_FEATURE_OFDM_OPTION_4_WISUN:
default:
break;
}
}
}
{
pCmdMdrTxPacket->
slot = CMD_TX_MDR_PACKET_SLOT;
{
}
{
}
else
{
while(1);
}
}
int main(void)
{
uint32_t *pData = (uint32_t *)RF_CONFIG_PTR;
uint8_t mdrHeaderIdx = getMdrHeaderIdx(map);
int16_t mdrHeaderChannelPlanId = getMdrHeaderChannelPlanId(map, mdrHeaderIdx);
int32_t mdrHeaderTxFrequency = get_wisun_frequency_from_channel_JP(mdrHeaderChannelPlanId, MDR_HEADER_CHANNEL);
uint8_t mdrPacketIdx = getMdrPacketIdx(map);
int16_t mdrPacketChannelPlanId = getMdrPacketChannelPlanId(map, mdrPacketIdx);
uint32_t mdrPacketTxFrequency = getMdrPacketTxFrequency(mdrPacketChannelPlanId, mdrHeaderTxFrequency);
int32_t mdrPacketDeltaTxFrequency = (int32_t)(mdrPacketTxFrequency - mdrHeaderTxFrequency);
uint16_t newPhyId = map->
mappingTable[mdrPacketIdx].mdrByte;
setupCmdMdrCsHdr(&(cmdCsMdrHeader), mdrHeaderTxFrequency);
setupCmdMdrTxHdr(&(cmdTxMdrHeader), newPhyId);
setupCmdMdrCsPacket(&(cmdCsMdrPacket), mdrPacketTxFrequency);
setupCmdMdrTxPacket(&(cmdTxMdrPacket), mdrPacketTxFrequency);
}
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:
- WiSUN PHY Configuration Settings
- WiSUN MDR Delta Table
Several pieces of information are also required to create the RX command:
- Frequency of the first RX (Header)
- 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.
#include <ti/drivers/GPIO.h>
#include <ti/drivers/dpl/SemaphoreP.h>
#include "ti_drivers_config.h"
#include <ti/trx/wisun/wisun_delta_tables_JP.h>
#define MDR_HEADER_OPTION_MASK TRX_PHY_FEATURE_FSK_MODE_2B_WISUN
#define MDR_HEADER_MODEM TRX_RadioCommand_Modem_FSK
#define MDR_HEADER_CHANNEL 10
#define RX_PAYLOAD_LENGTH (10U) // Maximum payload in a receive operation
#define REPEAT_MODE (1U) // 1: Enable
#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)
#define RF_CONFIG_ID (1U)
#define DELTA_TABLE_CONFIG_ID (2U)
#define MDR_PACKET_STREAM_ID (0U)
#define CMD_RX_MDR_PACKET_SLOT (0U)
#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)
uint8_t rxBuffer[RX_PACKET_LENGTH] = {0};
uint8_t rxPayload[RX_PAYLOAD_LENGTH];
uint8_t rxRssi[RSSI_SIZE_BYTES];
uint8_t rxTimestamp[TIMESTAMP_SIZE_BYTES];
uint16_t eventSyncFrameDetected = 0U;
{
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);
}
{
uint8_t mdrHeaderChannelPlanIdIdx = map->
mappingTable[mdrHeaderIdx].channelPlanIdIdx;
int16_t mdrHeaderChannelPlanId = get_wisun_channelPlanId_from_channelPlanIdIdx_JP(mdrHeaderChannelPlanIdIdx);
return(mdrHeaderChannelPlanId);
}
{
pCmdMdrRxHeader->
slot = CMD_RX_MDR_PACKET_SLOT;
}
static void sfdCallback(uint_least8_t index)
{
GPIO_write(CONFIG_GPIO_RLED, 1);
eventSyncFrameDetected++;
GPIO_write(CONFIG_GPIO_RLED, 0);
}
int main(void)
{
uint32_t *pData = (uint32_t *)RF_CONFIG_PTR;
uint8_t mdrHeaderIdx = getMdrHeaderIdx(map);
int16_t mdrHeaderChannelPlanId = getMdrHeaderChannelPlanId(map, mdrHeaderIdx);
int32_t mdrHeaderRxFrequency = get_wisun_frequency_from_channel_JP(mdrHeaderChannelPlanId, MDR_HEADER_CHANNEL);
int16_t baseIdx = get_wisun_delta_table_baseindex_JP(mdrHeaderChannelPlanId, MDR_HEADER_CHANNEL);
setupCmdMdrRxHdr(&cmdMdrRxHeader, mdrHeaderRxFrequency, baseIdx);
GPIO_setConfig(CONFIG_GPIO_SFD, GPIO_CFG_IN_PU | GPIO_CFG_IN_INT_RISING);
GPIO_setCallback(CONFIG_GPIO_SFD, sfdCallback);
GPIO_enableInt(CONFIG_GPIO_SFD);
};
}
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:
{
{
eventError++;
SemaphoreP_post(rxSemaphore);
}
uint32_t *pData = (uint32_t *)RF_CONFIG_PTR;
uint8_t mdrHeaderIdx = getMdrHeaderIdx(map);
int16_t mdrHeaderChannelPlanId = getMdrHeaderChannelPlanId(map, mdrHeaderIdx);
int32_t mdrHeaderRxFrequency = get_wisun_frequency_from_channel_JP(mdrHeaderChannelPlanId, MDR_HEADER_CHANNEL);
{
{
eventRxBufFull++;
}
else
{
if((uintptr_t)NULL != pCmdOrData)
{
int8_t *rssi = (int8_t *)endOfPayload;
uint32_t *timestamp = (uint32_t *)(endOfPayload + sizeof(int8_t));
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 = mdrByte ^ rate;
}
{
if(mdrByte != 0)
{
uint8_t i;
{
{
break;
}
}
}
}
{
if(mdrByte != 0)
{
uint8_t i;
{
{
break;
}
}
}
}
}
}
{
SemaphoreP_post(rxSemaphore);
eventLastCmdDone++;
switch(cmdStatus)
{
commandStatus_GracefulStopTimeout++;
break;
commandStatus_GracefulStopApi++;
break;
commandStatus_GracefulStopScheduling++;
break;
commandStatus_HardStopApi++;
break;
commandStatus_HardStopScheduling++;
break;
default:
break;
}
}
{
eventCmdDone++;
}
}