Downloading Received Messages#
This tutorial is the continuation of a series started with the article Introduction. The previous chapter was about Event Handling and Automatic State Transitions.
Please read them first if you haven't.
The completed example for this article is available on GitHub. You can either import this example, as documented in the readme on Github, or follow this article and modify the example from the previous article.
In the previous tutorial, we had both transmit and receive working, but we couldn't see what we received. This tutorial will solve that problem.
We're also adding calibrations, which is recommended for all projects.
You can find a SimpleTRX example in Simplicity Studio, which has a similar purpose. However, the result of this tutorial will be somewhat different. Mainly, this tutorial is not very pedantic on checking errors returned by all functions, to make it easier to understand. However, we highly recommend checking all possible errors, therefore using the SimpleTRX example in Studio as a reference.
Enabling Calibrations#
All calibrations are enabled by default in RAIL Utility, Initialization as depicted below, but require some application code to perform them.
First, we need to add the related event during initialization to enable it (in
app_init.c
):
RAIL_Handle_t app_init(void)
{
//...
RAIL_Status_t status = RAIL_ConfigEvents(rail_handle, RAIL_EVENTS_ALL,
RAIL_EVENTS_TX_COMPLETION
| RAIL_EVENTS_RX_COMPLETION
| RAIL_EVENT_CAL_NEEDED
);
//...
}
Next, we need to perform calibrations when RAIL requests them (in
app_process.c
):
void sl_rail_util_on_event(RAIL_Handle_t rail_handle, RAIL_Events_t events)
{
//...
if (events & RAIL_EVENT_CAL_NEEDED) {
RAIL_Status_t status = RAIL_Calibrate(rail_handle,
NULL,
RAIL_CAL_ALL_PENDING);
if (status != RAIL_STATUS_NO_ERROR) {
sl_led_toggle(&sl_led_led1);
}
}
}
This is all that's needed from an application to perform all calibrations available on EFR32, to have the maximum available performance. Calibrations are detailed further in another tutorial.
Preparing for Printing#
In the previous tutorials, we already enabled LEDs and buttons, but we'll need a more user friendly output: We need a serial console. To enable that, install the Log component under Application/Utility. This should pull up its dependencies, including an IO Stream: EUSART or UART component, which is the serial driver itself.
Finally, we need to enable Virtual COM UART on the Board Control component:
This pulls up a GPIO, which will enable the USB-UART bridge on the development kit (on the motherboard, if you use a dual board development kit).
Receive FIFO#
We already configured Tx FIFO in multiple ways. RAIL also implements an Rx FIFO.
By default, it's 512 Bytes, 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 512 Bytes, 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 the receive FIFO with this method, in which case FIFO mode should be used. We return to that topic in a later tutorial.
The Packet Handle#
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_NEWEST
RAIL_RX_PACKET_HANDLE_OLDEST
RAIL_RX_PACKET_HANDLE_OLDEST_COMPLETE
RAIL_RX_PACKET_HANDLE_INVALID
Note that RAIL_RX_PACKET_HANDLE_OLDEST
and RAIL_RX_PACKET_HANDLE_NEWEST
will
always return with packet information. For example, OLDEST will return with one
of the following:
the oldest packet received, if available
the packet the radio is receiving at the moment, if available and if no fully received packet is available
the placeholder which will be used for the next packet the radio will receive, if no fully or partially received packet is available
If you don't care about upcoming or ongoing packets, you can use
RAIL_RX_PACKET_HANDLE_OLDEST_COMPLETE
. It will only return with fully received
packets.
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, it is desirable in most cases to keep large memory copy operations outside of interrupt handlers, therefore the attached example demonstrates the second method.
Note however, that if you plan to use dynamic multiprotocol, DMP, you must read the FIFO before yielding the radio. On every DMP protocol change, RAIL resets the receive FIFO. In this case, it's usually better to download the packet in the event handler.
The Event Handler#
In the event handler, we want to instruct RAIL to hold all successfully received frames:
void sl_rail_util_on_event(RAIL_Handle_t rail_handle, RAIL_Events_t events)
{
//...
if (events & RAIL_EVENTS_RX_COMPLETION) {
if (events & RAIL_EVENT_RX_PACKET_RECEIVED) {
if (RAIL_HoldRxPacket(rail_handle) == RAIL_RX_PACKET_HANDLE_INVALID) {
sl_led_toggle(&sl_led_led1);
}
sl_led_toggle(&sl_led_led0);
} else {
sl_led_toggle(&sl_led_led1); // all other events in
// RAIL_EVENTS_RX_COMPLETION are errors
}
}
}
The API RAIL_HoldRxPacket
will also return a packet handler, but we only
report an error if it did not. We don't need the handle itself, since we're
going to use sentinel handles. Keep in mind that you must release all
packets that were held; otherwise, you will lose part of your Rx fifo,
essentially leaking memory. Note also that this API shouldn't be used when
developing for dynamic multiprotocol (DMP).
Download and Print#
First, let's create a buffer we can use to download the packet:
static uint8_t rx_buffer[256];
In app_process_action()
, if there's something in the packet handle, let's
download and print it:
void app_process_action(RAIL_Handle_t rail_handle)
{
static RAIL_RxPacketHandle_t packet_handle;
static RAIL_RxPacketInfo_t packet_info;
static RAIL_RxPacketDetails_t packet_details;
//...
packet_handle = RAIL_GetRxPacketInfo(rail_handle,
RAIL_RX_PACKET_HANDLE_OLDEST_COMPLETE,
&packet_info);
if (packet_handle != RAIL_RX_PACKET_HANDLE_INVALID) {
RAIL_CopyRxPacket(rx_buffer, &packet_info);
RAIL_Status_t status =
RAIL_GetRxPacketDetails(rail_handle, packet_handle, &packet_details);
if (status != RAIL_STATUS_NO_ERROR) {
sl_led_toggle(&sl_led_led1);
}
status = RAIL_ReleaseRxPacket(rail_handle, packet_handle);
if (status != RAIL_STATUS_NO_ERROR) {
sl_led_toggle(&sl_led_led1);
}
app_log("RX");
for (int i = 0; i < packet_info.packetBytes; i++) {
app_log(" 0x%02X", rx_buffer[i]);
}
app_log("; RSSI=%d dBm\n", packet_details.rssi);
}
}
Let's go through this, line by line:
First, RAIL_GetRxPacketInfo()
will return with a valid packet_handle
if we
have a completely received frame. In that case, it also returns a usable
packet_info
which has the length of the received packet, and pointers to
download it. Note that the length will include the full frame, and is always
valid, whichever length configuration is used (fixed or variable).
Since the receive buffer is a ring buffer, the packet in the buffer might not be
in a single sequential buffer, copying from that using memcpy()
is a bit
complicated: RAIL_CopyRxPacket()
macro implements that.
The API RAIL_GetRxPacketDetails()
will return some useful information of the
packet, such as RSSI or timestamps.
Finally, we release the packet, using RAIL_ReleaseRxPacket()
.
Note that this method is safe, even if we receive a new packet while downloading
one. That packet will be downloaded and printed when app_process_action()
is
called the next time.
It is important to note here that there is a subtle difference between RAIL_PACKET_HANDLE_OLDEST and RAIL_PACKET_HANDLE_OLDEST_COMPLETE.
RAIL_PACKET_HANDLE_OLDEST as well as RAIL_PACKET_HANDLE_NEWEST become valid at beginning of frame reception. As a result, it cannot be used for packets reception easily in an active loop.
The following tab clarifies the differences between the handles:
NEWEST | OLDEST | OLDEST_COMPLETE | |
---|---|---|---|
FIFO Empty | INVALID | INVALID | INVALID |
FIFO receiving packet A | Points to packet A | Points to packet A | INVALID |
FIFO has received packet A | Points to packet A | Points to packet A | Points to packet A |
FIFO receiving packet B | Points to packet B | Points to packet A | Points to packet A |
FIFO receiving packet B and A is copied/released | Points to packet B | Points to packet B | INVALID |
While this isn't exhaustive, it helps to clarify the ambiguity between all three handles.
Download in the Event Handler#
Downloading the packet in the event handler is essentially the same as what we did in the main loop above, except we don't need to worry about releasing:
if (events & RAIL_EVENT_RX_PACKET_RECEIVED) {
RAIL_RxPacketInfo_t packet_info;
RAIL_RxPacketHandle_t packet_handle = RAIL_GetRxPacketInfo(rail_handle, RAIL_RX_PACKET_HANDLE_NEWEST, &packet_info);
RAIL_CopyRxPacket(rx_buffer, &packet_info);
RAIL_Status_t status = RAIL_GetRxPacketDetails(rail_handle, packet_handle, &packet_details);
}
Note that packet_details
should be a global variable (i.e. declared outside of
the functions to be available to both functions) 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.