FIFO Mode#

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 your frame, you can still upload the end of the frame.

Since the radio is using ring buffers, this makes it possible to handle longer frames than what fits into the buffer, and nowadays, this is pretty much the only reason to use FIFO mode. A typical use case for example is a protocol, where almost all of the frames fits into 128B buffer, but in rare cases, the protocol must handle 512B long frames. In this article, the examples will present how to do that.

Another case when FIFO mode is required are some Direct and IQ modes. Setting up those are detailed in a dedicated article, but FIFO handling is the same as documented here.

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 need longer packets, you can customize both the Tx and Rx FIFO length, via RAIL_SetTxFifo() and from the callback RAILCb_SetupRxFifo().

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() (except with DMP, but that would make the use of FIFO mode difficult as well).

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#

The easiest way to enable FIFO mode is on the RAIL Utility, Initialization component:

FIFO setupFIFO setup

This calls RAIL_ConfigData() during init with the following parameters:

static const RAIL_DataConfig_t rail_data_config = {
  .txSource = TX_PACKET_DATA,
  .rxSource = RX_PACKET_DATA,
  .txMethod = FIFO_MODE, //doesn't do anything
  .rxMethod = FIFO_MODE,
};
RAIL_ConfigData(rail_handle, &rail_data_config);

However, to properly use FIFO mode, you probably want to know when the FIFO fills up/runs out of data. This is configured as below:

RAIL_SetTxFifoThreshold(rail_handle, TX_BUFFER_SIZE / 10);
RAIL_SetRxFifoThreshold(rail_handle, RX_BUFFER_SIZE - RX_BUFFER_SIZE / 10);
RAIL_ConfigEvents(rail_handle, RAIL_EVENT_TX_FIFO_ALMOST_EMPTY|RAIL_EVENT_RX_FIFO_ALMOST_FULL,
                    RAIL_EVENT_TX_FIFO_ALMOST_EMPTY|RAIL_EVENT_RX_FIFO_ALMOST_FULL);

This sets the thresholds to 10% from being empty/full (for Tx and Rx, respectively), 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" events.

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:

#define PACKET_LENGTH 1024
uint8_t packet_to_send[PACKET_LENGTH];
volatile uint16_t tx_offset;
void start_tx(){
  tx_offset = RAIL_WriteTxFifo(rail_handle, packet_to_send, PACKET_LENGTH, false);
  RAIL_StartTx(rail_handle, 0, RAIL_TX_OPTIONS_DEFAULT, NULL);
}

void sl_rail_util_on_event(RAIL_Handle_t rail_handle, RAIL_Events_t events){
  if ( (events & RAIL_EVENT_TX_FIFO_ALMOST_EMPTY) && ( tx_offset < PACKET_LENGTH ) ){
    tx_offset += RAIL_WriteTxFifo(rail_handle, packet_to_send+tx_offset, PACKET_LENGTH-tx_offset, false);
  }
}

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. We could do that, using RAIL_GetTxFifoSpaceAvailable(), but 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 the same in the event, but we're adjusting both the source and the size with tx_offset. The condition tx_offset < 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:

#define PACKET_LENGTH 1024
uint8_t packet_to_receive[PACKET_LENGTH];
volatile uint16_t rx_offset = 0;

void RAILCb_Generic(RAIL_Handle_t rail_handle, RAIL_Events_t events){
  if ( events & RAIL_EVENT_RX_FIFO_ALMOST_FULL ){
  	rx_offset += RAIL_ReadRxFifo(rail_handle, packet_to_receive+rx_offset, PACKET_LENGTH - rx_offset);
  }
  if ( events & RAIL_EVENTS_RX_COMPLETION ){
	  if ( events & RAIL_EVENT_RX_PACKET_RECEIVED ){
	  	if ( rx_offset < PACKET_LENGTH ){
		  rx_offset += RAIL_ReadRxFifo(rail_handle, packet_to_receive+rx_offset, PACKET_LENGTH - rx_offset);
	  	}
	  	RAIL_RxPacketDetails_t packet_details = {
	      .isAck = false,
	      .timeReceived = {
	      	.timePosition = RAIL_PACKET_TIME_AT_SYNC_END,
	      	.totalPacketBytes = PACKET_LENGTH,
	      },
	  	};
	  	RAIL_GetRxPacketDetails(rail_handle, RAIL_RX_PACKET_HANDLE_NEWEST, &packet_details);
	  }
	  rx_offset = 0;
	}
}

We start with an empty packet_to_receive, 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 packet_to_receive for reception).

Each time we get 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 get the RAIL_EVENT_RX_PACKET_RECEIVED, we probably still have some data remaining in the FIFO, so we download that.

Then we get the packet_details, just like we do for a normal packet. This step is very important if you can't reset the FIFO between packets. If you leave the appended info in the FIFO, it will remain there and will be difficult to figure out where the next packet starts.

Finally, 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.

API Introduced in this Article#

Functions#

Types and Enums#