Rx Direct Mode on EFR32#

This document outlines the Rx Direct Mode feature of the EFR32 Wireless Gecko family. Rx Direct Mode is a feature that allows the user to access the raw symbols received by the radio without packet handling. Direct Mode can be useful when the received data does not conform to the standard packet structure or when the user wants to implement custom packet handling. Although, Tx Direct Mode where the modulator signal is fed into the radio directly for immediate transmission also exists, the current document is restricted to discuss Rx Direct Mode only.

Definition of Direct Mode#

In the classical sense, in Direct Mode the radio operates via the GPIO interface, where it outputs (expects) the demodulated (modulating) binary signal stream for the received (transmitted) signal. In a broader sense, however, Rx Direct Mode can be considered as any operational mode where the radio operates with a continuous, digital, semi-processed or raw signal stream requiring the detection and symbol decoding (in some cases even the demodulation) to be performed in software with limitted HW assistance.

Practical Importance - Typical Use Cases#

In Packet Mode, losing some data symbols during packet detection and clock synchronization is unavoidable. If the synchronization pattern is too short or not included in the transmitted data, reliable detection becomes impossible. In such scenarios, switching to Direct Mode should be considered.

Access to the radio's internal signals provides the advantage of observing the raw, the unprocessed signal stream -- the same signals the radio uses in conventional Packet Mode. This makes Direct Mode an excellent debugging interface, too.

Also, unconventional demodulation (such as multi-level FSK, AFSK, multi-level PSK, Analog, etc.) or coding (32 bit+ DSSS, Pulse-Width OOK, etc.) schemes can only be implemented in Direct Mode on the EFR32.

Finally, Direct Mode can improve the performance compared to what Packet Mode would be capable of by accessing more low-level information, at higher resolution and rate on the signal not just the demodulated stream. This advantage, however, may cost a tremendous amount of CPU resources.

Direct Mode Classification#

Types of Direct Mode#

The following list outlines the supported types of Direct Mode, arranged by data integrity and complexity. Higher numbers indicate that the signal is accessed earlier in the receiver chain, resulting in less hardware processing of the signal:

(0. Packet Mode: This is the standard operational mode and not considered a type of Direct Mode;)

  1. Synchronous Direct Mode: bit-clock synchronized demodulated data provided in a binary interface; depends on how the bit clock is recovered;

  2. Asynchronous Direct Mode: Oversampled demodulated data provided in a binary interface; heavily depends on the symbol detector configuration;

  3. Demod Data: Amplitude or frequency of the received signal depending on the modulation scheme used;

  4. IQ Data: Quadrature samples directly tapped after the RF mixer; independent of the demodulator.

Except for IQ Data, each successive mode simplifies the signal by reducing the amount of information it carries while enhancing conciseness. This simplification makes the signal easier to process and allows for higher achievable data rates as it approaches Packet Mode. IQ Data and Demod Data modes are the most versatile, offering detailed insights into the signal's physical properties. In contrast, Synchronous and Asynchronous Direct Modes operate on signals that have already passed through the demodulator’s slicer, which converts the physical signal properties into binary symbols (0s and 1s).

The Synchrounous Direct Mode is captured at the Actual Rx Baudrate [baud]. From mode 2 to 4, the radio operates at the Oversampling Rate (OSR), and the sample rate is defined by Raw Data Sampling Rate [sps]. These parameters can be found in the Radio Configurator generated log files (radioconf_generation_log.json).


We recommend using Packet Mode whenever possible, as this is the intended design of our products. If Packet Mode cannot be used, choose the least complex Direct Mode that meets your application's requirements.

As a general rule of thumb, the following guidelines can help you choose the most suitable type of Direct Mode for your application:

  • Synchronous Direct Mode:

    • When robust signal detection cannot be set up in the radio configuration, but the frame can be seen on the synchronized bit stream.

    • For Bit Error Rate (BER) tests.

  • Asynchronous Direct Mode:

    • In channel scanning/protocol learning applications where the frequency or the baud rate is not precisely known.

    • In applications where enormous baud rate tolerance is desired.

  • IQ or Demod Data:

    • For signal analysis, debugging, or custom demodulation.

    • For applications where the signal is not modulated with a supported modulation scheme.

Interfaces and Availability of Rx Direct Mode Types#

Direct Mode type availability and the interface through which the data is provided for further processing depend on the radio configuration and the device generation. The following interfaces are available for Rx Direct Mode types:

  1. Direct-to-Buffer: may be available for all Direct Modes types;

  2. Direct-to-PRS: may only be available for binary Direct Modes types.

  3. Direct-to-GPIO: may only be available for binary Direct Modes types;

Several Direct Mode types are only available if the radio configuration is generated with the Rx Direct Mode option enabled (i.e., not the DISABLED option is selected). This option is available on a limited subset of Series 2 device generations (e.g., EFR32xG23). These configurations will be referred to as the Direct Mode Radio Configuration hereafter.

Note: The SYNC mode is preferred over the ASYNC mode, as it provides both Direct-to-PRS signals simultaneously.


In Direct-to-Buffer mode, RAIL stores the samples into the Rx FIFO. In this mode, all Direct Mode types are available and can be configured through the RAIL_ConfigData(..., data_config) API.

The type can be selected via the data_config.rxSource field and data_config.rxMethod must be set to FIFO_MODE.

The following table summarizes the available APIs for each Rx Direct Mode types:

Direct Mode Type

RAIL API

Availability

Synchronous Direct Mode

RX_DIRECT_SYNCHRONOUS_MODE_DATA

Limitted to Direct Mode Radio Configuration

Asynchronous Direct Mode

RX_DIRECT_MODE_DATA)

Limitted to Direct Mode Radio Configuration

Demod Data

RX_DEMOD_DATA)

No restrictions

IQ Data

RX_IQDATA_FILTLSB/RX_IQDATA_FILTMSB

No restrictions

In this mode, the data flow typically looks like this:

  1. The samples captured by the radio are transferred to the Rx FIFO allocated for RAIL.

  2. The application reads the samples from the RAIL's Rx FIFO into an application buffer.

  3. The application processes the samples or transfers them to a host processor through a communication interface.

The highest available baud rate for Direct-to-Buffer mode limited by the interface through which the data is provided for further processing (or potentially even lower if the data is processed within the EFR32's core). While the radio can operate at symbol rates of up to several Msps, it's important to note that each IQ sample consists of 4 bytes, whereas in Demod Data, the data is represented with just 1 byte. Additionally, the Oversampling rate (baud rate/symbol rate) can be as high as 10-20.

For example, for a 100 kbps PHY with an OSR of 10, the IQ Data rate would be 4 * 100 kbps * 10 * 8 = 32 Mbps (4 MBps), while the Demod Data rate would be 100 kbps * 10 * 8 => 8 Mbps (1 MBps).


Series 2 Direct-to-PRS is only available when a Direct Mode Radio Configuration is the active radio configuration.

Direct Mode Type

PRS Signal Name

Synchronous Direct Mode Data

PRS_MODEM_DOUT

Synchronous Direct Mode Clock

PRS_MODEM_DCLK

Asynchronous Direct Mode Data

PRS_MODEM_LRDSALIVE

The baud rate of the Direct-to-PRS signals is essentially limited by the peripheral to which the PRS signals are connected.

For more information on how to route PRS signals to GPIOs, see the Debugging training article.


For Series 1 devices, Direct Mode types with binary output (Sync and Async) can be traced onto GPIOs (via DBUS interface). These are available via the RAIL_ConfigDirectMode() and RAIL_EnableDirectMode() APIs.

Note: Direct-to-GPIO mode is not recommended on Series 2 devices as as it may not work as expected with certain radio PHYs. Instead, use the Direct-to-PRS mode.

Examples#

In this section, we provide examples on how to set up the different Direct Mode types and interfaces, either through firmware code implementation or, for a quick testing, using RAILtest commands.

Setting up Direct-to-Buffer Mode#

C Code#

/*
Available data sources:
RX_DIRECT_SYNCHRONOUS_MODE_DATA
RX_DIRECT_MODE_DATA
RX_DEMOD_DATA
RX_IQDATA_FILTLSB RX_IQDATA_FILTMSB
*/

// Defines the RAIL Rx FIFO length and the application's buffer length
#define RX_FIFO_LENGTH 4096
#define BUFFER_LENGTH 2000

// Select the default data source
#define DEFAULT_DATA_SOURCE RX_DIRECT_MODE_DATA

// Allocate the Rx FIFO
__ALIGNED(RAIL_FIFO_ALIGNMENT)
uint8_t rx_fifo[RX_FIFO_LENGTH];

// Allocate a ping-pong style application buffer
uint8_t rx_buffers[2][BUFFER_LENGTH];

// Initialize the active buffer index
uint8_t active_buffer = 0;

// ...

/* Initialization code */
{
  // ...
  RAIL_Status_t status;

  // Select Direct Mode type and configure the FIFO_MODE
  RAIL_DataConfig_t data_config = {
    .rxSource = DEFAULT_DATA_SOURCE,
    .rxMethod = FIFO_MODE,
  };

  // Configure `Direct-to-Buffer` mode
  status = RAIL_ConfigData(rail_handle, &data_config);
  if (status != RAIL_STATUS_NO_ERROR)
  {
    // handle error
  }

  // Set the Rx FIFO threshold for the Rx FIFO almost full event
  RAIL_SetRxFifoThreshold(rail_handle, BUFFER_LENGTH);

  // Enable the Rx FIFO almost full event
  RAIL_Events_t events = RAIL_EVENT_RX_FIFO_ALMOST_FULL;
  status = RAIL_ConfigEvents(rail_handle, events, events);
  if (status != RAIL_STATUS_NO_ERROR)
  {
    // handle error
  }
  // ...
}

// Bind the Rx FIFO to the RAIL handle
RAIL_Status_t RAILCb_SetupRxFifo(RAIL_Handle_t railHandle)
{
  uint16_t rx_fifo_size = RX_FIFO_LENGTH;
  RAIL_Status_t status = RAIL_SetRxFifo(railHandle, &rx_fifo[0], &rx_fifo_size);
  if (rx_fifo_size != RX_FIFO_LENGTH)
  {
    // We set up an incorrect FIFO size
    return RAIL_STATUS_INVALID_PARAMETER;
  }
  if (status == RAIL_STATUS_INVALID_STATE)
  {
    // Allow failures due to multiprotocol
    return RAIL_STATUS_NO_ERROR;
  }
  return status;
}

/* RAIL event handler */ 
{
  // ...
  if (events & RAIL_EVENT_RX_FIFO_ALMOST_FULL)
  // This event fires whenever the Rx FIFO contains more data than configured 
  // in the threshold; BUFFER_LENGTH in this example
  {
    uint16_t bytes_read; 
    // Read the Rx FIFO into the active buffer
    bytes_read = RAIL_ReadRxFifo(rail_handle, rx_buffer[active_buffer], BUFFER_LENGTH);
    if (bytes_read != BUFFER_LENGTH)
    {
      // handle error
    }
    // Switch the active buffer for the next iteration
    active_buffer ^= 1;
  }
  // ...
}

RAILtest Commands#

rx 0
setRxFifo <fifo_size>
dataConfig fifo fifo <rx_data_source> 0
// rx_data_source can be 1-5; e.g., RX_DIRECT_MODE_DATA is 4
fifoModeTestOptions 1 1
seteventconfig 0x400 0x000 
fiforeset 1 1
rx 1
rxFifoManualRead 0 <fifo_size> 0

Setting up Direct-to-PRS Mode#

C Code#

#define SYNC_DOUT_GPIO_PORT gpioPortC
#define DCLK_GPIO_PORT gpioPortC
#define ASYNC_DOUT_GPIO_PORT gpioPortC

#define SYNC_DOUT_GPIO_PIN 0
#define DCLK_GPIO_PIN 1
#define ASYNC_DOUT_GPIO_PIN 2

/*
  Channel 0-5  available on GPIO port A/B
  Channel 6-11 available on GPIO port C/D
*/
#define SYNC_DOUT_PRS_CHANNEL 6
#define DCLK_PRS_CHANNEL 7
#define ASYNC_DOUT_PRS_CHANNEL 8

// (...)

// Initialize PRS signals
PRS_ConnectSignal(SYNC_DOUT_PRS_CHANNEL, prsTypeAsync, PRS_MODEML_DOUT);
PRS_ConnectSignal(DCLK_PRS_CHANNEL, prsTypeAsync, PRS_MODEML_DCLK);
PRS_ConnectSignal(ASYNC_DOUT_PRS_CHANNEL, prsTypeAsync, PRS_MODEM_LRDSALIVE);

// Initialize GPIO pins
GPIO_PinModeSet(SYNC_DOUT_GPIO_PORT, SYNC_DOUT_GPIO_PIN, gpioModePushPull, 0);
GPIO_PinModeSet(DCLK_GPIO_PORT, DCLK_GPIO_PIN, gpioModePushPull, 0);
GPIO_PinModeSet(ASYNC_DOUT_GPIO_PORT, ASYNC_DOUT_GPIO_PIN, gpioModePushPull, 0);

// Assing PRS signals to GPIO pins
PRS_PinOutput(SYNC_DOUT_PRS_CHANNEL, prsTypeAsync, SYNC_DOUT_GPIO_PORT, SYNC_DOUT_GPIO_PIN);
PRS_PinOutput(DCLK_PRS_CHANNEL, prsTypeAsync, DCLK_GPIO_PORT, DCLK_GPIO_PIN);
PRS_PinOutput(ASYNC_DOUT_PRS_CHANNEL, prsTypeAsync, ASYNC_DOUT_GPIO_PORT, ASYNC_DOUT_GPIO_PIN);

RAILtest Commands#

// The signal-source associations are valid for EFR32xG23/25/28 parts
setdebugsignal <gpio_pin_selection: PC0> CUSTOM_PRS 0x29 6 // SYNC DOUT
setdebugsignal <gpio_pin_selection: PC1> CUSTOM_PRS 0x29 5 // DCLK
setdebugsignal <gpio_pin_selection: PC2> CUSTOM_PRS 0x2A 3 // ASYNC DOUT

Setting up Direct-to-GPIO Mode#

C Code#

RAIL_Status_t status;

RAIL_DirectModeConfig_t direct_mode_config = {
  // configure
};

status = RAIL_ConfigDirectMode(rail_handle, &direct_mode_config);
if (status != RAIL_STATUS_NO_ERROR)
{
  // handle error
}

status = RAIL_EnableDirectModeAlt(rail_handle, <enable_tx>, true);
if (status != RAIL_STATUS_NO_ERROR)
{
  // handle error
}

RAILtest Commands#

rx 0
configDirectMode <parameters>
directmode 1
rx 1

For additional examples, explore our EFR32 RAIL Application Examples repository on GitHub.