Multi-PHY Use cases and Examples#
In this article, we go through a couple of Multi-PHY examples and their setups using the Radio Configurator. If you haven't already, please read Introduction to Multi-PHY and Multiprotocol first to understand the various Multi-PHY and Multiprotocol capabilities of the Wireless Geckos. Some basic understanding of radio configs and the Radio Configurator is also required. You can find a more detailed description of the new Radio Configurator in Proprietary Radio Configurator Guide.
The article includes short videos to demonstrate how to set up the given use case.
Channel-based Multi-PHY#
The main idea behind Channel-based Multi-PHY is to create channel groups, each of which can have different PHYs. Changing between these channel groups is handled by RAIL: from the RAIL API, you can load the required PHY just by selecting a channel.
A PHY configuration exposes elements of the radio configurator for each of the
channel groups in the selected protocol. Channel groups can be added with the


Different Bitrate#
Let's say you need to work on the same 915MHz frequency, but on both 100kb/s and 500kb/s (with deviation 50kHz and 250kHz, respectively). Typically, this is used as an extension of a protocol that originally only supported the lower bitrate: The default bitrate is 100kb/s, but it's possible to switch to 500kb/s after a handshake.
In the above setup, channel 0 uses 100kb/s bitrate, while channel 1 uses 500
kb/s. Note that the channels are on the exact same frequency, but their
configuration is slightly different - you might call these virtual channels.
From RAIL, you can change between these virtual channels just as regular
channels. If you call sl_rail_start_rx(rail_handle, 0, NULL) it will search
for 100kb/s packets, while sl_rail_start_rx(rail_handle, 1, NULL) will search
for 500kb/s packets.
Different Packet#
Let's say your protocol defines a variable length data packet (1st byte is length) and a fixed, 2B long ACK packet.
In the above setup, channel 0 is for data packets, while channel 1 is for ACK packets (both Tx and Rx) - on the same frequency, so these are virtual channels just like in the previous example. A transmitting device would send a data packet on channel 0, then start listening on channel 1 for ACK, while a receiving device would receive on channel 0, and transmit an ACK on channel 1 after successful reception.
Note that the only difference between the PHYs is the length decoding algorithm itself. On the configurator GUI, we have two separate and complete configurations, but when generated, the configurator will detect what the difference is, and only generate a few lines of difference.
Asymmetric Protocol (Wireless M-Bus T Mode)#
Wireless M-Bus T mode defines a different protocol based on the direction:
Meter -> Other (M2O): 868.95MHz, 3of6 encoding, 100kb/s
Other -> Meter (O2M): 868.3MHz, Manchester encoding, 32.768kb/s
The above example implements the "Other device" (typically collector). It receives M2O on channel 0 and transmits O2M on channel 1. The implementation required two pre-defined PHYs without any further modifications.
Transmitting modeT M2O messages requires other tricks on the EFR32xG1x parts, see Using RAIL for Wireless M-Bus Applications with EFR32 for details.
Wide Bandwidth for CSMA/CA#
Some regulatory standards also standardize the bandwidth for CSMA/CA, but your protocol would have better sensitivity with a narrower bandwidth. The trick here is to understand that this is actually a subset of the Asymmetric protocol use case: Since CSMA/CA will listen on the same channel that you will use for transmitting and the bandwidth setting in the configurator only affects receive performance, you can define a virtual channel with your setup with the required wider bandwidth:
In the above example, channel 0 uses the bandwidth recommended by the configurator, while channel 1 has a fixed, wide bandwidth. You would receive on channel 0 with high sensitivity, and transmit on channel 1. The CSMA/CA preceding the transmit would also happen on channel 1 using the fixed, wide bandwidth.
You can check the bandwidths in the autogen/radioconf_generation_log.json
file: 4.8kHz for channel 0 and 100kHz for channel 1.
Uneven Channel Spacing#
Channel groups can be used in "single-PHY" mode as well: one example of that is setting up uneven channel spacing. Let's say you have to receive on 868.0MHz, 868.33MHz and 868.4MHz - so the channel spacing is 330kHz and 70kHz at the same time. You obviously can't configure this with a single channel spacing field, but you can with channel groups:
With the above setup, you will have channels 0, 1 and 2 on the required frequencies.
How Channel-based Multi-PHY Actually Works#
See the Multi-PHY capabilities chapter in the Understanding RAIL config files article.
Protocol-based Multi-PHY#
The main idea behind Protocol-based Multi-PHY is to create multiple - completely independent - configs, and use sl_rail_config_channels() to select the required one.
The order of protocols will be used in the generated channelConfigs[] array,
i.e. the first protocol in the list will have index 0 and so on.
A protocol can be added to the config with the


Multiple Regions#
Let's say your device operates on the 868MHz 500kb/s pre-configured PHY in Europe, and on the 915MHz 100kb/s pre-configured PHY in North America.
With the above setup, calling sl_rail_config_channels(rail_handle,
channelConfigs[0], NULL) will load the European config, while
sl_rail_config_channels(rail_handle, channelConfigs[1], NULL) will load the
North American config.
Configuration for a DMP Setup#
The above setup can also be used in a Dynamic Multiprotocol setup. You'll need
two rail handles, let's call them rail_handle_868 and rail_handle_915. During
the initialization of the 868MHz protocol, you'll call
sl_rail_config_channels(rail_handle_868, channelConfigs[0], NULL), and during
the initialization of the 915MHz protocol, you'll call
sl_rail_config_channels(rail_handle_915, channelConfigs[1], NULL). From this
point, RAIL will automatically load the required config using the RAIL
scheduler.
IR Calibration in Multi-PHY Configs#
If you're not sure what IR calibration is, please refer to the Calibration article.
Configuration and Cache Variables in rail_config.c#
This section is copied from the Understanding RAIL Config Files article.
The radio configurator generates two elements - an array and a struct - that are involved in IR calibration. They look like this:
static const uint8_t generated_irCalConfig[] = {
24, 69, 3, 6, 4, 16, 1, 1, 1, 3, 1, 6, 0, 16, 39, 0, 0, 10, 0, 0, 0, 0, 0, 0, 0
};
RAIL_ChannelConfigEntryAttr_t generated_entryAttr = {
{ 0xFFFFFFFFUL }
};The first one, generated_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, generated_entryAttr is used to cache the calibration result. When you run
sl_rail_calibrate_ir()
,
sl_rail_apply_ir_calibration()
or
sl_rail_calibrate(),
it will store the IR calibration result here. If you load a channel with the
special entryAttr value of
SL_RAIL_CAL_INVALID_VALUE,
it will trigger the event
SL_RAIL_EVENT_CAL_NEEDED.
This is passed as part of the channel entry configuration.
IR Calibration in Protocol-based Multi-PHY#
IR calibration should be performed for all protocols. This will be requested from the application through the SL_RAIL_EVENT_CAL_NEEDED event. After the calibration is performed on each protocol, RAIL can use the cached results from these prior operations. When doing so, the calibration values will be applied automatically and near-instantaneously.
IR Calibration in Channel-based Multi-PHY#
The radio configurator will automatically detect if channel groups are similar
enough to use a common calibration (e.g. only the packet is different). In these
cases, both irCalConfig and entryAttr will be the same. Calibration should
be performed on each channel group that has its own irCalConfig and
entryAttr. RAIL will detect this and request calibration through
SL_RAIL_EVENT_CAL_NEEDED
if you select a channel group that has a different IR calibration setup and
SL_RAIL_CAL_INVALID_VALUE
in entryAttr. Once calibration is performed on each channel group that needs
it, RAIL can then cache these values for future use. As in Protocol-based
Multi-PHY, with cached results the calibration values will be applied
automatically and near-instantaneously.
Calibration on Multi-PHY at Startup#
In some cases, using the SL_RAIL_EVENT_CAL_NEEDED event to calibrate is not
viable, and you might want to explicitly request calibration on everything at
startup. To do that, you should:
loop through all protocols
loop through all channel groups that have their own
irCalConfigandentryAttr
For the latter, you should check the generated rail_config.c. There's no easy way to figure out which channel to calibrate from code. To loop through the channel groups, you can use the API sl_rail_prepare_channel() to trigger the PHY loading. The calibration code will likely look something like this:
#define NUMBER_OF_GROUPS 5
const int16_t calibration_channels[NUMBER_OF_GROUPS] = {0, 10, 25, 50, 65};
for(uint8_t i=0; i<NUMBER_OF_GROUPS;i++){
sl_rail_prepare_channel(rail_handle, calibration_channels[i]);
sl_rail_calibrate_ir(rail_handle, NULL);
}In the above example, we have 5 channel groups that require calibration
(NUMBER_OF_GROUPS). The first channel of each channel group is stored in
calibration_channels.
If you have more protocols, you can simply load them using sl_rail_config_channels(). Note that the first channel of the protocol is automatically configured, so sl_rail_prepare_channel() is only needed for Channel-Based Multi-PHY.
Multi-PHY in Connect#
The Connect stack has some limitations on Multi-PHY features:
It doesn't support Protocol-Based Multi-PHY: Connect always loads the first protocol (
channelConfigs[0])It doesn't support PA change, so Multi-PHY channels should use the same frequency band (i.e. either 2.4GHz or sub-GHz)