While RAIL attempts to be chip agnostic there are certain hardware differences that can't be overlooked. Where possible, missing features will be simulated in software, but performance characteristics may vary. This section will cover what differs for the EFR32 series of chips as well as any hardware specific configurations or calibrations.

Packet Buffer Restrictions

Currently the RAIL library for the EFR32 allocates an internal buffer to store the receive data contiguously. This buffer is set to 512 bytes at build time and cannot be changed. This choice limits the maximum size of receive packets in packet mode and determines the size of the receive FIFO in FIFO mode. Since each receive packet has 6 bytes of overhead this means that you can only receive up to one 506 byte packet without switching to FIFO mode. In FIFO mode you must read out packet data as you approach this limit and store it off to construct the full packet later. Note that this 6 byte overhead may increase or decrease in future releases as we change functionality though we would not expect large jumps in either direction.

The transmit FIFO must be set at runtime with RAIL_SetTxFifo(). Its size must be a power of 2 from 64 to 4096 for hardware compatibility. Note that there is no difference between packet and FIFO mode on the transmit side. A packet may either be loaded all at once or in pieces using the RAIL_WriteTxFifo() function. You may also use the RAIL_SetTxFifoThreshold() function and the RAIL_EVENT_TX_FIFO_ALMOST_EMPTY event to load data as there is space available in the FIFO during transmission.

Data Reception Sources

When receiving data, you can configure hardware to provide data from three different hardware sources.

First, you can configure the hardware to provide a packet of information. This configuration utilizes the built-in demodulator and frame controller. Use RAIL_RxDataSource_t::RX_PACKET_DATA to enable this receive source.

Second, you can configure the hardware to provide data directly from the demodulator. In this mode, the hardware demodulator is used, but the user is responsible for implementing frame controller functionality. (i.e. Preamble detection, sync word detection, CRC validation, etc. must be performed in software by the application.) All data returned is represented as 8-bit 2's-compliment values. Use RAIL_RxDataSource_t::RX_DEMOD_DATA to enable this receive source.

Third, you can configure the hardware to provide data directly from the I and Q ADCs. In this mode, the user is responsible for implementing demodulator and frame controller functionality. The receive signal hardware has a 19-bit dynamic range, and the user can select whether to return the upper 16 bits of the 19-bit value (RAIL_RxDataSource_t::RX_IQDATA_FILTMSB) or the lower 16 bits (RAIL_RxDataSource_t::RX_IQDATA_FILTLSB). All data returned is represented as 16-bit 2s-compliment values. The I and Q values are put into the buffer in an alternating fashion where the first 16-bit value is the first I ADC value, and the second 16-bit value is the first Q ADC value, etc.

Interrupt Vectors

The RAIL library for EFR32 implements all of the interrupt vectors for radio peripherals. This is required for the RAIL library to function correctly. RAIL does not, however, set the priorities of these interrupt vectors. That must be handled by your application using the CMSIS NVIC_SetPriority() API or direct manipulation of the NVIC. Below is the full list of interrupts used by the radio. You must run them all at the same priority which is what all Silicon Labs RAIL applications do by default. You are free to choose what that priority is based on the requirements of your application (e.g. putting them below FreeRTOS atomic for access to OS functions in RAIL callbacks). Keep in mind that putting the radio interrupts at too low a priority can cause missed packets and other radio performance issues. A restriction on EFR32xG12 and newer is that you must not disable radio interrupts for longer than a quarter of the RAIL timebase (2^32/4 microseconds or 18 minutes) to ensure proper timekeeping. Please see EFR32xG1x_Interrupts or follow the links below for more information.

Chip Specific Initialization

EFR32 Hardware Initialization

The EFR32 comes with versions of EMLIB and EMDRV to create a basic HAL layer. A lot of this initialization code is completely up to you, but there are a couple of requirements when you're building a RAIL app. Specifically, the radio will only work if you are running off a high precision crystal oscillator. Since some APIs will assume this is running you should make sure to initialize and switch to the crystal before calling any radio APIs.

For the WSTK you can use the crystal configuration in the HAL config header file for your specific kit. Example code for this is shown below. If you have a custom hardware layout you may want to create your own HFXOInit structure to account for things like your specific CTUNE value.

#include "bsp.h" // Contains WSTK versions of the HFXO init structure
void efrStartup(void)
// Initialize the HFXO using the settings from the WSTK bspconfig.h
// Note: This configures things like the capacitive tuning CTUNE variable
// which can vary based on your hardware design.
// Switch HFCLK to HFXO and disable HFRCO
CMU_ClockSelectSet(cmuClock_HF, cmuSelect_HFXO);
CMU_OscillatorEnable(cmuOsc_HFRCO, false, false);

Radio Specific Initialization

Currently there are two initialization functions for the radio that depend on your board configuration and must be manually called at startup: RAIL_ConfigTxPower() and RAIL_ConfigPti(). You MUST initialize the power amplifier (PA) in order to transmit. You may initialize the packet trace interface (PTI) if you want to use this for debugging.

Power Amplifier (PA) Initialization

There are three different PAs available for the EFR32xG1 family of chips: the High Power 2.4GHz PA, the Low Power 2.4GHz PA, and the Sub GHz PA. The specific set of PAs you have available and the supported power levels for those PAs are determined by your part number. Consult the data sheet for more details.

Each PA supports a raw power level value of type RAIL_TxPowerLevel_t where a numerically smaller number causes less power to be output from the chip than a higher number. Keep in mind that these values may be capped at the upper and lower ends and do not necessarily map linearly to output power in dBm. To map these values to and from dBm values the RAIL library uses the RAIL_ConvertRawToDbm() and RAIL_ConvertDbmToRaw() APIs. By default, these use whatever piecewise linear power curves were passed into RAIL_InitTxPowerCurves() to do the conversion. On Silicon Labs boards these curves are included with the board header files. For custom boards this must be measured using test equipment to take into account the differences in the RF path that may exist. In addition for even more custom use cases the conversion functions can be strongly defined in the customer application and provide whatever type of mapping is found to be most effective. Below is an example of using the standard APIs to initialize all PAs on a dual band chip and switch to the 2.4GHz high power PA.

#include "rail.h"
#include "rail_chip_specific.h"
#include "plugin/pa-conversion/pa_conversion_efr32.h"
RAIL_DECLARE_TX_POWER_VBAT_CURVES(piecewiseSegments, curvesSg, curves24Hp, curves24Lp);
// Must be called with a valid RAIL_Handle_t returned by RAIL_Init()
void initPa(RAIL_Handle_t myRailHandle)
// Initialize the RAIL Tx power curves for all PAs on this chip
RAIL_TxPowerCurvesConfig_t txPowerCurvesConfig = {
if (RAIL_InitTxPowerCurves(&txPowerCurvesConfig) != RAIL_STATUS_NO_ERROR) {
// Could not initialize transmit power curves so something is configured
// wrong. Please fix and rebuild.
// Switch to the 2.4GHz HP PA powered off the 1.8V DCDC connection
RAIL_TxPowerConfig_t railTxPowerConfig = {
RAIL_TX_POWER_MODE_2P4_HP, // 2.4GHz HP Power Amplifier mode
1800, // 1.8V vPA voltage for DCDC connection
10 // Desired ramp time in us
if (RAIL_ConfigTxPower(myRailHandle, &railTxPowerConfig)
// Error: The requested PA could not be selected. Fix your configuration
// and try again.
// Set the output power to the maximum supported by this chip
RAIL_SetTxPower(myRailHandle, 255);

Packet Trace Interface (PTI) Initialization

Packet trace on the EFR32 provides a mechanism for viewing transmitted and received radio packets for network sniffing or debugging. It can also be captured by a WSTK and sent to Simplicity Studio for viewing the data in its Network Analyzer tool.

If you want to use this functionality, you must configure what pins you want to use for this output and, optionally how you want to format the data. Note that the WSTK requires the following output format:

Mode: 8 bit UART mode
Baudrate: 1.6Mbps
Framing Signal: Enabled

If you change anything here it is currently unsupported by the WSTK. In the future we may include support for alternate output modes.

To choose the pins you want you must look in the data sheet for your part and select a valid route location for the FRC_DOUT and FRC_DFRAME signals. These map to PTI.DATA and PTI.FRAME on the WSTK respectively. Once you've found pins that work for your hardware you need to configure the RouteLocation, port, and pin fields in the PTI initialization structure. When using the WSTK for example, you should initialize things as follows.

#include "rail.h"
#include "rail_chip_specific.h"
void initPti(void)
RAIL_PtiConfig_t ptiConfig = {
RAIL_PTI_MODE_UART, // Only supported output mode for the WSTK
1600000, // Choose 1.6 Mbps for the WSTK
6, // WSTK uses location 6 for DOUT
gpioPortB, // FRC_DOUT#6 is PB12
12, // FRC_DOUT#6 is PB12
6, // WSTK uses location 6 for DFRAME
gpioPortB, // FRC_DOUT#6 is PB13
13, // FRC_DOUT#6 is PB13
// Initialize the Packet Trace Interface (PTI) for the EFR32
// Enable Packet Trace (PTI)

Other Radio GPIO Functions

There are various useful signals related to the radio that can be output on a GPIO using the Peripheral Reflex System (PRS). The PRS is an advanced system where you can route signals to channels and then output those channels to a number of configurable locations. There's a lot more to it than that, so consult the reference manual chapter on the PRS if you want to understand it better.

A list of some of the most interesting PRS signals related to the radio is shown below along with how to enable them in the PRS. The definition for these signals for a given chip can be found in the release at platform/Device/SiliconLabs/<chipFamily>/Include/<chipFamily>_prs_signals.h where <chipFamily> would be the beginning of the part number (EFR32MG12P for example).

Signal Summary
PRS_RAC_ACTIVE Radio enabled
PRS_RAC_TX Transmit mode enabled
PRS_RAC_RX Receive mode enabled
PRS_RAC_LNAEN LNA enabled for Rx
PRS_RAC_PAEN PA enabled for Tx
PRS_MODEM_PREDET Preamble detected
PRS_MODEM_TIMDET Timing detected

The example below shows you how to configure a PRS channel to output RAC_RX on a GPIO. For this example we assume you're using the WSTK with the BRD4153A radio board. If so, the code below will put RACRX on PRS Channel 0 and output PRS Channel 0 on pin PC10 which is wired to WSTK_P12 and EXP_HEADER15 on the WSTK.

#include "em_cmu.h"
#include "em_prs.h"
#include "em_gpio.h"
#include "em_device.h"
void enableDebugGpios(void)
// Turn on the PRS and GPIO clocks so we can access their registers
CMU_ClockEnable(cmuClock_PRS, true);
CMU_ClockEnable(cmuClock_GPIO, true);
// Configure PC10 as an output
GPIO_PinModeSet(gpioPortC, 10, gpioModePushPull, 0);
// Configure PRS Channel 0 to output RACRX
// Configure PRS Channel 0 to use output location 12 (PC10 - see datasheet)
// Enable PRS Channel 0

Required Dependencies

Most of the RAIL library is self contained, however, there are some dependencies on external functions. Specifically, we rely on functions from the C standard library, EMLIB, and CMSIS. Below is a complete list of these dependencies. If you want to change the implementation of any of these functions while maintaining the functionality that's fine, just know that they could impact RAIL operation.

Group Functions
CMSIS SystemHFXOClockGet()
EMLIB (em_cmu) CMU_ClockEnable
EMLIB (em_gpio) GPIO_PinModeSet()
EMLIB (em_system) SYSTEM_ChipRevisionGet()
EMLIB (em_core) CORE_EnterCritical()
EMLIB (em_emu) EMU_DCDCLnRcoBandSet()
stdlib memcpy()

Peripherals Consumed by RAIL

Currently, RAIL consumes only peripherals that are a part of the radio.

Future Peripheral Considerations

In the future, RAIL might use a few of the customer facing peripherals for enhanced functionality. In preparation, below is a list of potential peripherals that could be shared in future RAIL releases.

  • PRS Channels
  • RTCC Timer

Entropy Generation

The EFR32 supports true entropy collection using the radio. It is able to collect 1 bit per radio clock cycle while in receive mode. This means that we must enable the receiver when collecting entropy and if you attempt to transmit or otherwise delay entry into receive it will take us a longer time to collect the data. You can still receive packets while collecting entropy and this does mean that requesting random numbers longer than the length of your preamble and sync word may trigger a packet reception. This may be fixed in a future revision but for now this is done to ensure proper packet reception during random data collection.

Due to the nature of random data collection we will always wait for the full amount requested so we should return either the number of bytes requested or zero. Zero is returned if the radio is uninitialized and cannot be enabled for collection.

RAIL Timebase

The EFR32 uses a dedicated radio timer to create the RAIL timebase and perform scheduled transmit and receive operations. This timer ticks at roughly every 2us on the EFR32xG1 platform and 0.5us on the EFR32xG12 and newer. The exact tick value depends on the high frequency crystal in use so there may be some small rounding error. All internal APIs account for this error so over long periods of time it is generally unnoticeable. Note that RAIL uses the hardware tick rate to create the 1us time base that is exposed to the user. This is all that should matter for most users and the information here is only to allow you to better understand the underlying implementation.

Radio Calibration

The EFR32 supports two radio calibration options: image rejection (IR) calibration and VCO temperature calibration. You may choose to enable one or both of these based upon your specific situation. We always recommend using both for optimal performance of the radio.

  • Image Rejection Calibration (IRCAL)
    • This calibration should be run once every time your PHY config changes. It is based on the modulation scheme, frequency band, and other radio settings. RAIL will request this by calling RAIL_Config_t::eventsCallback with the RAIL_EVENT_CAL_NEEDED bit set and the RAIL_CAL_ONETIME_IRCAL bit set in RAIL_GetPendingCal().
    • Using a proper value for this will improve sensitivity by several dBm, so it's highly recommended.
    • It can take on the order of 700 ms to complete this calibration. You may want to save off a known good value for this calibration and load it each time you switch PHYs to save time.
    • This calibration should be initialized before calling RAIL_ConfigChannels() to ensure that it is properly configured by the time the first channel is setup. This initialization involves enabling the algorithm and passing in parameters to configure the algorithm for the specific PHY generated by the EFR32 Radio Configurator.
    • You cannot use the radio while this calibration is being performed or you may generate an incorrect calibration. Application code should ensure that the radio remains in the idle state during this calibration.
    • This calibration is only meaningful for Zigbee, BLE, and sub GHz radio configurations. For all others you should still enable the calibration but the algorithm will apply a safe, default calibration value.
  • VCO Temperature Calibration (VCO_TEMPCAL)
    • If you sit in receive for a very long time and experience temperature swings it's possible for the radio to drift off frequency. When this situation is detected RAIL will request this calibration via RAIL_Config_t::eventsCallback with the RAIL_EVENT_CAL_NEEDED bit set and the RAIL_CAL_TEMP_VCO bit set in RAIL_CalPendingGet().
    • This calibration is automatically run every time we enter receive. So if you are frequently forcing a re-enter into receive mode you may not need to enable this calibration.
    • On EFR, the application will get this event when the absolute temperature crosses 0C degrees as well as when the temperature delta from the last calibration increases or decreases by 70C.
    • We always recommend that you enable and handle this calibration since it doesn't add much overhead and is much safer if you ever do end up in this situation.

State Transition Timing

The EFR32 allows automatic transitions from receive to transmit, in order to precisely time transmitted packets after packet reception. During this process, some internal calculations are done which require the received packet's duration, from sync word to end of CRC, to be less than 32 ms. If some received packets in a given protocol have an on-air time greater than 32 ms, the automatic transition from receive to transmit may not be used with that protocol, as the timing of that transition will be incorrect.

Radio State Verification

RAIL includes a radio state verification feature capable of verifying radio register contents. There are multiple ways of configuring and running the verification process. All radio state verification should occur when the radio is in the idle state. If not in idle, the number of radio registers different from their reference values varies at any given time.

Default vs. Custom Radio Config

When verification occurs, a reference value is compared with a radio register's contents. The reference value is provided through one of two ways.

The radio config (originally passed into RAIL_ConfigChannels()) is used as a reference with which to compare radio register values. When the default radio config is used for verification, only those addresses flagged as being verifiable by the radio configurator will be checked. Bit 27 of an encoded address indicates whether that address is verifiable or not. For example, encoded address 0x08020004 is flagged as being verifiable and will be verified when RAIL_Verify() is called, but the encoded address 0x00020008 will not be verified.

A custom radio config can be provided to the verification API (passed into RAIL_ConfigVerification()). When a custom radio config is provided, all addresses in the provided radio config will be checked, regardless of whether or not their encoded addresses are flagged as being verifiable.

Approval Callback

When verification occurs, no register value differences are allowed. There is the option to define an application-level approval callback.

Without an approval callback, no register value differences are allowed. Any difference will be flagged as data corruption, with RAIL_Verify() returning a status of RAIL_STATUS_INVALID_STATE.

With an approval callback, the application can scrutinize all differences and deem them acceptable or not. By providing an approval callback, individual register fields, instead of the entire register contents, can be compared against reference values at the application level.


When verification occurs, there is the option to restart verification or not.

When running verification from the beginning to completion (by specifying a sufficiently long test duration or by specifying a duration of RAIL_VERIFY_DURATION_MAX), the verification process is indifferent to the restart parameter.

When running verification for a duration that does not permit verification to run to completion, the RAIL_Verify() function will return with a status of RAIL_STATUS_SUSPENDED. This indicates test success for those values already verified, but sufficient time was not permitted to verify all values. In this scenario, the initial run of RAIL_Verify() is indifferent to the value of the restart input, but subsequent runs of RAIL_Verify() should set the restart input to false so that verification will resume where it left off on the previous call. For the purposes of recording where a previous verification left off, the RAIL_VerifyConfig_t structure must be declared by the application and provided to RAIL. These structure contents should only be altered by RAIL.

Configuration and Running Options

With the ability to specify default radio config vs. custom radio config, approval callback vs. no approval callback, and run to completion vs. run for a specific time, there are multiple ways in which to run RAIL's radio state verification feature. The example below shows how one might run in each of these modes.

#include "rail.h"
#include "rail_types.h"
#include "rail_config.h"
RAIL_VerifyConfig_t configVerify;
const uint32_t customRadioConfig[] = {
0x00010048UL, 0x00000000UL,
0x000400A0UL, 0x00004CFFUL,
/* 00A4 */ 0x00000000UL,
/* 00A8 */ 0x00004DFFUL,
/* 00AC */ 0x00000000UL,
0x00012000UL, 0x00000744UL,
bool RAILCb_VerificationApproval(uint32_t address,
uint32_t expectedValue,
uint32_t actualValue)
address, expectedValue, actualValue);
return true OR false; // true = change approved; false = change unapproved
main ()
RAIL_Status_t result;
// Initialize the radio.
railHandle = RAIL_Init(..., ...);
// Associate a radio config with this RAIL handle.
RAIL_ConfigChannels(railHandle, channelConfigs[0], ...);
// Idle the radio before verifying registers.
RAIL_Idle(railHandle, RAIL_IDLE, true);
uint8_t verifyConfigOption = 1; // 1,2,3,4
uint8_t verifyRunOption = 1; // 1,2
// Configure the radio state verification in one of 4 ways:
if (1 == verifyConfigOption) {
// Config Option 1: Use default radio config with no approval callback.
NULL, // use channelConfigs[0] for verification
} else if (2 == verifyConfigOption) {
// Config Option 2: Use default radio config with approval callback.
NULL, // use channelConfigs[0] for verification
} else if (3 == verifyConfigOption) {
// Config Option 3: Use custom radio config with no approval callback.
} else {
// Config Option 4: Use custom radio config with approval callback.
// Run the radio state verification in one of 2 ways:
if (1 == verifyRunOption) {
// Run Option 1: Run verification once to completion.
result = RAIL_Verify(railHandle,
true); // run from the beginning of verification
} else {
// Run Option 2: Run verification for a specific time before returning.
result = RAIL_Verify(railHandle,
100, // run for 100 microseconds before returning
true); // run from the beginning of verification
while (RAIL_STATUS_SUSPENDED == result) {
"verification suspended - run again to continue");
result = RAIL_Verify(railHandle,
100, // run for 100us before returning
false); // run from where verification left off
switch (result) {
responsePrint("verifyRadio", "success, done");
responsePrint("verifyRadio", "invalid input parameter");
responsePrint("verifyRadio", "data corruption");