TI NESB User’s Guide

This guide provides example usage of NESB protocol, which supports two-way data packet communication. NESB also provides features such as package acknowledgement, automatic retransmission of lost packets, and variable packet length. A device that uses NESB can act either as primary transmitter (PTX) or as primary receiver (PRX).

Note

For more background information, please reference the Radio Control Layer (RCL) configuration and settings please visit:

NESB Introduction

NESB is a basic protocol which supports two-way data packet communication with the following features:

  1. Packet acknowledgement

  2. Automatic retransmission of lost packets

  3. Variable packet length

NESB provides a simple bi-directional data link in which a device can act as either primary transmitter (PTX) or as primary receiver (PRX).

To start working with NESB please install the latest SimpleLink Low Power F3 SDK, and follow the links below to get started with development on the TX, or RX side.

NESB PTX

A PTX on the CC23xx acts as the main transmitter in an NESB network. Depending on user configuration, the handler acts similarly to the Generic Tx Command handler, and an advantage of NESB is that it can automatic retransmit unacknowledged packets.

The following describes how to configure and use NESB, with example code demonstrating its use.

Note

For more information related to NESB PTX please visit NESB PTX Command Handler

Summary

To start developing a program with NESB PTX start with the rfPacketTx example provided in the SimpleLink Low Power F3 SDK found in: example->rtos->CC23xx->prop_rf folder. We will be modifying this example to provide functionality with NESB.

Initial synchronization

In order to submit a PTX command, the following steps must have taken place:

  1. RCL has been initialized (see RCL_init()) and a handle must exist (see RCL_open()).

  2. The RCL_CMD_NESB_PTX_t command has been initialized and configured.

  3. The Tx buffer has been set up.

  4. An RX buffer has been set up to receive the ACK (if configuration demands it).

Once these steps have been completed, RCL_Command_submit and RCL_Command_pend are called to effectively send a packet and then wait for the command to conclude.

As with any command handler, the application must build packets so that they are compatible with the internal packet format used in the LRF. Having said this, NESB packets also have a specific packet format that needs to be considered at the application level.

Program flow

  1. Initialize and open RCL.

  2. Set RCL settings.
    • Set frequency.

    • Set PHY.

    • Set Syncword.

    • Set scheduling.

    • Set status.

    • Set FS.

    • Set TX_POWER.

    • Set auto retransmit mode to ACK.

    • If set to ACK, set numbers of transmits, and delay.

    • Set hdrConf.

  3. Set up the multibuffer.

  4. Clear the multiBuffer for use later.

  5. Set NESB status update.

  6. Set global variable (stats) for use in debugging.

  7. Set RCL callback.

  8. Set txPacket structure.

  9. Set up green GPIO (CONFIG_GPIO_GLED).

  10. Set number of packets (100).

  11. In a for loop, create a packet with random payload.

  12. Set packet to transmit.

  13. Submit RCL tx command.

  14. Pend on command completion.

  15. Callback toggle CONFIG_GPIO_GLED.

  16. If ACK received, clear for now, and clear multibuffer.

  17. Repeat steps 11 - 17 until 100 packets are sent.

Example code snippets

In order to use NESB you first need to include the RCL libraries and drivers. These include, but are not limited to:

  • RCL.h

  • RCL_Scheduler.h

  • rcl/commands/generic.h

/* TI Drivers */
#include <ti/drivers/GPIO.h>
#include <ti/drivers/rcl/RCL.h>
#include <ti/drivers/rcl/RCL_Scheduler.h>
#include <ti/drivers/rcl/commands/generic.h>

Next you will need to define the packet configuration, such as how long the packets will be, the number of entries, pad bytes, packet TX interval, and more.

/* Packet TX Configuration */
#define MAX_LENGTH              (30U) // Length, match with RX
#define NUM_DATA_ENTRIES        (2U)  // Number of data entries
#define NUM_PAD_BYTES           (3U)  // Number of pad bytes

/* Header length */
#define HDR_LEN                 (2U)
#define PACKET_INTERVAL     500000  /* Set packet interval to 500000us or 500ms */

#define FS_OFF                  (1U)  // 0: On, 1: Off
#define FREQUENCY               (2470000000U) // 2470 MHz
#define TX_POWER (5U)

#define TX_CONFIG_SYNCWORD      (0xABF95EB6)
#define TX_CONFIG_ADDR          (0xFE1E3C)
#define TX_CONFIG_AUTO_ACK      (3) // 3: Always listen for ACK and retransmit if missing.
#define TX_CONFIG_MAX_RETRANS   (40)
#define TX_CONFIG_RETRANS_DELAY (50000)
#define NESB_HDR_NUM_PAD        (3) // numPad
#define NESB_HDR_LEN            (2) // (numHdrBits + 7) / 8
#define NESB_ADDR_LEN           (3)
#define NESB_NO_ACK_BIT         (0)

Now let’s define some variable declarations to be used within the code.

/***** Variable declarations *****/
RCL_CmdNesbPtx     txCmd; // RCL Commands
RCL_StatsNesb      nesbStats; //stats for use in debug

#if defined(USE_NESB) && (TX_CONFIG_AUTO_ACK > 0)
#define NUM_DATA_ENTRIES    (1)
#define BUFF_STRUCT_LENGTH  (32)
RCL_MultiBuffer * nesbAckMultiBuffer; //define the multibuffer
uint32_t nesbAckBuffer[NUM_DATA_ENTRIES][BUFF_STRUCT_LENGTH/4];
#endif

/* RCL Client used to open RCL */
static RCL_Client  rclClient;

/* TX packet buffer */
uint32_t packet[NUM_DATA_ENTRIES][3 + ((MAX_LENGTH + 10)/ 4)];

/* Counters for RCL event callback */
volatile uint32_t gCmdDone = 0;     // Command done

In order to respond to the ACK (acknowledgement) from the RX device, we can use a callback function, this will also control the blinking that shows each packet being transmitted.

/***** Callback Functions *****/
void defaultCallback(RCL_Command *cmd, LRF_Events lrfEvents, RCL_Events rclEvents)
{
    if (rclEvents.lastCmdDone)
    {
        gCmdDone += 1;

        GPIO_toggle(CONFIG_GPIO_GLED); //toggles the green led, this is what blinks 50 times
    }
    if (cmd->cmdId == RCL_CMDID_NESB_PTX && rclEvents.rxEntryAvail) //red if rx ACK
    {
        // ack received, just clear it for now

        RCL_CmdNesbPtx *pNesbTx         =   (RCL_CmdNesbPtx *) cmd;
        RCL_MultiBuffer * multiBuffer   =   RCL_MultiBuffer_head(&(pNesbTx->rxBuffers));

        RCL_MultiBuffer_clear(multiBuffer); //clear ACK response
    }
}

Inside the main thread we first initialize the RCL, and assign a handle and then open the RCL through the use of the following lines.

/* Initialize and open RCL */
RCL_init();
RCL_Handle rclHandle = RCL_open(&rclClient, &LRF_configNesb);
txCmd = RCL_CmdNesbPtx_DefaultRuntime();

Then we can set the relative configurable features such as frequency, PHY, syncword for the RCL. In order for the TX side to communicate successfully with the RX side you are required to match these, as if they are not matched the TX/RX cannot communicate with each other.

txCmd.rfFrequency = FREQUENCY; //2470000000U
txCmd.common.phyFeatures = RCL_PHY_FEATURE_SUB_PHY_1_MBPS_NESB;
txCmd.syncWord = TX_CONFIG_SYNCWORD;

We also need to set how commands are sent to the RCL, ACK mode, and FS mode. ACK mode 3 will always listen for ACK and retransmit if missing.

/* Start command as soon as possible */
txCmd.common.scheduling = RCL_Schedule_Now;
txCmd.common.status = RCL_CommandStatus_Idle;

txCmd.config.fsOff = FS_OFF; // Turn off FS
txCmd.txPower.dBm = TX_POWER;

txCmd.config.autoRetransmitMode = TX_CONFIG_AUTO_ACK; //set to 3 for auto ACK
if (TX_CONFIG_AUTO_ACK > 0)
{
    txCmd.maxRetrans    = TX_CONFIG_MAX_RETRANS; //set the (max) number of retransmit
    txCmd.retransDelay  = TX_CONFIG_RETRANS_DELAY; //set the delay
}
txCmd.config.hdrConf = 1;

Now that we have completed our setup, and defined our settings we need to setup the RX buffer (within TX) to receive ACK packets from the RX device, as well as clearing the multibuffer for smooth operation.

 /* Set up Rx buffer to receive ACK packets */
uint32_t i;
for (i = 0; i < NUM_DATA_ENTRIES; i++)
{
    RCL_MultiBuffer *multiBuffer = (RCL_MultiBuffer *)nesbAckBuffer[i];
    RCL_MultiBuffer_init(multiBuffer, BUFF_STRUCT_LENGTH);
    RCL_MultiBuffer_put(&txCmd.rxBuffers, multiBuffer);
}

/* Clear multibuffer before starting the operation */
RCL_MultiBuffer *multiBuffer = RCL_MultiBuffer_head(&txCmd.rxBuffers);
while (multiBuffer != NULL)
{
    RCL_MultiBuffer_clear(multiBuffer);
    multiBuffer = RCL_MultiBuffer_next(multiBuffer);
}

Next we need to set the callback to trigger on last command done.

/* Callback triggers on last command done */
txCmd.common.runtime.callback = defaultCallback;
txCmd.common.runtime.rclCallbackMask.value = RCL_EventLastCmdDone.value;
txCmd.common.runtime.rclCallbackMask.value |= RCL_EventRxEntryAvail.value;

Now let’s set up the RCL TX buffer packet, as well as the green LED config. The LED is setup in such a fashion that it toggles in the defaultCallback pending on last command completion, so when testing the LED blinks 50 times, which indicates 100 packets sent.

RCL_Buffer_TxBuffer *txPacket = (RCL_Buffer_TxBuffer *)&packet;
/* setup the GPIO for green LED */
GPIO_setConfig(CONFIG_GPIO_GLED, GPIO_CFG_OUT_STD | GPIO_CFG_OUT_LOW);
GPIO_write(CONFIG_GPIO_GLED, CONFIG_GPIO_LED_OFF);

In order to send the bytes we make use of a for loop, this for loop is defined around the number of packets, 100 in this case. The for loop executes 100 times, filling each packet and sending it to the CC23xx RX side. Alternatively you could change the for loop to a while(1) loop to send packets forever. RCL_TxBuffer_init (txData) will initialize a TX buffer entry for use by the RCL, consisting of the number of padding bytes in front of a packet, number of header bytes to hold, and the number of payload bytes to hold.

for (i = 0; i < numPkt; i++) //alternatively you could make this a while(1) to loop forever
    /* Create packet with random payload, similar to generatebytes in previous example */
    uint8_t *txData;
    txData = RCL_TxBuffer_init(txPacket, NUM_PAD_BYTES, HDR_LEN, MAX_LENGTH);

    static uint8_t seqNum = 0;

    union
    {
        uint32_t value;
        uint8_t bytes[4];
    } nesbAddress;
    union
    {
        uint16_t value;
        uint8_t bytes[2];
    } nesbHeader;
    uint32_t payloadLength = MAX_LENGTH - NESB_ADDR_LEN;
    uint32_t pkt_len = payloadLength + NESB_ADDR_LEN;
    RCL_Buffer_TxBuffer *txBuffer = (RCL_Buffer_TxBuffer *)&packet[0];
    txData = RCL_TxBuffer_init(txBuffer, NESB_HDR_NUM_PAD, NESB_HDR_LEN, pkt_len);
    nesbHeader.value = (txCmd.config.hdrConf) ? ((pkt_len << 3) | (seqNum << 1) | NESB_NO_ACK_BIT) : ((pkt_len << 3) | NESB_NO_ACK_BIT);
    /* Write NESB header to buffer */
    for (int i = 0; i < NESB_HDR_LEN; i++)
    {
        txData[i] = nesbHeader.bytes[i];
    }
    /* Write NESB address to buffer */
    nesbAddress.value = TX_CONFIG_ADDR;
    for (int i = 0; i < NESB_ADDR_LEN; i++)
    {
        txData[NESB_HDR_LEN + i] = nesbAddress.bytes[i];
    }

    seqNum = (seqNum + 1) % 4;


    /* Write NESB payload */
    populateTestBytes(&txData[NESB_HDR_LEN + NESB_ADDR_LEN], payloadLength);


    /* Set packet to transmit */
    RCL_TxBuffer_put(&txCmd.txBuffers, txPacket);

    txCmd.common.status = RCL_CommandStatus_Idle;

    /* Submit command */
    RCL_Command_submit(rclHandle, &txCmd);

    /* Pend on command completion */
    RCL_Command_pend(&txCmd);
    //sleep for some time...
    usleep(PACKET_INTERVAL);
}

Finally, the populateTestBytes function fills the packets with a random payload ready to be used during transmit.

static void populateTestBytes(void *input, uint32_t len)
{

// incrementing bytes
    {
      //static uint8_t byte = 0x00;
      uint8_t byte = 0x00;

        uint8_t *bytePointer = (uint8_t *)input;
        for(uint32_t i = 0; i < len; i++)
        {
        bytePointer[i] = byte & 0xFF;
        byte += 1;
    }
}

NESB PRX

A Primary Receiver (PRX) CC23xx normally acts as the main receiver in an NESB network. Depending on user configuration, the handler acts similarly to the Generic Rx Command handler, or it can add automatic acknowledgement and address filtering.

The following guide is an introduction into how to use NESB protocol with TI devices using RCL command structure.

Note

For more information regarding NESB, or RCL please visit: NESB Command Handlers or Radio Control Layer.

Summary

To start developing a program with NESB PRX start with the rfPacketRx example provided in the SimpleLink Low Power F3 SDK example->rtos->CC23xx->prop_rf folder. We will be modifying this example to provide functionality with NESB.

Example Usage

In order to submit a PRX command, the following steps must have taken place:

  1. RCL has been initialized (See RCL_init()) and a handle must exist (See RCL_open()).

  2. The RCL_CMD_NESB_PRX_t command has been initialized and configured.

  3. A Multibuffer has been set up to receive the incoming packets.

RCL_Command_submit and RCL_Command_pend are called to effectively receive a packet, and then wait for the command to conclude.

As with any command handler, the application must build packets so that they are compatible with the internal packet format used in the LRF. Having said this, NESB packets also have a specific packet format that needs to be considered at the application level.

This can be accomplished by using a struct to define the various fields that need to be considered when building or checking the content of the packet.

Steps

  1. Import the rfPacketRx

  2. Implement required libraries and drivers for NESB configuration

  3. Implement defines, and variable declarations

  4. Implement and match the defines such as packet length, structure, and syncword (among others) with rfPacketTx side

  5. Create the hdrdef structure for the packets

  6. Setup the NESB PRX callback

  7. Implement the runNesbPrx() function

  8. Adapt main.c to account for changes

Example code usage

The RCL driver library is needed to use NESB functions, found below are the drivers included in this example.

/* TI Drivers */
#include <ti/drivers/GPIO.h>
#include <ti/drivers/rcl/RCL.h>
#include <ti/drivers/rcl/RCL_Scheduler.h>
#include <ti/drivers/rcl/commands/generic.h>
#include "ti_drivers_config.h"

Next we will need to provide definitions such as maximum packet length, and number of entries for NESB packet structure. These will need to match between TX and RX in order for packets to be successfully received between the devices. We also need to define the packet length, padding bytes, multibuffer size, frequency, and syncword (among other definitions).

/***** Defines *****/
/* Packet RX Configuration */
#define MAX_LENGTH              (30U) // Max packet length
#define NUM_DATA_ENTRIES        (8U)  // Number of data entries

/* RCL buffer length */
#define BUFF_STRUCT_LENGTH      (2048U)

/* Indicates if FS is off */
#define FS_OFF                  (1U)  // 0: On, 1: Off

/* Indicates if RX packet is not stored */
#define DISCARD_RX_PACKET       (0U)  // 0: Store received packets in rxBuffers, 1: Discard packets
/* Number of packets to initialize for multi buffer */
#define NUM_OF_PACKETS          (1U)

#define PKT_LEN 200
#define HDR_LEN 3
#define NUM_PKT 4
#define NUM_PAD 3
#define NUM_RX_BUF 1
#define MULTI_BUF_SZ 2048
#define FREQUENCY 2470000000
#define SYNCWORD 0xABF95EB6
#define NESB_ADDR 0xFE1E3C

The hdrdef structure is as follows:

#define NESB_hdrDef_Default()   \
{                               \
    .numHdrBits = 11,           \
    .numLenBits = 8,            \
    .lenPos = 3,                \
    .lenOffset = 0,             \
    .numPad = 3,                \
}
#define NESB_hdrDef_DefaultRuntime() (ExampleRCL_NesbHdrDef) NESB_hdrDef_Default()

typedef struct {
    uint8_t numHdrBits;
    uint8_t numLenBits;
    uint8_t lenPos;
    int8_t lenOffset;
    uint8_t numPad;
} ExampleRCL_NesbHdrDef;

Now let’s define variable declarations, which will be used later in the main function(s) of the code. Since these are defined as globals you can easily see the data while in debugging mode.

/***** Variable Declarations *****/
/* RCL Commands */
RCL_CmdGenericRx    rxCmd;
RCL_CmdNesbPrx cmd;// RX command
RCL_StatsGeneric    stats;              // Statistic command

/* RCL Client used to open RCL */
static RCL_Client  rclClient;

RCL_MultiBuffer *multiBuffer;

/* Counters for RCL event callback */
volatile uint32_t gCmdDone = 0;         // Command done
volatile uint32_t gPktRcvd = 0;         // Packet received

/* Buffer used to store RCL packet */
uint32_t buffer[NUM_DATA_ENTRIES][BUFF_STRUCT_LENGTH/4];

Next let’s define the NESB PRX callback, this callback will check the last received command, check for a received entry, and then shift the data in a global structure, and clear the entry to move onto the next entry.

/***** Callback Functions *****/
void defaultCallback(RCL_Command *cmd, LRF_Events lrfEvents, RCL_Events rclEvents)
{
    if (rclEvents.lastCmdDone)
    {
        gCmdDone += 1;
    }
    if (rclEvents.rxEntryAvail)
    {
        gPktRcvd += 1; //used in global stats to indicate packets received
        GPIO_toggle(CONFIG_GPIO_RLED); //Toggle led
        if (cmd->cmdId == RCL_CMDID_NESB_PRX)
        {
            RCL_CmdNesbPrx  * nesbRxCmd     =   (RCL_CmdNesbPrx *) cmd;
            List_List consumedBuffers = { 0 };

            uint32_t timestamp  =  nesbRxCmd->stats->lastTimestamp;


            uint8_t * pDataEntry;
            pDataEntry = (uint8_t * ) RCL_MultiBuffer_RxEntry_get( &nesbRxCmd->rxBuffers, &consumedBuffers );

            RCL_MultiBuffer_ListInfo listInfo;
            RCL_MultiBuffer_ListInfo_init( &listInfo, &consumedBuffers );

            while (pDataEntry) //while we have data to process
            {
                RCL_Buffer_DataEntry * pRclBufDataEntry = (RCL_Buffer_DataEntry *) pDataEntry;

                const uint32_t headerLen = 2, addrLen = 3; //packet has metadata in it, length of packet, and address, etc

                uint32_t startOfData_i;
                uint32_t lengthOfData;

                startOfData_i =
                        sizeof(pRclBufDataEntry->length)    +
                        sizeof(pRclBufDataEntry->numPad)    +
                        pRclBufDataEntry->numPad            +
                        headerLen  +  addrLen ;
                // startOfData_i should now index to the first data byte.

                //just looking at the data itself in the packet from TX

                lengthOfData =
                        pRclBufDataEntry->length            -
                        sizeof(pRclBufDataEntry->numPad)    -
                        pRclBufDataEntry->numPad            -
                        (headerLen  +  addrLen);

                uint8_t * startOfData  =  &pDataEntry[startOfData_i];
                pDataEntry = (uint8_t *) RCL_MultiBuffer_RxEntry_next( &listInfo );
            }
            RCL_MultiBuffer * multiBuffer   =   RCL_MultiBuffer_head(&(nesbRxCmd->rxBuffers));
            RCL_MultiBuffer_clear(multiBuffer);
        }
    }

}

Now let’s initialize RCL, and set up the configuration settings for NESB (match the TX side). Optional is to set up a useful global “stats” for use in debugging, to show that we received 100 packets ok. In RCL_MultiBuffer_put(&cmd.rxBuffers, multiBuffer); we take the data entry, and prepare to re-transmit the entry.

#define NUM_RX_BUF 1
#define MULTI_BUF_SZ 2048

void runNesbPrx(void)
{
    uint32_t rxMultiBuffer[NUM_RX_BUF][MULTI_BUF_SZ / 4];
    List_List multiBuffers = { 0 };

    RCL_init();
    RCL_Handle rclHandle = RCL_open(&rclClient, &LRF_configNesb);

    /* Declare command */
    cmd = RCL_CmdNesbPrx_DefaultRuntime();
    RCL_StatsNesb rxStats;
    rxStats = RCL_StatsNesb_DefaultRuntime();

    /* Command configuration */
    cmd.common.scheduling = RCL_Schedule_Now;
    cmd.common.status = RCL_CommandStatus_Idle;
    cmd.common.runtime.callback = defaultCallback;
    cmd.config.discardRxPackets = DISCARD_RX_PACKET;
    cmd.config.fsOff = 1U;
    cmd.common.runtime.rclCallbackMask.value = RCL_EventLastCmdDone.value | RCL_EventRxEntryAvail.value;
    cmd.rfFrequency = FREQUENCY;
    cmd.syncWordA = SYNCWORD;
    cmd.syncWord[0].address = NESB_ADDR; // Set NESB address
    cmd.addrLen = 4; // Consider 4 bytes for the NESB address
    cmd.syncWord[0].seqValid = 1; // Only accept packets with sequence number and CRC different from the previous one
    cmd.syncWord[0].autoAckMode = 3; // Enable auto-acknowledgement regardless of received NO_ACK
    cmd.common.timing.relGracefulStopTime = 0;
    cmd.config.repeatNok = 1; // Does radio want to restart the RX if it has received a packet with bad (Nok) CRC
    cmd.config.repeatOk = 1; // Does radio want to restart RX or do we just consider RX as complete

    /* Stats configuration, useful for debugging */
    rxStats.config.accumulate = 1;
    rxStats.nTx = 0;
    rxStats.nRxOk = 0;
    rxStats.nRxNok = 0;
    rxStats.nRxIgnored = 0;
    rxStats.nRxAddrMismatch = 0;
    rxStats.nRxBufFull = 0;

    stats.config.accumulate = 1; //how often stats update.
    stats.config.activeUpdate = 1;

    /* Set RX command statistics structure */
    cmd.stats = &stats;
    GPIO_setConfig(CONFIG_GPIO_RLED, GPIO_CFG_OUT_STD | GPIO_CFG_OUT_LOW);
    GPIO_write(CONFIG_GPIO_RLED, CONFIG_GPIO_LED_OFF);
    /* Set up Rx (multi)buffer */

    cmd.rxBuffers = multiBuffers;

    for(int i = 0; i < NUM_OF_PACKETS; i++)
    {
        multiBuffer = (RCL_MultiBuffer *) buffer[i];
        RCL_MultiBuffer_init(multiBuffer, BUFF_STRUCT_LENGTH);
        RCL_MultiBuffer_put(&cmd.rxBuffers, multiBuffer);
    }
    cmd.common.status = RCL_CommandStatus_Idle;
    RCL_Command_submit(rclHandle, &cmd);

    /* Wait for command to conclude  */
    RCL_Command_pend(&cmd);
    while(1)
    {

    }

}

For more help

For any help or questions regarding NESB please visit TI E2E.