Understanding RAIL Config Files#

While this is a RAIL software documentation, it could be useful for a hardware engineer who's working on a radio setup as well. It helps if you have some knowledge of RAIL, but only basic C knowledge is a requirement.

Silicon Labs provides tools to generate RAIL config files, but it could be useful to understand how they work.

The Generated Files#

The radio configurator an advanced configurator (meaning it's not just an editor GUI for a header file) in Simplicity Studio v5, which generates 3 files into the <project_dir>/autogen folder:

  • rail_config.c

  • rail_config.h

  • radioconf_generation_log.json

The Log#

The file radioconf_generation_log.json provides some information on the generated output. For example, the bandwidth is usually not an input from the radioconf file, but a calculated value. That is reported in this file as Acquisition Channel Bandwidth.

The Header#

The configurator generates rail_config.h containing a few lines apart from header comments and guards and includes:

#define PROTOCOL_ACCELERATION_BUFFER protocolAccelerationBuffer
extern uint32_t protocolAccelerationBuffer[];

#define RAIL0_CHANNEL_GROUP_1_PHY_STUDIO_868M_GMSK_500KBPS
#define RAIL0_CHANNEL_GROUP_1_PROFILE_BASE
#define RADIO_CONFIG_XTAL_FREQUENCY 39000000UL

extern const RAIL_ChannelConfig_t *channelConfigs[];

The first two lines defines a layer to give access for the application to the protocolAccelerationBuffer variable. The protocolAccelerationBuffer is used for DMP switches, to accelerate channel hopping mechanism. This buffer can be removed in most cases, but the compiler should optimize it out if it is not used.

The RAIL0_ macros provide some information to the application code on what is set up on the radioconf file.

RADIO_CONFIG_XTAL_FREQUENCY tells that the config was created for a 39 MHz crystal, while the last line externs a variable to access the configuration from the application.

The most important file, rail_config.c, will be analyzed in the subsequent sections. It's recommended to open a rail_config.c file for reference when reading this article.

Callback Functions#

The configurator generates the following callback functions, with empty body:

uint32_t RAILCb_CalcSymbolRate(RAIL_Handle_t railHandle)
uint32_t RAILCb_CalcBitRate(RAIL_Handle_t railHandle)
void RAILCb_ConfigFrameTypeLength(RAIL_Handle_t railHandle, const RAIL_FrameType_t *frameType)

These callbacks are implemented in RAIL as weak functions, but were used only in a config generated by an older configurator. Since they're not needed with the new configurator, these functions override the internal RAIL functions saving code space.

The Config Array(s)#

The largest part of the code is encoded into a config array (or arrays in case of a multi-PHY config), that looks like this:

const uint32_t Protocol_Configuration_modemConfigBase[] = {
  0x01040FF0UL, (uint32_t) phyInfo,
    /* 0FF4 */ 0x00000000UL,
    /* 0FF8 */ 0x0003C000UL,
    // more address-value pairs
  0xFFFFFFFFUL,
};

These are essentially address-value pairs decoded and loaded to the correct place by RAIL.

Keep in mind that these are EFR32 generation specific arrays. E.g., you cannot use an array on an xG13 device if it was generated for xG1. However, you can use the same config for devices from different families (MG, BG or FG) as long as the generation is the same. For more information see AN1244: EFR32 Migration Guide for Proprietary Applications.

Helper Arrays#

Some features, like the "3 out of 6" encoder, or the OOK dynamic slicer, need helper arrays that are referenced by a pointer in the main config array.

Some parts also need such helper arrays, e.g., xG23 generates a hfxoRetimingConfigEntries to configure minimal interference between the HFXO and the Local Oscillator of the receiver.

These arrays are usually passed either in the phyInfo or the main config array.

PHY Info Array#

One of the values of the config array is a pointer to another array called phyInfo. This holds information of the config for RAIL. For example, the function RAIL_GetBitrate() returns the bitrate for the config. While it can be calculated from the config values, it's faster and even takes less code space to store it in the phyInfo.

IR Calibration Helpers#

For details on the IR calibration, refer to the Calibration article.

The radio configurator generates an array and a struct that is connected to the IR calibration, and they look like this:

static const uint8_t irCalConfig[] = {
  20, 41, 2, 0, 0, 49, 17, 0, 0, 0, 1, 0, 2, 100, 0, 1, 1, 47, 0, 0, 7
};

static RAIL_ChannelConfigEntryAttr_t channelConfigEntryAttr = {
  //a lot of #ifdef macros
};

The first one, irCalConfig tells RAIL how to run the IR calibration on this PHY. This array is passed as part of the PHY Info Array. The second one, channelConfigEntryAttr is used to cache the calibration result. Although it looks complex, it's really just a couple of uint32_t, depending on how many IQ receive and transmit paths the part have.

When you run RAIL_CalibrateIr(), RAIL_ApplyIrCalibration() or RAIL_Calibrate(), it will store the IR calibration result channelConfigEntryAttr. If you load a channel with the channelConfigEntryAttr value of 0xFFFFFFFFUL, it will trigger the event RAIL_EVENT_CAL_NEEDED. This variable is passed as part of RAIL_ChannelConfigEntry_t.

Channel Entry Configuration#

RAIL_ConfigChannels() takes a RAIL_ChannelConfig_t argument. It is defined in the config file, and usually looks like this:

const RAIL_ChannelConfig_t Protocol_Configuration_channelConfig = {
  .phyConfigBase = Protocol_Configuration_modemConfigBase,
  .phyConfigDeltaSubtract = NULL,
  .configs = Protocol_Configuration_channels,
  .length = 1U,
  .signature = 0UL,
  .xtalFrequencyHz = 39000000UL,
};

The first argument is the base configuration: It is loaded immediately when you call RAIL_ConfigChannels(). The second parameter is part of RAIL 2.x multi-PHY features, we'll return to that later. The third parameter is a pointer to and array of RAIL_ChannelConfigEntry_t. The fourth parameter parameter is the number of elements in the previous array. Signature is used only on modules, and the last parameter is the crystal frequency in Hz, obviously.

The channel entry is an array of structs, looking like this (with a single element):

const RAIL_ChannelConfigEntry_t channels[] = {
  {
    .phyConfigDeltaAdd = NULL,
    .baseFrequency = 868000000,
    .channelSpacing = 1000000,
    .physicalChannelOffset = 0,
    .channelNumberStart = 0,
    .channelNumberEnd = 20,
    .maxPower = RAIL_TX_POWER_MAX,
    .attr = &entryAttr
#ifdef RADIO_CONFIG_ENABLE_CONC_PHY
    .entryType = 0,
#endif
#ifdef RADIO_CONFIG_ENABLE_STACK_INFO
    .stackInfo = NULL,
#endif
    .alternatePhy = NULL,
  },
};

The first parameter is again part of the multi-PHY features. maxPower serves as a limit of the transmit power on some channels to meet regulatory standards. Note that it's in deci-dBm, so unless it's set to RAIL_TX_POWER_MAX, you'll need the RAIL Utility, PA component or the PA conversion functions implemented to convert it. attr points to the entryAttr struct, which is part of the IR calibration system, see above. The final 3 arguments are used by some of our stacks, and rarely used in proprietary setup.

The remaining parameters set the frequency and the channel numbers. The frequency of a given channelNumber is calculated with this formula:

channelFrequency = baseFrequency + channelSpacing * ( channelNumber - physicalChannelOffset )

Note that you cannot set the frequency beyond a limit without changes in the configuration. These limits depend on the part, so please rely on the configurator's help. If you set the frequency beyond the configuration's capability, you'll get a RAIL_ASSERT_FAILED_SYNTH_VCO_FREQUENCY assert.

channelNumberStart and channelNumberEnd sets the limits of the entry, i.e., the first and the last channel.

API introduced in this Article#

Types and Enums#