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 AN1253.

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

ChannelGroupIconChannelGroupIcon
icon. When you add an element to the channel-based configuration, it will initially inherit the PHY from the root protocol. Inside each channel group, the root protocol values can be overridden by toggling the "Customized" button or by selecting a predefined PHY.

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.

VIDEO

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 RAIL_StartRx(rail_handle, 0, NULL) it will search for 100kb/s packets, while RAIL_StartRx(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.

VIDEO

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

VIDEO

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 AN1119 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:

VIDEO

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:

VIDEO

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 RAIL_ConfigChannels() 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

protocolButtonprotocolButton
icon.

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.

VIDEO

With the above setup, calling RAIL_ConfigChannels(railHandle, channelConfigs[0], NULL) will load the European config, while RAIL_ConfigChannels(railHandle, 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 railHandle868 and railHandle915. During the initialization of the 868MHz protocol, you'll call RAIL_ConfigChannels(railHandle868, channelConfigs[0], NULL), and during the initialization of the 915MHz protocol, you'll call RAIL_ConfigChannels(railHandle915, channelConfigs[1], NULL). From this point, RAIL will automatically load the required config using the RAIL scheduler.

PA Configuration#

RAIL will automatically change everything needed for Multi-PHY during channel or protocol change, except one thing: The PA (power amplifier) configuration. On Wireless Geckos, changing the PA is needed if you change between a sub-GHz and a 2.4GHz config (or maybe between a low power and a high power 2.4GHz config).

To solve this, you should pass a function pointer as the last parameter of RAIL_ConfigChannels().

If you use the RAIL Utility, PA and RAIL Utility, Initialization component, you might want to set this to sli_rail_util_on_channel_config_change() - this will first call the function sl_rail_util_pa_on_channel_config_change() in the PA component, which selects the right PA, and then call sl_rail_util_on_channel_config_change(), what the application can implement.

If you don't use either of these components, you should create your own callback function, and call RAIL_ConfigTxPower() to select the right PA for the given config.

You can also use RAIL_EnablePaAutoMode() to enable automatic switching between the PAs or modes of the PA whenever you set the Tx power with RAIL_SetTxPowerDbm(). If you enable auto PA mode, you cannot use RAIL_ConfigTxPower().

This is implemented in both RAILtest and Simple TRX Multi-PHY.

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 RAIL_CalibrateIr() , RAIL_ApplyIrCalibration() or RAIL_Calibrate(), it will store the IR calibration result here. If you load a channel with the special entryAttr value of RAIL_CAL_INVALID_VALUE, it will trigger the event 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 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 RAIL_EVENT_CAL_NEEDED if you select a channel group that has a different IR calibration setup and 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 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 irCalConfig and entryAttr

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 RAIL_PrepareChannel() 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++){
  RAIL_PrepareChannel(rail_handle, calibration_channels[i]);        
  RAIL_CalibrateIr(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 RAIL_ConfigChannels(). Note that the first channel of the protocol is automatically configured, so RAIL_PrepareChannel() 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)