In this tutorial, we're going to combine what we had from the previous tutorial. We're going to switch between transmit and receive, and we're going to use a few peripherals on the WSTK as a user interface.
Our goal is to transmit a predefined packet when either of the buttons are pressed, and print the packet to the serial console when received. We're also going to toggle LEDs on finished Rx/Tx, but we ignore errors.
You can find a SimpleTRX example in Simplicity Studio, which has similar purpose. However, the result of this tutorial will be somewhat different.
Preparations
We're going to start from the usual, the Simple RAIL with HAL example. However, we'll need some modifications in the Hardware Configurator because we'll need the UART bridge and buttons, which are not enabled by default. To do that, enable the following defaultMode peripheral modules:
Virtual COM Port
Button
Next, we'll have to include a few things to have these peripherals, and while some of the peripherals are automatically initialized, not all, so we're going to do that in a function:
#include "bsp.h"
#include "retargetserial.h"
#include "gpiointerrupt.h"
#include <stdio.h>
typedef struct ButtonArray{
GPIO_Port_TypeDef port;
unsigned int pin;
} ButtonArray_t;
static const ButtonArray_t buttonArray[BSP_BUTTON_COUNT] = BSP_BUTTON_INIT;
void gpioCallback(uint8_t pin);
void peripheralInit(){
RETARGET_SerialCrLf(1); //converts \n to \r\n
//set up button GPIOs to input with pullups
for ( int i= 0; i < BSP_BUTTON_COUNT; i++){
GPIO_PinModeSet(buttonArray[i].port, buttonArray[i].pin, gpioModeInputPull, 1);
}
//set up interrupt based callback function on falling edge
GPIOINT_Init();
GPIOINT_CallbackRegister(buttonArray[0].pin, gpioCallback);
GPIOINT_CallbackRegister(buttonArray[1].pin, gpioCallback);
GPIO_IntConfig(buttonArray[0].port, buttonArray[0].pin, false, true, true);
GPIO_IntConfig(buttonArray[1].port, buttonArray[1].pin, false, true, true);
}
As you see, gpioCallback() will be called on button press.
Radio init
By default, after receiving or transmitting a packet, RAIL will switch to idle mode (i.e., turn off the radio). Since we want it to keep receiving future packets, we can use RAIL's auto state transition feature with the RAIL_SetRxTransitions and RAIL_SetTxTransitions APIs.
We're basically saying, after the packets are received/transmitted, we want to return to Rx. We're also enabling some events as we've seen before. The only new one is RAIL_EVENT_CAL_NEEDED - it will have it's own tutorial; for now, it is important to know that it should always be implemented to have the maximum available performance.
There's not much new here. We start by initializing everything then going to Rx mode. When a button is pressed startTx will be true, which will start transmitting a packet (changing from Rx to Tx mode). Note that the variable startTx is volatile, because it's changing in interrupt context. If you're unfamiliar with this practice, look it up; it's very important for low-level embedded applications.
In the event handler, we blink LEDs on Rx/Tx completion. In the CAL_NEEDED handler we're basically saying: If any calibration is needed, run it. Again, this is important to get the maximum available performance, but we won't go into details for now.
At this point, you should see LEDs blinking on both devices when you press a button on either of them. So at this point, we added a single concept: we return to Rx state both after receiving a packet, and after transmitting one. This enabled a combined Rx/Tx application. However, there's one important thing we didn't do so far: we did not download the received packet.
Downloading the Packet
Receive FIFO
We already configured Tx FIFO multiple ways. RAIL also implements an Rx FIFO. By default, it's 512B, allocated by the RAIL library. We will use that, but it is configurable. See RAILCb_SetupRxFifo() and RAIL_SetRxFifo() in the API documentation for details.
By default, you cannot use the whole 512B, as RAIL adds a few bytes to each packet to store timestamp, RSSI and similar information in the FIFO (sometimes we call this "appended info").
We're working on what we call packet mode. It's the simpler, and recommended way, but it's not possible to handle packets bigger than 512B with this method,; in which case FIFO mode should be used. We return to that topic in a later tutorial.
The Packet Handler
The FIFO is accessible with RAIL_RxPacketHandle_t variables or packet handles. Obviously, we don't have the handle of the packet we just received, but we have "sentinel" handles:
RAIL_RX_PACKET_HANDLE_OLDEST
RAIL_RX_PACKET_HANDLE_NEWEST
RAIL_RX_PACKET_HANDLE_INVALID
The names are self-explanatory.
When to Read the FIFO
Let's say we want to download the packet when it's fully received (although it's possible to access it during reception). We must let RAIL know this in the event RAIL_EVENT_RX_PACKET_RECEIVED. If we don't, RAIL automatically frees the memory allocated to the packet we just received.
We have two options:
Download the packet from the event handler
Hold the packet in the FIFO and download later
While the first one is simpler, we also want to print the message on UART, which takes a long time, and that's not a good idea in interrupt context, so we'll use the second one.
To do that, let's create a volatile packetHandle, with invalid value:
We should only write the handle if it's invalid, otherwise we might lose the handle to a locked packet (and essentially lose part of the receive buffer). We'll also need a buffer to copy the packet before printing it.
The Event Handler
Let's implement the event handler so it will only store packets if packetHandle is invalid:
First, RAIL_GetRxPacketInfo will return with packetInfo, which has the length of the received packet, and pointers to download it. Note that unlike transmit, this is independent of the length coding method (fixed or variable) we used in the Configurator.
Since the receive buffer is a ring buffer, the download will be always the same 3 lines of code, which is done in the RAIL_CopyRxPacket inline function.
The API RAIL_GetRxPacketDetails will return some useful information of the packet, such as RSSI.
Finally, we release the packet, and set the handle to invalid, so the event handle can write it.
Download in the Event Handle
Downloading the packet in the event handler is essentially the same as what we did in the main loop above:
Note that packetDetails should be a global variable as well in this case.
Conclusion
With this, you can use RAIL for basic transmit and receive, which concludes the getting started series. However, RAIL can do much more - you can continue with the basic tutorials from the table of contents site.
Proprietary Knowledge Base
RAIL Tutorial 4: Combining Transmit and Receive
This tutorial builds on the following tutorials:
Please read them first if you haven't.
You can find other tutorials on the table of contents site.
In this tutorial, we're going to combine what we had from the previous tutorial. We're going to switch between transmit and receive, and we're going to use a few peripherals on the WSTK as a user interface.
Our goal is to transmit a predefined packet when either of the buttons are pressed, and print the packet to the serial console when received. We're also going to toggle LEDs on finished Rx/Tx, but we ignore errors.
You can find a SimpleTRX example in Simplicity Studio, which has similar purpose. However, the result of this tutorial will be somewhat different.
Preparations
We're going to start from the usual, the Simple RAIL with HAL example. However, we'll need some modifications in the Hardware Configurator because we'll need the UART bridge and buttons, which are not enabled by default. To do that, enable the following defaultMode peripheral modules:
Next, we'll have to include a few things to have these peripherals, and while some of the peripherals are automatically initialized, not all, so we're going to do that in a function:
As you see,
gpioCallback()
will be called on button press.Radio init
By default, after receiving or transmitting a packet, RAIL will switch to idle mode (i.e., turn off the radio). Since we want it to keep receiving future packets, we can use RAIL's auto state transition feature with the
RAIL_SetRxTransitions
andRAIL_SetTxTransitions
APIs.We're basically saying, after the packets are received/transmitted, we want to return to Rx. We're also enabling some events as we've seen before. The only new one is
RAIL_EVENT_CAL_NEEDED
- it will have it's own tutorial; for now, it is important to know that it should always be implemented to have the maximum available performance.Basic Rx and Tx Combined
There's not much new here. We start by initializing everything then going to Rx mode. When a button is pressed
startTx
will betrue
, which will start transmitting a packet (changing from Rx to Tx mode). Note that the variablestartTx
is volatile, because it's changing in interrupt context. If you're unfamiliar with this practice, look it up; it's very important for low-level embedded applications.In the event handler, we blink LEDs on Rx/Tx completion. In the CAL_NEEDED handler we're basically saying: If any calibration is needed, run it. Again, this is important to get the maximum available performance, but we won't go into details for now.
At this point, you should see LEDs blinking on both devices when you press a button on either of them. So at this point, we added a single concept: we return to Rx state both after receiving a packet, and after transmitting one. This enabled a combined Rx/Tx application. However, there's one important thing we didn't do so far: we did not download the received packet.
Downloading the Packet
Receive FIFO
We already configured Tx FIFO multiple ways. RAIL also implements an Rx FIFO. By default, it's 512B, allocated by the RAIL library. We will use that, but it is configurable. See
RAILCb_SetupRxFifo()
andRAIL_SetRxFifo()
in the API documentation for details.By default, you cannot use the whole 512B, as RAIL adds a few bytes to each packet to store timestamp, RSSI and similar information in the FIFO (sometimes we call this "appended info").
We're working on what we call packet mode. It's the simpler, and recommended way, but it's not possible to handle packets bigger than 512B with this method,; in which case FIFO mode should be used. We return to that topic in a later tutorial.
The Packet Handler
The FIFO is accessible with
RAIL_RxPacketHandle_t
variables or packet handles. Obviously, we don't have the handle of the packet we just received, but we have "sentinel" handles:The names are self-explanatory.
When to Read the FIFO
Let's say we want to download the packet when it's fully received (although it's possible to access it during reception). We must let RAIL know this in the event
RAIL_EVENT_RX_PACKET_RECEIVED
. If we don't, RAIL automatically frees the memory allocated to the packet we just received.We have two options:
While the first one is simpler, we also want to print the message on UART, which takes a long time, and that's not a good idea in interrupt context, so we'll use the second one.
To do that, let's create a volatile packetHandle, with invalid value:
We should only write the handle if it's invalid, otherwise we might lose the handle to a locked packet (and essentially lose part of the receive buffer). We'll also need a buffer to copy the packet before printing it.
The Event Handler
Let's implement the event handler so it will only store packets if packetHandle is invalid:
The API
RAIL_HoldRxPacket
will lock the packet we just received (or receiving) and return its handle.Download and Print
In the while loop, if there's something in the handle, let's download and print it:
Let's go through this, line by line:
First,
RAIL_GetRxPacketInfo
will return with packetInfo, which has the length of the received packet, and pointers to download it. Note that unlike transmit, this is independent of the length coding method (fixed or variable) we used in the Configurator.Since the receive buffer is a ring buffer, the download will be always the same 3 lines of code, which is done in the
RAIL_CopyRxPacket
inline function.The API
RAIL_GetRxPacketDetails
will return some useful information of the packet, such as RSSI.Finally, we release the packet, and set the handle to invalid, so the event handle can write it.
Download in the Event Handle
Downloading the packet in the event handler is essentially the same as what we did in the main loop above:
Note that
packetDetails
should be a global variable as well in this case.Conclusion
With this, you can use RAIL for basic transmit and receive, which concludes the getting started series. However, RAIL can do much more - you can continue with the basic tutorials from the table of contents site.
API Introduced in this Tutorial
Functions
Types and enums