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.