Like most embedded radio platforms, EFR32 provides a way to download/upload packets during reception/transmission, respectively. So, for example, while you're sending the first couple of bytes of of your frame, you can still upload the end of the frame.
Since the radio is using ring buffers, this makes possible to handle longer frames than what fits into the buffer, and nowadays, this is pretty much the only reason to use it. A typical usecase for example is a protocol, where almost all of the frames fit into 128B buffer, but in rare cases, the protocol must handle 512B long frames. In this tutorial the examples will present how to do that.
Advanced packet mode features
If you have to look into the packet during reception (e.g. because you will use RAIL_SetFixedLength() to handle a frame length coding which is not supported by hardware), you shouldn't use FIFO mode. Instead, you can use RAIL_PeekRxPacket() to look into the packet, but still keeping it in the buffer, and only downloading it when you got the full frame.
If you want to have multiple frames in the buffer, there's also no reason to use FIFO mode: you can handle multiple frames without problems using packet mode. Before transmit, you can use the same APIs (RAIL_SetTxFifo() and RAIL_WriteTxFifo()) to write multiple packets, while you can store the packetHandles in Rx mode and keep them in buffer using RAIL_HoldRxPacket().
Differences in FIFO mode
Switching to FIFO mode doesn't actually do anything on the transmit side (since RAIL 2.0), but changing the receiver to FIFO mode slightly changes the behavior of the receive buffer: RAIL_GetRxPacketInfo() (the usual way to access the received packet) won't work, you must use RAIL_ReadRxFifo(). Also, RAIL won't provide information on the frame length, and you should be careful to read exactly as many bytes as your frame, otherwise you might corrupt the next packet: E.g. if you leave the end of the packet in the FIFO, you will probably download it as the beginning of the next packet, corrupting that frame as well.
Enabling FIFO mode
RAIL_ConfigData() can be used to switch to FIFO mode, probably at the end of your radio init function:
First, it sets up a tx buffer with RAIL_SetTxFifo, then configures RAIL to use FIFO mode. Then it sets up both rx and tx thresholds to 10%, which is usually a good value, although you might want to increase it at high data rates (RX_BUFFER_SIZE should be 512B, if you did not implement RAILCb_SetupRxFifo). Finally, it enables the two "threshold reached" event.
Using FIFO mode: transmit
On the transmit side, the idea is to fill the buffer completely, start the transmission, and fill the remaining when needed:
This is just the related code, you probably have more events handled. If you look at the calls of RAIL_WriteTxFifo(), you can see that we always try to write the whole (remaining) packet, and do not check how much space we have in the FIFO. While we could do that, using RAIL_GetTxFifoSpaceAvailable(), it's not necessary: RAIL_WriteTxFifo() will automatically stop when the FIFO is full, and return with the number of bytes successfully written.
We do pretty much the same in the event, but we're adjusting both the source and the size with txOffset. The condition txOffset < PACKET_LENGTH is important, because the FIFO events do not check packet lengths: unless you have multiple packets in the buffer, you'll get an RAIL_EVENT_TX_FIFO_ALMOST_EMPTY every time when you have just threshold amount of bytes remaining of your packet (since it's normal to have an empty buffer if you don't want to send anything).
Using FIFO mode: receive
The receive side is basically the opposite of the transmit side:
We start with an empty packetToReceive, and the first thing we receive will be a single or multiple RAIL_EVENT_RX_FIFO_ALMOST_FULL (alternatively, RAIL_EVENT_RX_SYNC1_DETECT/RAIL_EVENT_RX_SYNC2_DETECT could be handled to set up packetToReceive for reception). Each time we got that event, we download as much from the FIFO as we can, and adjust the offset (again, we could check the amount of data in the FIFO using RAIL_GetRxFifoBytesAvailable(), but it's not necessary). When we got the RAIL_EVENT_RX_PACKET_RECEIVED, we probably still have some data remaining in the FIFO, so we download that. Then we get the packetDetails, just like we do for a normal packet. We reset the offset to be ready for the next packet, both on successful receive and errors.
Since this was a fixed length setup, there was no need to figure out the length. In variable length mode, you probably want to download the length field only first, set up a global variable with the length, and continue just like the above sample.
Special data sources
For the RX buffer, you can set up special data modes:
RX_PACKET_DATA: normal mode, default
RX_DEMOD_DATA: demodulated, but not sliced data. 1B per sample
RX_IQDATA_FILTLSB: IQ data, lower 16 bits of 19. 2x2B per sample
RX_IQDATA_FILTMSB: IQ data, upper 16 bits of 19. 2x2B per sample
All of these modes have a higher sample rate than what you set up on the configurator, since the radio does about 5-10x oversampling (depending on the configuration), which is removed at the end of the demodulation chain. Keep in mind that this could result so high data rate that the MCU won't be able to move data that fast. In our tests, we could handle about 50kb/s in IQ modes. These are quite complex to use, because you basically disable parts of the demodulation chain, but sometimes it's the only possibility.
RX_DEMOD_DATA is useful if your frame format is impossible to detect with the hardware, e.g. it has no preamble. It can be also useful if you need baudrate/deviation tolerance not supported by the hardware. It's basically tapping the demodulation chain when the "analog" demodulation is done, but it's quantized to 8 bits - so if you have set up FSK2 modulation, the result will be basically an FM demodulated signal.
RX_IQDATA_FILTLSB and RX_IQDATA_FILTMSB is useful if the hardware does not support the modulation you need - you can write you're own demodulator which works from the IQ samples, but that's quite heavy task for the CPU, and usually a complicated problem to solve. When IQ mode is used, one sample includes 4B, in this sequence:
I[LSB], I[MSB], Q[LSB], Q[MSB].
If you enable any of these modes, they will start filling the FIFO immediately, as detection doesn't work without the end of the demodulator chain.
Direct mode
You can configure the radio to send the data it receives to a GPIO, and read data from a GPIO and send it through radio, making essentially a transceiver. You can enable it with RAIL_EnableDirectMode(), and it's currently fixed to portC10 and portC11 for DIN/DOUT. When you start rx, you'll see nothing on DOUT: It will only start outputting data after sync word detection. Length decoder works as well, and it will stop reception when reaches the configured length (either variable or fixed). Tx mode works similarly, but it's mostly for diagnostic purposes, as it's not possible to use a sync mode reliably without a clock signal.
There's an Asynchronous direct mode checkbox on the configurator. If it's enabled, the DOUT pin's behavior will change: It will output oversampled data whenever the radio is in rx mode. See this KBA for details on this mode.
RAIL tutorial: Special data modes
This tutorial builds on the following tutorials:
Please read them first if you haven't. You should also have some experience with SWD based debugging in Simplicity Studio.
You can find other tutorials on the table of contents site.
Like most embedded radio platforms, EFR32 provides a way to download/upload packets during reception/transmission, respectively. So, for example, while you're sending the first couple of bytes of of your frame, you can still upload the end of the frame.
Since the radio is using ring buffers, this makes possible to handle longer frames than what fits into the buffer, and nowadays, this is pretty much the only reason to use it. A typical usecase for example is a protocol, where almost all of the frames fit into 128B buffer, but in rare cases, the protocol must handle 512B long frames. In this tutorial the examples will present how to do that.
Advanced packet mode features
If you have to look into the packet during reception (e.g. because you will use
RAIL_SetFixedLength()
to handle a frame length coding which is not supported by hardware), you shouldn't use FIFO mode. Instead, you can useRAIL_PeekRxPacket()
to look into the packet, but still keeping it in the buffer, and only downloading it when you got the full frame.If you want to have multiple frames in the buffer, there's also no reason to use FIFO mode: you can handle multiple frames without problems using packet mode. Before transmit, you can use the same APIs (
RAIL_SetTxFifo()
andRAIL_WriteTxFifo()
) to write multiple packets, while you can store the packetHandles in Rx mode and keep them in buffer usingRAIL_HoldRxPacket()
.Differences in FIFO mode
Switching to FIFO mode doesn't actually do anything on the transmit side (since RAIL 2.0), but changing the receiver to FIFO mode slightly changes the behavior of the receive buffer:
RAIL_GetRxPacketInfo()
(the usual way to access the received packet) won't work, you must useRAIL_ReadRxFifo()
. Also, RAIL won't provide information on the frame length, and you should be careful to read exactly as many bytes as your frame, otherwise you might corrupt the next packet: E.g. if you leave the end of the packet in the FIFO, you will probably download it as the beginning of the next packet, corrupting that frame as well.Enabling FIFO mode
RAIL_ConfigData()
can be used to switch to FIFO mode, probably at the end of your radio init function:First, it sets up a tx buffer with
RAIL_SetTxFifo
, then configures RAIL to use FIFO mode. Then it sets up both rx and tx thresholds to 10%, which is usually a good value, although you might want to increase it at high data rates (RX_BUFFER_SIZE
should be 512B, if you did not implementRAILCb_SetupRxFifo
). Finally, it enables the two "threshold reached" event.Using FIFO mode: transmit
On the transmit side, the idea is to fill the buffer completely, start the transmission, and fill the remaining when needed:
This is just the related code, you probably have more events handled. If you look at the calls of
RAIL_WriteTxFifo()
, you can see that we always try to write the whole (remaining) packet, and do not check how much space we have in the FIFO. While we could do that, usingRAIL_GetTxFifoSpaceAvailable()
, it's not necessary:RAIL_WriteTxFifo()
will automatically stop when the FIFO is full, and return with the number of bytes successfully written.We do pretty much the same in the event, but we're adjusting both the source and the size with
txOffset
. The conditiontxOffset < PACKET_LENGTH
is important, because the FIFO events do not check packet lengths: unless you have multiple packets in the buffer, you'll get anRAIL_EVENT_TX_FIFO_ALMOST_EMPTY
every time when you have just threshold amount of bytes remaining of your packet (since it's normal to have an empty buffer if you don't want to send anything).Using FIFO mode: receive
The receive side is basically the opposite of the transmit side:
We start with an empty
packetToReceive
, and the first thing we receive will be a single or multipleRAIL_EVENT_RX_FIFO_ALMOST_FULL
(alternatively,RAIL_EVENT_RX_SYNC1_DETECT
/RAIL_EVENT_RX_SYNC2_DETECT
could be handled to set uppacketToReceive
for reception). Each time we got that event, we download as much from the FIFO as we can, and adjust the offset (again, we could check the amount of data in the FIFO usingRAIL_GetRxFifoBytesAvailable()
, but it's not necessary). When we got theRAIL_EVENT_RX_PACKET_RECEIVED
, we probably still have some data remaining in the FIFO, so we download that. Then we get thepacketDetails
, just like we do for a normal packet. We reset the offset to be ready for the next packet, both on successful receive and errors.Since this was a fixed length setup, there was no need to figure out the length. In variable length mode, you probably want to download the length field only first, set up a global variable with the length, and continue just like the above sample.
Special data sources
For the RX buffer, you can set up special data modes:
RX_PACKET_DATA
: normal mode, defaultRX_DEMOD_DATA
: demodulated, but not sliced data. 1B per sampleRX_IQDATA_FILTLSB
: IQ data, lower 16 bits of 19. 2x2B per sampleRX_IQDATA_FILTMSB
: IQ data, upper 16 bits of 19. 2x2B per sampleAll of these modes have a higher sample rate than what you set up on the configurator, since the radio does about 5-10x oversampling (depending on the configuration), which is removed at the end of the demodulation chain. Keep in mind that this could result so high data rate that the MCU won't be able to move data that fast. In our tests, we could handle about 50kb/s in IQ modes. These are quite complex to use, because you basically disable parts of the demodulation chain, but sometimes it's the only possibility.
RX_DEMOD_DATA
is useful if your frame format is impossible to detect with the hardware, e.g. it has no preamble. It can be also useful if you need baudrate/deviation tolerance not supported by the hardware. It's basically tapping the demodulation chain when the "analog" demodulation is done, but it's quantized to 8 bits - so if you have set up FSK2 modulation, the result will be basically an FM demodulated signal.RX_IQDATA_FILTLSB
andRX_IQDATA_FILTMSB
is useful if the hardware does not support the modulation you need - you can write you're own demodulator which works from the IQ samples, but that's quite heavy task for the CPU, and usually a complicated problem to solve. When IQ mode is used, one sample includes 4B, in this sequence:I[LSB], I[MSB], Q[LSB], Q[MSB].
If you enable any of these modes, they will start filling the FIFO immediately, as detection doesn't work without the end of the demodulator chain.
Direct mode
You can configure the radio to send the data it receives to a GPIO, and read data from a GPIO and send it through radio, making essentially a transceiver. You can enable it with
RAIL_EnableDirectMode()
, and it's currently fixed to portC10 and portC11 for DIN/DOUT. When you start rx, you'll see nothing on DOUT: It will only start outputting data after sync word detection. Length decoder works as well, and it will stop reception when reaches the configured length (either variable or fixed). Tx mode works similarly, but it's mostly for diagnostic purposes, as it's not possible to use a sync mode reliably without a clock signal.There's an Asynchronous direct mode checkbox on the configurator. If it's enabled, the DOUT pin's behavior will change: It will output oversampled data whenever the radio is in rx mode. See this KBA for details on this mode.
API introduced in this tutorial
Functions
Types and enums