(*) Make sure to read this one, as DMP is often misunderstood and used in applications that could be better served by multi-PHY. We recommend that you (at least) browse through the other tutorials before reading this (DMP) tutorial, since DMP affects most APIs in one way or another.
Note that this tutorial will not provide you with all of the knowledge required for DMP development. Rather, it will summarize what's already documented, and highlight differences between RAIL single protocol and multiprotocol operations.
Some chapters below are not important if you need RAIL-Bluetooth DMP, but even in that case most chapters are still relevant because they describe RAIL development in a DMP environment.
Note that this tutorial is slightly outdated in Studio v5, since the we now have components, not plugins, and the way we initialize RAIL in the examples are performed by the RAIL Utility, Initialization component, and not by the application. However, these are minor details and the wast majority of this article is accurate.
What is Dynamic Multiprotocol
Dynamic multiprotocol time-slices the radio and rapidly changes configurations to enable different wireless protocols to simultaneously operate reliably in the same application.
A typical usecase involves a RAIL-based proprietary protocol and the Silicon Labs Bluetooth stack running on the same chip at the same time. Since Bluetooth communication is periodic and therefore very predictable, other protocols can use the radio between Bluetooth communication bursts. However, it's also helpful to use DMP when all involved protocols are proprietary RAIL-based, to benefit from the code clarity provided by the separate RAIL instances.
From a firmware perspective, RAIL-DMP is similar to an object-oriented programming (OOP) class. It provides the radio driver, and multiple instances can be "created". Each instance can use the driver in the same way, and do so almost independently.
However, from a hardware perspective the chip has only a single instance of the radio. As such, RAIL-DMP is similar to multitasking: the same resource must be shared among multiple "tasks" demanding its time - this sharing is managed by the radio scheduler.
The Radio Scheduler
The radio scheduler's job is to give (or revoke) radio hardware access to (or from) the protocols running on it. To accomplish this it implements cooperative, preemptive multitasking.
It's cooperative: Protocols should tell the scheduler when and for how long they need the radio, and protocols should yield the radio when not using it anymore
It's preemptive: Protocols should define priority to each radio task, and the scheduler will abort tasks if a higher priority radio task must run
Note that the radio scheduler schedules only radio tasks. Although DMP is often used with an RTOS (like Micrium OS), RTOS tasks and Radio tasks - despite the common name - are very different terms. To make this less confusing, our documentation sometimes uses radio operation instead of radio task.
For the radio scheduler to calculate the time required for a given radio operation, it must know how much time it takes to change radio access from one protocol to another. Therefore, in DMP, a protocol change always takes 450us on EFR32xG1x, which accommodates the worst-case protocol change time. This makes DMP protocol change much slower than Multi-PHY PHY change.
Protocol change time in RAIL 2.5.x (Flex 2.4.x) and earlier was 750us (and it might change further in the future).
Setting up a project for RAIL-RAIL DMP
If you need RAIL+Bluetooth DMP, follow AN1134. This chapter describes how to set up DMP in a RAIL-only project.
To use DMP, you should first switch to the multiprotocol RAIL library, under plugins. Since DMP has a bigger memory footprint (both RAM and code), it's provided in a library which is not enabled in single protocol examples.
Next, you'll need PHY configurations for your protocols. You can either use Protocol Based Multi-PHY (see the Multi-PHY usecases and examples tutorial for details), or you can use embedded protocols, e.g. IEEE 802.15.4 2.4GHz 250kbps by calling RAIL_IEEE802154_Config2p4GHzRadio().
Micrium OS
Micrium OS is not technically required for RAIL DMP, although it's highly recommended to clearly separate protocols (and for RAIL+Bluetooth DMP, we actually only support Micrium-enabled projects.) Hence, Micrium OS will not be discussed in this article - it doesn't impact how you use RAIL APIs in RAIL-only DMP.
RAIL initialization
Let's see how one RAIL protocol should be initialized in DMP:
railSchedState is a new static variable required for RAIL. This keeps the state of the protocol when it's not active (e.g. configured Rx options)
railCfg.protocol is set to NULL. This is also a DMP specific field, but it's only used by the Silicon Labs Bluetooth stack.
RAILCb_Generic is now static: Since all protocols will have their own callback functions, we should either make the function names unique, or put them in different C files as static. Although technically it's possible to use a common callback function for all protocols, it's not recommended as the readability of the code is much better with separate event handlers.
halInit() is no longer called from initRadio1() - since we have multiple protocols to support, it makes more sense to set up RAIL requirements elsewhere in a protocol-independent init function
This code snippet initializes a single protocol. However, all remaining protocols can be initialized in the same way, noting that railHandle, railSchedState and RAILCb_Generic should be different for all protocols (probably by placing in separate C files).
From this point you can use RAIL almost the same way as in single protocol mode. Since almost all APIs have a railHandle argument, RAIL will know which protocol you want to access. However, you should be careful with:
protocol independent APIs, i.e. the ones that don't have a railHandle argument
APIs that affect the state of the radio scheduler, i.e. schedule or stop a radio task
Protocol independent APIs
There are a few APIs that always work the same way, regardless of which protocol they were called from:
RAIL_SetTime()/RAIL_GetTime() - the timebase (which is also used for timestamping) is common for all protocols. It is not recommended to call RAIL_SetTime() in a DMP application (and calling it in a single protocol application can still be dangerous)
RAIL_ConfigMultiTimer() - MultiTimer mode is a requirement in DMP, so it's enabled by default and cannot be disabled
RAIL_Sleep()/RAIL_Wake() - Sleep management should be handled protocol-independently (e.g. in the idle task of the RTOS)
There are some other APIs that don't require railHandle, but they usually need a packet handler or timer handler, and so are still tied to a protocol.
Scheduling a finite radio task
The following APIs will trigger the radio scheduler to schedule a finite length new radio task:
RAIL_ScheduleRx()
RAIL_StartTx()
RAIL_StartScheduledTx()
RAIL_StartCcaCsmaTx()
RAIL_StartCcaLbtTx()
RAIL_StartAverageRssi()
(Note that RAIL_StartRx() and RAIL_StartTxStream() is not listed, as these as these are not finite length tasks.)
All the above APIs have the optional argument RAIL_SchedulerInfo_t. In single protocol mode, this argument is ignored, and can be NULL. In multiprotocol mode, this is the base of the scheduling, with its 3 member:
priority - 0 to 255, 0 being the highest. Equal priority does not preempt
slipTime - maximum time the task can be delayed due to other protocols, in us
transactionTime - the time the task takes, in us
When the radio hardware is available, none of these scheduler info arguments are used. However, if two tasks are scheduled at the same time, the radio scheduler will try to stagger the operations using slipTime in order to satisfy both tasks. If that fails, the lower priority task will not get access to the radio.
It is also possible that an application schedules a high priority radio task when a conflicting lower priority task has already started. In that case, the lower priority task will be aborted (possibly in the middle of frame transmission).
If RAIL_SchedulerInfo_t is NULL, the scheduler will assume all parameters to be 0, i.e. highest priority, with no slipTime - it is highly recommended to not use this feature, and it's only mentioned here to simplify debugging.
Each protocol can schedule at most a single finite task.
After each radio task the application must yield the radio. That can be achieved by calling RAIL_YieldRadio() or RAIL_Idle() - the latter also turns off the radio, which should have already occurred for finite tasks, so it's recommended to use RAIL_YieldRadio(). Manual yielding is required because the scheduler doesn't know about any automatic follow-up tasks (like ACKs) - you might want to yield immediately after receiving a packet, or you may instead want to wait for the auto ACK to go out.
Since yielding the radio will also call the radio scheduler to start/schedule the next radio task, you must first capture everything you need that might be overwritten by another protocol after yield, even in interrupt context. For example, timestamps of a transmitted packet must be accessed before yielding.
A Tx operation scheduled in the future can be cancelled using RAIL_Idle() or RAIL_StopTx(), where the latter doesn't yield the radio. An Rx operation scheduled in the future can only be cancelled using RAIL_Idle().
Background Rx
RAIL_StartRx() receives special handling in the radio scheduler, as it schedules an infinite task - namely, background Rx. The main difference when compared to finite tasks is that an infinite task sets the "default" state of the radio. It can be aborted by higher priority finite tasks, but it will be resumed automatically after the higher priority task is finished. For background Rx, this practically means that the radio will automatically return to Rx after Tx or packet reception, and you don't have to use auto state transitions like you do in single protocol mode. (Currently, the only infinite task supported by RAIL is background Rx).
Keep in mind that RAIL does not store the channel of background Rx: if you interrupt background Rx with a finite task on a different channel, the channel of the background Rx will change as well. E.g. if you're receiving in channel 0, then transmit on channel 1, the radio will "return" to receiving on channel 1. To change the background Rx channel back to 0, simply call RAIL_StartRx() again after you yield the radio.
RAIL_StartRx() has the same RAIL_SchedulerInfo_t configuration as the finite tasks, but depends only on the priority member - which, in general, should be the lowest priority used by the protocol.
Background Rx can be aborted using RAIL_Idle(), which also yields the radio. In DMP mode, it's recommended to only use RAIL_Idle() to stop background Rx.
It is also possible to change the priority of the background RX using RAIL_SetTaskPriority() - this is useful e.g. to increase the priority during packet reception, or after a particular packet.
TxStream
RAIL_StartTxStream() is called a debug task: A debug task must have the highest priority, because it can't be aborted. Otherwise, it works similarly to infinite tasks.
This API is different from all others as it doesn't use RAIL_SchedulerInfo_t. Since a stream can't really be aborted, the radio scheduler will handle this with the highest priority and no slip time.
Stream can be stopped with RAIL_StopTxStream(), RAIL_Idle() or RAIL_YieldRadio(), although it is recommended to use RAIL_StopTxStream() for clarity.
Auto state transitions
You might wonder, "what's the point of auto state transitions in DMP, if background Rx changes the default state to Rx?" The added value of auto state transitions is that the resulting state will inherit the original state's priority.
For example, you probably want the ACK reception on the same priority as the transmission itself, so you can set RAIL_SetTxTransition() to set up receive after the transmission for the ACK, and configure a timeout for it using RAIL_SetStateTiming().
If you have automatic state transition set up, you should only yield the radio after the whole operation (e.g. ACK reception or timeout) is finished. It's highly recommended to have automatic timeouts to prevent a high priority infinite task, but if you haven't set up timeouts, you can cancel the receive and yield the radio using RAIL_Idle().
To summarize:
In RAIL single protocol, the default state is always idle, and auto state transitions should be used to always return the radio to Rx. In DMP, background Rx should be used for this.
In single protocol mode, Rx state started by auto state transition is the same as Rx state started by StartRx. In DMP, auto state transitions are intended for ACK, as they inherit the (usually high) priority of the preceding task.
RAIL includes a timer virtualization service, called MultiTimer. Since its memory footprint is not negligible, this feature is disabled by default in single protocol mode. However, in multiprotocol mode, it is always enabled, since multiple timers are required for the multiple protocols. Therefore, using MultiTimers in your protocol implementations has no drawback in DMP.
Error handling
In single protocol RAIL, you generally need to places to handle errors:
RAIL_StartXYZ()/RAIL_ScheduleXYZ() might return an error. If no error were returned, the operation was either finished, or an event will be triggered
The event handler, where either a success or error event can be triggered, e.g. RAIL_EVENT_TX_PACKET_SENT or RAIL_EVENT_TX_CHANNEL_BUSY
In DMP, there's a third error case to handle above the previous two: You should always handle RAIL_EVENT_SCHEDULER_STATUS, where you might receive an error by calling RAIL_GetSchedulerStatus(). It's usually obvious which API call failed, e.g. RAIL_SCHEDULER_STATUS_SINGLE_TX_FAIL means a RAIL_StartTx() failed, but it might not be possible to know the original call resulted in error.
This was implemented because when you call e.g. RAIL_StartTx, the radio scheduler just creates task. When that task is running, it will actually call the single protocol RAIL_StartTx, and if that returns an error, it will trigger an RAIL_EVENT_SCHEDULER_STATUS event.
The event RAIL_EVENT_SCHEDULER_STATUS is also used when a higher priority task interrupts an ongoing radio operation, in which case, RAIL_SCHEDULER_STATUS_EVENT_INTERRUPTED will be returned by RAIL_GetSchedulerStatus().
Debugging
When debugging DMP, Rx/Tx PRS channels are still very useful, see the tutorial about debugging for details.
For DMP based code, writing out to a GPIO when a protocol is scheduled/unscheduled is really useful. This can be easily done by setting a GPIO on RAIL_EVENT_CONFIG_SCHEDULED and clearing it on RAIL_EVENT_CONFIG_UNSCHEDULED.
Recommendations and practices
Although these practices are very important for DMP applications, you should consider applying them in single protocol applications as well, to simplify a potential future port to DMP:
Only use RAIL_Idle() when it's necessary - In RAIL 1.x almost all APIs required that they be called from idle mode. This requirement was removed in RAIL 2.x, so it's very rarely needed, and since RAIL_Idle() also yields the radio, it should be very rarely used in DMP.
Use the scheduling Rx/Tx features as much as possible - i.e. don't use timers and start Rx/Tx at timer interrupt, use the corresponding RAIL schedule API instead. This helps the radio scheduler to resolve conflicts with the configured slipTimes, or at least promptly let the application know about an unavoidable conflict.
Set the slipTime/transactionTime correctly. Again, this helps the radio scheduler to resolve conflicts.
How not to use DMP
Using multiple DMP instances for multiple radio tasks (e.g. one protocol for advertising, one for connection, or one for Tx, one for Rx) is bad design: while it works, each protocol instance has a significant memory footprint, and switching time between protocols is much slower than switching Rx/Tx inside a protocol. At the moment, it is recommended to use DMP only for serving separate protocol stacks.
Using DMP to handle multiple PHYs for the same protocol stack is also not recommended: Multi-PHY is a much better solution for that, see the introduction to Multi-PHY and Multiprotocol tutorial for more details.
Related documentation
UG305: Dynamic Multiprotocol User's Guide - You can skip the chapters that won't affect your design, e.g. if you don't plan to use zigbee, you can skip the zigbee chapters. This article somewhat overlaps with UG305 as we provide a short summary of the basics here.
If you find any conflicts between this article and the above documents, give those documents priority as they will more quickly receive updates to track new RAIL DMP features and guidance.
The BT factor on the Gaussian pulse shaping filter is often used for narrowing spectral emissions at the Tx side to fit into a regulatory / applications standard mask requirement. On how to adjust the BT factor on the EZRadioPro familiy you can read here.
Note that adjusting the BT factor will only alter the spectrum emission of the modulated signal, it will not affect discrete spurious emissions that are not part of the modulation, like these.
Adjusting the BT factor, however does have an effect on sensitivity at the Rx side. This is quite often overlooked and may result in a huge loss in the link budget. To that end find below two graphs showing sensitivity versus BT factor parameterized with modulation index. (These measurements were taken on revision revB1B but hold up for revC2A/ A2A.)
Key takeaway:
Don’t ever go to a lower BT factor than 0.25 if the modulation index <= 2. If narrower emissions are required lower the modulation index instead.
Also be prepared that the frequency offset tolerance of the Rx degrades by dropping BT and / or the modulation index.
If you find any contradiction between the tutorials and the API documentation, just always remember that the API documentation is more accurate.
This tutorial series focuses on the embedded code running on Wireless Geckos. Configuring the radio correctly is equally important, see AN971 for details on that for Studio v4 and AN1253 for Studio v5.
The tutorial series is currently under review and update for the Studio v5 updates. Although RAIL itself haven't changed much, some critical tools did. For details, see AN1254. The tutorials are mostly accurate for both v4 and v5. On those articles where an update will be needed in the near future, a note is added to the first couple of paragraphs.
Getting Started
You should start with these tutorials. All others are based on these, as these are the minimum to develop useful applications on RAIL.
RAIL Utility, Initialization: how it works, how to init RAIL without it - coming soon (studio5 specific)
Power manager with RAIL - coming soon (studio 5 specific)
Advanced Tutorials
These tutorials describe features that are rarely needed - they are available and can be used when necessary, but you probably won't use them all in your application. Generally, you should read them only if you think you need them.
These tutorials are about the Multi-PHY and multiprotocol capabilites of RAIL. If you plan to use that feature, we recommend to read the introduction then what you need, depending on your application.
In this article, we go through a couple of example usecases and their setups using the Multi-PHY 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.
This tutorial includes videos (without audio) to guide you through the setup on the Configurator GUI.
Note that the videos on this tutorial were recorded on Studio v4, and Studio v5 follows a slightly different configurator workflow. Until the videos are updated, you can find details of the new workflow in AN1253.
Channel-based Multi-PHY
The theory
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.
Channel groups can be created with the icon. By default, these new channel groups have no PHY configuration.
A PHY configuration can be added using the icon. A PHY configuration exposes elements of the radio configurator for each of the channel groups in the selected protocol. The PHY applied to any specific channel group can then be customized by changing the values for each element present in the PHY configuration.
Once you add something to the channel-based configs (as described above), the protocol-based setup will be disabled.
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 which originally only supported the lower bitrate: The default bitrate is 100k, but it's possible to switch to 500k 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 RAIL_StartRx(railHandle, 0, NULL) it will search for 100k packets, while RAIL_StartRx(railHandle, 1, NULL) will search for 500k 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 are the length decoding algorithm itself. This is because the configurator can keep a complete fixed length and a variable length configuration, and changing between those is possible by just changing the the frame length algorithm.
Asymmetric protocol (Wireless M-Bus T mode)
Wireless M-Bus T mode defines a very 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.
Transmitting modeT M2O messages requires other tricks, 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 usecase: Since CSMA/CA will listen on the same channel that you will use for transmit 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 the 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 bandwidth configured for channel 0 in the efr32_radio_configurator_log.txt file: It's 4.8kHz.
Uneven channel spacing
Channel groups can be used in "single-PHY" mode as well: one example for 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.
Power limited channels
This is still a "single-PHY" usecase: Let's say you have to receive on 40 channels, starting at 2.402GHz with channel spacing of 1MHz. However, to meet regulatory standards, you must lower the transmit power on the first and last channel to 16dBm.
The above setup meets the requirements. Note that channel 0 and 39 are both defined twice. First as limited power, then the same as the other 38 channels. The order of channel groups are important: RAIL always loads the first channel group which includes the requested channel.
Channel number offset was used in the above video. The frequency of a given channel is calculated as follows:
Channel number offset is the same as First channel number if it's disabled. This is useful for uneven channel spacing, since you want the channel group to start on the selected base frequency, and not offset by firstChannelNumber*channelSpacing. However, setting it to 0 is useful for overrides: This way, the power limited channel 39 in the above config will be exactly the same as in the original channel group. See understanding RAIL config files tutorial for more details.
You also have to keep in mind that the actual output power depends on your board and antenna design. The recommended way to solve this problem is to create a PA conversion curve documented in AN1127, and enable the PA conversions plugin with the generated header:
Simplicity Studio ships with conversion curve headers for Silicon Labs boards - if you create a project for a Silicon Labs board, simply enable the PA Conversions plugin to include the correct header for your board.
Note that RAIL only lowers and limits the output power on channel change, it won't increase it back if you switch to an unlimited channel from a limited one.
In general, you should disable all advanced settings if you change much in a configuration - the same guidance applies for Multi-PHY. What makes this advice even more important for Multi-PHY is that it's easy to miss a problem if something misconfigured under the protocol setup makes your channel group misbehave.
Inheritance from protocol
When you add an element to the channel-based configuration, it will initially inherit a value from the root protocol - and that is the only time this inheritance happens. If you later change the root protocol (ex: switch to a pre-configured PHY), doing so won't update the channel-based config properties for any channel groups, even if you hadn't changed the value inherited from the original protocol. This behavior is depicted in the following video:
Channel group change performance
If you have significant differences between channel groups, channel changing that involves a channel group change will take somewhat longer - in most cases, this is not too significant, but you should measure and calculate with it if you need short channel switch times (PRS channels can be used for measuring this time, see the tutorial about debugging).
Also, if you have 3 channel groups, and only a single field is different between A and B, but C is very different compared to the other two, channel change between A and B will take the same time as channel change between C and A or C and B.
If you have a lot of channel groups in the protocol, this could also cause a small but measurable delay during channel change: it takes some time for RAIL to look up the channel group which includes the requested channel.
In some cases, it's possible to work around these problems by combining channel-based and protocol-based Multi-PHYs:
Use Channel-based multi-PHY between channels where quick channel change is required
Use Protocol-based multi-PHY where channel change time is not a priority
Protocol-based Multi-PHY
The theory
The main idea behind Protocol-based Multi-PHY is to create multiple - completely independent - configs, and use RAIL_ConfigChannels() to select the required one.
Protocols can be created with the icon.
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.
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 RAIL_ConfigChannels(railHandle, channelConfigs[0], NULL) will load the European config, while RAIL_ConfigChannels(railHandle, channelConfigs[1], NULL) will load the North American config.
RAILTest support
Protocol-based Multi-PHY is also useful if you want to test multiple configuration setups using RAILTest: You can change between protocols using the setConfigIndex RAILTest command.
Configuration for a DMP setup
The above setup can be also 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 high power 2.4GHz config).
To solve this, you should pass a function pointer as the last parameter of RAIL_ConfigChannels. In the examples, we call it RAILCb_RadioConfigChanged and it will be called when RAIL switches between channel groups, with the RAIL_ChannelConfigEntry_t as an argument. You can use that argument to figure out if you need to change the PA or not, and call RAIL_ConfigTxPower if needed.
This is implemented in both RAILTest and in SimpleTRX-MultiPHY.
IR Calibration in Multi-PHY configs
If you're not sure what IR calibration is, please refer to the Calibration tutorial.
Configuration and cache variables in rail_config.c
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 (API doc), 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:
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 calibrationChannels.
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
It doesn't support PA change, so Multi-PHY channels should use the same frequency band (i.e. either 2.4GHz or sub-GHz)
To get the most out of this tutorial, some familiarity with the radio configurator is required, and it's also recommended that you understand the basics of radio protocol stacks (i.e. the OSI model).
With a customizable radio, the Wireless Gecko is a great option to support any radio configuration you need. You can set it up for standard IEEE 802.15.4, Bluetooth Low Energy, WMBus, or even the protocol (designed 30 years ago) used by your garage door opener. However, what if you need more than one of those standards? With single protocol radios, this requires a distinct 15.4 radio chip, a Bluetooth chip, and so on for each supported standard. Alternatively, with a single Wireless Gecko, you can store multiple "setups" in your device and switch between them. The only limitation is that it's still a single radio - you can't use multiple "setups" on the radio at the same time.
The best way to manage the sharing of this single radio resource depends on the particular usecase targeted by your application. We provide different solutions (discussed below) to help you identify an optimal approach for your design.
Channel-based Multi-PHY
The most common usecase in proprietary wireless is the need to support different PHY configurations. For example:
You always handle received packets the same way, but you have to receive at multiple bitrates
You have an asymmetric protocol, i.e. the Rx configuration is not the same as the Tx configuration
You need wider bandwidth for CSMA/CA than your normal Rx mode to pass regulatory standards
You have to handle different packet formats, but not at the same time: the upper layers of the stack decide which one to use
Although the PHYs are technically the same, there is a power limitation on a given channel to pass regulatory standards
The PHYs are technically the same, but the channel map is uneven - it's not possible to configure it using channel spacing
In these cases, Channel-based Multi-PHY is the best solution. It's called Channel-based, because in RAIL, you only need to change the channel to change the PHY.
This is not as widely used as Channel-based Multi-PHY, but it's the next logical step. The typical usecases:
Your device will support multiple regions, and you choose one during configuration, but the device will rarely switch between those.
Your device will support multiple protocols, but the device will rarely switch between those.
For this, we recommend Protocol-based Multi-PHY. In this case, you can switch between protocols by calling RAIL_ConfigChannels(), which will load the whole configuration.
Protocol based Multi-PHY is also useful if you want to test multiple configuration setups using RAILTest: You can change between protocols using the setConfigIndex RAILTest command.
In general, you should consider your application requirements: If you want to have (for example) 20 channels, but those 20 channels are defined differently in some application configurations (typically by region), you should use Protocol-based Multi-PHY. A protocol change will be slower than a channel change, but the application doesn't even need to know which protocol is in use, since it still sees the same 20 channels through RAIL.
Otherwise, in almost all cases, Channel-based Multi-PHY is a better choice.
Dynamic Multiprotocol (DMP) between RAIL-based protocols
If you have full protocol stacks that are completely independent, it makes sense to develop the PHY and Link layer independently as well. These layers communicate with RAIL, and the cleanest solution to this problem is Dynamic Multiprotocol. Typical usecase:
One protocol is used to communicate with key fobs, while another is used to communicate in a home automation network
In Dynamic Multiprotocol, the RAIL scheduler decides which protocol is active.
Dynamic Multiprotocol is more capable than Multi-PHY, but also more complex, so it has some drawbacks when compared to Multi-PHY:
Protocol change takes 450us on EFR32xG1x. This is a fixed worst-case delay, because the RAIL scheduler must plan time-sharing of the radio resource using calculations on a known sufficient time span in order to accomodate protocol switches.
It requires the multiprotocol version of the RAIL library, which has a bigger memory footprint (both RAM and code) than the single protocol version.
Writing RAIL application in DMP will be the subject of this tutorial.
Dynamic Multiprotocol between RAIL-based and other Silicon Labs stack protocols
The usecase for this is straight forward:
If you need Bluetooth and a RAIL-based protocol in the same application
At the moment, Silicon Labs only provides such a DMP solution based on the Bluetooth stack and RAIL. You can access it by installing the Bluetooth SDK and starting one of the DMP applications. See AN1134 for more details, and an upcoming tutorial detailing DMP usage from a RAIL perspective.
Other multiprotocol solutions
You can find other types of multiprotocol mentioned in some Silicon Labs documents, but they are rarely used:
Programmable multiprotocol means multiple firmware images for each protocol, and the protocol is decided during the manufacturing process, i.e. a device is either programmed to be a Bluetooth or a Zigbee device.
Switched multiprotocol means two firmware images, one for each protocol. A bootloader loads the firmware needed for the protocol, and the two firmware can communicate with each other in a shared NVM storage. Protocol switching in switched multiprotocol is rather slow, and using DMP is almost always a better choice.
Concurrent multiprotocol means that the PHY and maybe the lower layers of the stacks are the same, but the upper layers are different. E.g. You can run two proprietary 15.4 protocol on the same channel at the same time, but you don't really need multiprotocol specific APIs to support it.
Proprietary Knowledge Base
RAIL tutorial: Dynamic Multiprotocol (DMP)
This tutorial builds upon the material covered in previous offerings:
(*) Make sure to read this one, as DMP is often misunderstood and used in applications that could be better served by multi-PHY. We recommend that you (at least) browse through the other tutorials before reading this (DMP) tutorial, since DMP affects most APIs in one way or another.
You can find additional RAIL tutorials on the table of contents page.
Note that this tutorial will not provide you with all of the knowledge required for DMP development. Rather, it will summarize what's already documented, and highlight differences between RAIL single protocol and multiprotocol operations.
Some chapters below are not important if you need RAIL-Bluetooth DMP, but even in that case most chapters are still relevant because they describe RAIL development in a DMP environment.
Note that this tutorial is slightly outdated in Studio v5, since the we now have components, not plugins, and the way we initialize RAIL in the examples are performed by the RAIL Utility, Initialization component, and not by the application. However, these are minor details and the wast majority of this article is accurate.
What is Dynamic Multiprotocol
Dynamic multiprotocol time-slices the radio and rapidly changes configurations to enable different wireless protocols to simultaneously operate reliably in the same application.
A typical usecase involves a RAIL-based proprietary protocol and the Silicon Labs Bluetooth stack running on the same chip at the same time. Since Bluetooth communication is periodic and therefore very predictable, other protocols can use the radio between Bluetooth communication bursts. However, it's also helpful to use DMP when all involved protocols are proprietary RAIL-based, to benefit from the code clarity provided by the separate RAIL instances.
From a firmware perspective, RAIL-DMP is similar to an object-oriented programming (OOP) class. It provides the radio driver, and multiple instances can be "created". Each instance can use the driver in the same way, and do so almost independently.
However, from a hardware perspective the chip has only a single instance of the radio. As such, RAIL-DMP is similar to multitasking: the same resource must be shared among multiple "tasks" demanding its time - this sharing is managed by the radio scheduler.
The Radio Scheduler
The radio scheduler's job is to give (or revoke) radio hardware access to (or from) the protocols running on it. To accomplish this it implements cooperative, preemptive multitasking.
Note that the radio scheduler schedules only radio tasks. Although DMP is often used with an RTOS (like Micrium OS), RTOS tasks and Radio tasks - despite the common name - are very different terms. To make this less confusing, our documentation sometimes uses radio operation instead of radio task.
For the radio scheduler to calculate the time required for a given radio operation, it must know how much time it takes to change radio access from one protocol to another. Therefore, in DMP, a protocol change always takes 450us on EFR32xG1x, which accommodates the worst-case protocol change time. This makes DMP protocol change much slower than Multi-PHY PHY change.
Protocol change time in RAIL 2.5.x (Flex 2.4.x) and earlier was 750us (and it might change further in the future).
Setting up a project for RAIL-RAIL DMP
If you need RAIL+Bluetooth DMP, follow AN1134. This chapter describes how to set up DMP in a RAIL-only project.
To use DMP, you should first switch to the multiprotocol RAIL library, under plugins. Since DMP has a bigger memory footprint (both RAM and code), it's provided in a library which is not enabled in single protocol examples.
Next, you'll need PHY configurations for your protocols. You can either use Protocol Based Multi-PHY (see the Multi-PHY usecases and examples tutorial for details), or you can use embedded protocols, e.g. IEEE 802.15.4 2.4GHz 250kbps by calling
RAIL_IEEE802154_Config2p4GHzRadio()
.Micrium OS
Micrium OS is not technically required for RAIL DMP, although it's highly recommended to clearly separate protocols (and for RAIL+Bluetooth DMP, we actually only support Micrium-enabled projects.) Hence, Micrium OS will not be discussed in this article - it doesn't impact how you use RAIL APIs in RAIL-only DMP.
RAIL initialization
Let's see how one RAIL protocol should be initialized in DMP:
Let's compare it to the very first RAIL tutorial:
railSchedState
is a new static variable required for RAIL. This keeps the state of the protocol when it's not active (e.g. configured Rx options)railCfg.protocol
is set toNULL
. This is also a DMP specific field, but it's only used by the Silicon Labs Bluetooth stack.RAILCb_Generic
is nowstatic
: Since all protocols will have their own callback functions, we should either make the function names unique, or put them in different C files as static. Although technically it's possible to use a common callback function for all protocols, it's not recommended as the readability of the code is much better with separate event handlers.halInit()
is no longer called frominitRadio1()
- since we have multiple protocols to support, it makes more sense to set up RAIL requirements elsewhere in a protocol-independent init functionThis code snippet initializes a single protocol. However, all remaining protocols can be initialized in the same way, noting that
railHandle
,railSchedState
andRAILCb_Generic
should be different for all protocols (probably by placing in separate C files).From this point you can use RAIL almost the same way as in single protocol mode. Since almost all APIs have a
railHandle
argument, RAIL will know which protocol you want to access. However, you should be careful with:railHandle
argumentProtocol independent APIs
There are a few APIs that always work the same way, regardless of which protocol they were called from:
RAIL_SetTime()
/RAIL_GetTime()
- the timebase (which is also used for timestamping) is common for all protocols. It is not recommended to callRAIL_SetTime()
in a DMP application (and calling it in a single protocol application can still be dangerous)RAIL_ConfigMultiTimer()
- MultiTimer mode is a requirement in DMP, so it's enabled by default and cannot be disabledRAIL_Sleep()
/RAIL_Wake()
- Sleep management should be handled protocol-independently (e.g. in the idle task of the RTOS)There are some other APIs that don't require
railHandle
, but they usually need a packet handler or timer handler, and so are still tied to a protocol.Scheduling a finite radio task
The following APIs will trigger the radio scheduler to schedule a finite length new radio task:
RAIL_ScheduleRx()
RAIL_StartTx()
RAIL_StartScheduledTx()
RAIL_StartCcaCsmaTx()
RAIL_StartCcaLbtTx()
RAIL_StartAverageRssi()
(Note that
RAIL_StartRx()
andRAIL_StartTxStream()
is not listed, as these as these are not finite length tasks.)All the above APIs have the optional argument
RAIL_SchedulerInfo_t
. In single protocol mode, this argument is ignored, and can be NULL. In multiprotocol mode, this is the base of the scheduling, with its 3 member:priority
- 0 to 255, 0 being the highest. Equal priority does not preemptslipTime
- maximum time the task can be delayed due to other protocols, in ustransactionTime
- the time the task takes, in usWhen the radio hardware is available, none of these scheduler info arguments are used. However, if two tasks are scheduled at the same time, the radio scheduler will try to stagger the operations using
slipTime
in order to satisfy both tasks. If that fails, the lowerpriority
task will not get access to the radio.It is also possible that an application schedules a high priority radio task when a conflicting lower priority task has already started. In that case, the lower priority task will be aborted (possibly in the middle of frame transmission).
If
RAIL_SchedulerInfo_t
isNULL
, the scheduler will assume all parameters to be 0, i.e. highest priority, with no slipTime - it is highly recommended to not use this feature, and it's only mentioned here to simplify debugging.Each protocol can schedule at most a single finite task.
For more details on the radio scheduler, see UG305 chapter 3 and the Multiprotocol description in the RAIL API doc.
Yielding
After each radio task the application must yield the radio. That can be achieved by calling
RAIL_YieldRadio()
orRAIL_Idle()
- the latter also turns off the radio, which should have already occurred for finite tasks, so it's recommended to useRAIL_YieldRadio()
. Manual yielding is required because the scheduler doesn't know about any automatic follow-up tasks (like ACKs) - you might want to yield immediately after receiving a packet, or you may instead want to wait for the auto ACK to go out.Since yielding the radio will also call the radio scheduler to start/schedule the next radio task, you must first capture everything you need that might be overwritten by another protocol after yield, even in interrupt context. For example, timestamps of a transmitted packet must be accessed before yielding.
A Tx operation scheduled in the future can be cancelled using
RAIL_Idle()
orRAIL_StopTx()
, where the latter doesn't yield the radio. An Rx operation scheduled in the future can only be cancelled usingRAIL_Idle()
.Background Rx
RAIL_StartRx()
receives special handling in the radio scheduler, as it schedules an infinite task - namely, background Rx. The main difference when compared to finite tasks is that an infinite task sets the "default" state of the radio. It can be aborted by higher priority finite tasks, but it will be resumed automatically after the higher priority task is finished. For background Rx, this practically means that the radio will automatically return to Rx after Tx or packet reception, and you don't have to use auto state transitions like you do in single protocol mode. (Currently, the only infinite task supported by RAIL is background Rx).Keep in mind that RAIL does not store the channel of background Rx: if you interrupt background Rx with a finite task on a different channel, the channel of the background Rx will change as well. E.g. if you're receiving in channel 0, then transmit on channel 1, the radio will "return" to receiving on channel 1. To change the background Rx channel back to 0, simply call
RAIL_StartRx()
again after you yield the radio.RAIL_StartRx()
has the sameRAIL_SchedulerInfo_t
configuration as the finite tasks, but depends only on thepriority
member - which, in general, should be the lowest priority used by the protocol.Background Rx can be aborted using
RAIL_Idle()
, which also yields the radio. In DMP mode, it's recommended to only useRAIL_Idle()
to stop background Rx.It is also possible to change the priority of the background RX using
RAIL_SetTaskPriority()
- this is useful e.g. to increase the priority during packet reception, or after a particular packet.TxStream
RAIL_StartTxStream()
is called a debug task: A debug task must have the highest priority, because it can't be aborted. Otherwise, it works similarly to infinite tasks.This API is different from all others as it doesn't use
RAIL_SchedulerInfo_t
. Since a stream can't really be aborted, the radio scheduler will handle this with the highest priority and no slip time.Stream can be stopped with
RAIL_StopTxStream()
,RAIL_Idle()
orRAIL_YieldRadio()
, although it is recommended to useRAIL_StopTxStream()
for clarity.Auto state transitions
You might wonder, "what's the point of auto state transitions in DMP, if background Rx changes the default state to Rx?" The added value of auto state transitions is that the resulting state will inherit the original state's priority.
For example, you probably want the ACK reception on the same priority as the transmission itself, so you can set
RAIL_SetTxTransition()
to set up receive after the transmission for the ACK, and configure a timeout for it usingRAIL_SetStateTiming()
.If you have automatic state transition set up, you should only yield the radio after the whole operation (e.g. ACK reception or timeout) is finished. It's highly recommended to have automatic timeouts to prevent a high priority infinite task, but if you haven't set up timeouts, you can cancel the receive and yield the radio using
RAIL_Idle()
.To summarize:
For more details, see UG305 chapter 6
MultiTimers
RAIL includes a timer virtualization service, called MultiTimer. Since its memory footprint is not negligible, this feature is disabled by default in single protocol mode. However, in multiprotocol mode, it is always enabled, since multiple timers are required for the multiple protocols. Therefore, using MultiTimers in your protocol implementations has no drawback in DMP.
Error handling
In single protocol RAIL, you generally need to places to handle errors:
RAIL_StartXYZ()
/RAIL_ScheduleXYZ()
might return an error. If no error were returned, the operation was either finished, or an event will be triggeredRAIL_EVENT_TX_PACKET_SENT
orRAIL_EVENT_TX_CHANNEL_BUSY
In DMP, there's a third error case to handle above the previous two: You should always handle
RAIL_EVENT_SCHEDULER_STATUS
, where you might receive an error by callingRAIL_GetSchedulerStatus()
. It's usually obvious which API call failed, e.g.RAIL_SCHEDULER_STATUS_SINGLE_TX_FAIL
means aRAIL_StartTx()
failed, but it might not be possible to know the original call resulted in error.This was implemented because when you call e.g.
RAIL_StartTx
, the radio scheduler just creates task. When that task is running, it will actually call the single protocolRAIL_StartTx
, and if that returns an error, it will trigger anRAIL_EVENT_SCHEDULER_STATUS
event.The event
RAIL_EVENT_SCHEDULER_STATUS
is also used when a higher priority task interrupts an ongoing radio operation, in which case,RAIL_SCHEDULER_STATUS_EVENT_INTERRUPTED
will be returned byRAIL_GetSchedulerStatus()
.Debugging
When debugging DMP, Rx/Tx PRS channels are still very useful, see the tutorial about debugging for details.
For DMP based code, writing out to a GPIO when a protocol is scheduled/unscheduled is really useful. This can be easily done by setting a GPIO on
RAIL_EVENT_CONFIG_SCHEDULED
and clearing it onRAIL_EVENT_CONFIG_UNSCHEDULED
.Recommendations and practices
Although these practices are very important for DMP applications, you should consider applying them in single protocol applications as well, to simplify a potential future port to DMP:
RAIL_Idle()
when it's necessary - In RAIL 1.x almost all APIs required that they be called from idle mode. This requirement was removed in RAIL 2.x, so it's very rarely needed, and sinceRAIL_Idle()
also yields the radio, it should be very rarely used in DMP.How not to use DMP
Using multiple DMP instances for multiple radio tasks (e.g. one protocol for advertising, one for connection, or one for Tx, one for Rx) is bad design: while it works, each protocol instance has a significant memory footprint, and switching time between protocols is much slower than switching Rx/Tx inside a protocol. At the moment, it is recommended to use DMP only for serving separate protocol stacks.
Using DMP to handle multiple PHYs for the same protocol stack is also not recommended: Multi-PHY is a much better solution for that, see the introduction to Multi-PHY and Multiprotocol tutorial for more details.
Related documentation
If you find any conflicts between this article and the above documents, give those documents priority as they will more quickly receive updates to track new RAIL DMP features and guidance.
EZRadioPro Sensitivity vs. Gaussian Tx filter BT factor (Bandwidth Time product)
The BT factor on the Gaussian pulse shaping filter is often used for narrowing spectral emissions at the Tx side to fit into a regulatory / applications standard mask requirement. On how to adjust the BT factor on the EZRadioPro familiy you can read here.
Note that adjusting the BT factor will only alter the spectrum emission of the modulated signal, it will not affect discrete spurious emissions that are not part of the modulation, like these.
Adjusting the BT factor, however does have an effect on sensitivity at the Rx side. This is quite often overlooked and may result in a huge loss in the link budget. To that end find below two graphs showing sensitivity versus BT factor parameterized with modulation index. (These measurements were taken on revision revB1B but hold up for revC2A/ A2A.)
Key takeaway:
Don’t ever go to a lower BT factor than 0.25 if the modulation index <= 2. If narrower emissions are required lower the modulation index instead.
Also be prepared that the frequency offset tolerance of the Rx degrades by dropping BT and / or the modulation index.
RAIL Tutorial Series
Introduction
The goal of this tutorial series is to help you get started with RAIL, the C API for embedded software for Silicon Labs' EFR32 Wireless Geckos.
To read it, you should have some experience with embedded C - you should know how to handle interrupts, what volatile means, etc.
While we try to avoid using it, you might need some experience with radios as well. It definitely helps if you know what preamble or sync word is.
The tutorials should be read together with the RAIL API documentation.
If you find any contradiction between the tutorials and the API documentation, just always remember that the API documentation is more accurate.
This tutorial series focuses on the embedded code running on Wireless Geckos. Configuring the radio correctly is equally important, see AN971 for details on that for Studio v4 and AN1253 for Studio v5.
The tutorial series is currently under review and update for the Studio v5 updates. Although RAIL itself haven't changed much, some critical tools did. For details, see AN1254. The tutorials are mostly accurate for both v4 and v5. On those articles where an update will be needed in the near future, a note is added to the first couple of paragraphs.
Getting Started
You should start with these tutorials. All others are based on these, as these are the minimum to develop useful applications on RAIL.
Studio v5 series
Studio v4 series
Basic Tutorials
These tutorials are common and basic features, you should read them before you start working on a project which uses RAIL.
Advanced Tutorials
These tutorials describe features that are rarely needed - they are available and can be used when necessary, but you probably won't use them all in your application. Generally, you should read them only if you think you need them.
Multi-PHY and Multi protocol Tutorials
These tutorials are about the Multi-PHY and multiprotocol capabilites of RAIL. If you plan to use that feature, we recommend to read the introduction then what you need, depending on your application.
RAIL Tutorial: Multi-PHY usecases and examples
In this article, we go through a couple of example usecases and their setups using the Multi-PHY 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 related tutorials on the table of contents site.
This tutorial includes videos (without audio) to guide you through the setup on the Configurator GUI.
Note that the videos on this tutorial were recorded on Studio v4, and Studio v5 follows a slightly different configurator workflow. Until the videos are updated, you can find details of the new workflow in AN1253.
Channel-based Multi-PHY
The theory
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.
Channel groups can be created with the
icon. By default, these new channel groups have no PHY configuration.
A PHY configuration can be added using the
icon. A PHY configuration exposes elements of the radio configurator for each of the channel groups in the selected protocol. The PHY applied to any specific channel group can then be customized by changing the values for each element present in the PHY configuration.
Once you add something to the channel-based configs (as described above), the protocol-based setup will be disabled.
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 which originally only supported the lower bitrate: The default bitrate is 100k, but it's possible to switch to 500k 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
RAIL_StartRx(railHandle, 0, NULL)
it will search for 100k packets, whileRAIL_StartRx(railHandle, 1, NULL)
will search for 500k 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 are the length decoding algorithm itself. This is because the configurator can keep a complete fixed length and a variable length configuration, and changing between those is possible by just changing the the frame length algorithm.
Asymmetric protocol (Wireless M-Bus T mode)
Wireless M-Bus T mode defines a very different protocol based on the direction:
The above example implements the "Other device" (typically collector): It receives M2O on channel 0 and transmits O2M on channel 1.
Transmitting modeT M2O messages requires other tricks, 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 usecase: Since CSMA/CA will listen on the same channel that you will use for transmit 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 the 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 bandwidth configured for channel 0 in the
efr32_radio_configurator_log.txt
file: It's 4.8kHz.Uneven channel spacing
Channel groups can be used in "single-PHY" mode as well: one example for 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.
Power limited channels
This is still a "single-PHY" usecase: Let's say you have to receive on 40 channels, starting at 2.402GHz with channel spacing of 1MHz. However, to meet regulatory standards, you must lower the transmit power on the first and last channel to 16dBm.
The above setup meets the requirements. Note that channel 0 and 39 are both defined twice. First as limited power, then the same as the other 38 channels. The order of channel groups are important: RAIL always loads the first channel group which includes the requested channel.
Channel number offset was used in the above video. The frequency of a given channel is calculated as follows:
Channel number offset is the same as First channel number if it's disabled. This is useful for uneven channel spacing, since you want the channel group to start on the selected base frequency, and not offset by
firstChannelNumber*channelSpacing
. However, setting it to 0 is useful for overrides: This way, the power limited channel 39 in the above config will be exactly the same as in the original channel group. See understanding RAIL config files tutorial for more details.You also have to keep in mind that the actual output power depends on your board and antenna design. The recommended way to solve this problem is to create a PA conversion curve documented in AN1127, and enable the PA conversions plugin with the generated header:
Simplicity Studio ships with conversion curve headers for Silicon Labs boards - if you create a project for a Silicon Labs board, simply enable the PA Conversions plugin to include the correct header for your board.
Note that RAIL only lowers and limits the output power on channel change, it won't increase it back if you switch to an unlimited channel from a limited one.
How Channel-based Multi-PHY actually works
See the Multi-PHY capabilities chapter in the Understanding RAIL config files tutorial.
Tips and Tricks for Channel-based Multi-PHY
Advanced fields
In general, you should disable all advanced settings if you change much in a configuration - the same guidance applies for Multi-PHY. What makes this advice even more important for Multi-PHY is that it's easy to miss a problem if something misconfigured under the protocol setup makes your channel group misbehave.
Inheritance from protocol
When you add an element to the channel-based configuration, it will initially inherit a value from the root protocol - and that is the only time this inheritance happens. If you later change the root protocol (ex: switch to a pre-configured PHY), doing so won't update the channel-based config properties for any channel groups, even if you hadn't changed the value inherited from the original protocol. This behavior is depicted in the following video:
Channel group change performance
If you have significant differences between channel groups, channel changing that involves a channel group change will take somewhat longer - in most cases, this is not too significant, but you should measure and calculate with it if you need short channel switch times (PRS channels can be used for measuring this time, see the tutorial about debugging).
Also, if you have 3 channel groups, and only a single field is different between A and B, but C is very different compared to the other two, channel change between A and B will take the same time as channel change between C and A or C and B.
If you have a lot of channel groups in the protocol, this could also cause a small but measurable delay during channel change: it takes some time for RAIL to look up the channel group which includes the requested channel.
All of these delays are a result of the implementation in RAIL, see the Understanding RAIL config files tutorial for more details.
In some cases, it's possible to work around these problems by combining channel-based and protocol-based Multi-PHYs:
Protocol-based Multi-PHY
The theory
The main idea behind Protocol-based Multi-PHY is to create multiple - completely independent - configs, and use
RAIL_ConfigChannels()
to select the required one.Protocols can be created with the
icon.
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.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
RAIL_ConfigChannels(railHandle, channelConfigs[0], NULL)
will load the European config, whileRAIL_ConfigChannels(railHandle, channelConfigs[1], NULL)
will load the North American config.RAILTest support
Protocol-based Multi-PHY is also useful if you want to test multiple configuration setups using RAILTest: You can change between protocols using the
setConfigIndex
RAILTest command.Configuration for a DMP setup
The above setup can be also 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 callRAIL_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 high power 2.4GHz config).
To solve this, you should pass a function pointer as the last parameter of
RAIL_ConfigChannels
. In the examples, we call itRAILCb_RadioConfigChanged
and it will be called when RAIL switches between channel groups, with theRAIL_ChannelConfigEntry_t
as an argument. You can use that argument to figure out if you need to change the PA or not, and callRAIL_ConfigTxPower
if needed.This is implemented in both RAILTest and in SimpleTRX-MultiPHY.
IR Calibration in Multi-PHY configs
If you're not sure what IR calibration is, please refer to the Calibration tutorial.
Configuration and cache variables in rail_config.c
This section is copied from the Understanding RAIL Config Files tutorial.
The radio configurator generates two elements - an array and a struct - that are involved in IR calibration, they look like this:
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 runRAIL_CalibrateIr()
,RAIL_ApplyIrCalibration()
orRAIL_Calibrate
, it will store the IR calibration result here. If you load a channel with the special entryAttr value ofRAIL_CAL_INVALID_VALUE
(API doc), it will trigger the eventRAIL_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
andentryAttr
will be the same. Calibration should be performed on each channel group that has its ownirCalConfig
andentryAttr
. RAIL will detect this and request calibration throughRAIL_EVENT_CAL_NEEDED
if you select a channel group that has a different IR calibration setup andRAIL_CAL_INVALID_VALUE
inentryAttr
. 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 shouldirCalConfig
andentryAttr
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:In the above example we have 5 channel groups that require calibration (
NUMBER_OF_GROUPS
). The first channel of each channel group is stored incalibrationChannels
.If you have more protocols, you can simply load them using
RAIL_ConfigChannels
. Note that the first channel of the protocol is automatically configured, soRAIL_PrepareChannel()
is only needed for Channel-Based Multi-PHY.Multi-PHY in Connect
The Connect stack has some limitations on Multi-PHY features:
Related documentation
RAIL tutorial: Introduction to Multi-PHY and Multiprotocol
This tutorial builds on the following tutorials:
To get the most out of this tutorial, some familiarity with the radio configurator is required, and it's also recommended that you understand the basics of radio protocol stacks (i.e. the OSI model).
You can find these prerequisite tutorials on the table of contents site.
Introduction
With a customizable radio, the Wireless Gecko is a great option to support any radio configuration you need. You can set it up for standard IEEE 802.15.4, Bluetooth Low Energy, WMBus, or even the protocol (designed 30 years ago) used by your garage door opener. However, what if you need more than one of those standards? With single protocol radios, this requires a distinct 15.4 radio chip, a Bluetooth chip, and so on for each supported standard. Alternatively, with a single Wireless Gecko, you can store multiple "setups" in your device and switch between them. The only limitation is that it's still a single radio - you can't use multiple "setups" on the radio at the same time.
The best way to manage the sharing of this single radio resource depends on the particular usecase targeted by your application. We provide different solutions (discussed below) to help you identify an optimal approach for your design.
Channel-based Multi-PHY
The most common usecase in proprietary wireless is the need to support different PHY configurations. For example:
In these cases, Channel-based Multi-PHY is the best solution. It's called Channel-based, because in RAIL, you only need to change the channel to change the PHY.
You can find a setup guide for some of these usecases in the Multi-PHY usecases and examples tutorial.
Protocol-based Multi-PHY
This is not as widely used as Channel-based Multi-PHY, but it's the next logical step. The typical usecases:
For this, we recommend Protocol-based Multi-PHY. In this case, you can switch between protocols by calling
RAIL_ConfigChannels()
, which will load the whole configuration.Protocol based Multi-PHY is also useful if you want to test multiple configuration setups using RAILTest: You can change between protocols using the
setConfigIndex
RAILTest command.You can find a setup guide for some of these usecases in the Multi-PHY usecases and examples tutorial.
Channel-based vs Protocol-based Multi-PHY
In general, you should consider your application requirements: If you want to have (for example) 20 channels, but those 20 channels are defined differently in some application configurations (typically by region), you should use Protocol-based Multi-PHY. A protocol change will be slower than a channel change, but the application doesn't even need to know which protocol is in use, since it still sees the same 20 channels through RAIL.
Otherwise, in almost all cases, Channel-based Multi-PHY is a better choice.
Dynamic Multiprotocol (DMP) between RAIL-based protocols
If you have full protocol stacks that are completely independent, it makes sense to develop the PHY and Link layer independently as well. These layers communicate with RAIL, and the cleanest solution to this problem is Dynamic Multiprotocol. Typical usecase:
In Dynamic Multiprotocol, the RAIL scheduler decides which protocol is active.
Dynamic Multiprotocol is more capable than Multi-PHY, but also more complex, so it has some drawbacks when compared to Multi-PHY:
Writing RAIL application in DMP will be the subject of this tutorial.
Dynamic Multiprotocol between RAIL-based and other Silicon Labs stack protocols
The usecase for this is straight forward:
At the moment, Silicon Labs only provides such a DMP solution based on the Bluetooth stack and RAIL. You can access it by installing the Bluetooth SDK and starting one of the DMP applications. See AN1134 for more details, and an upcoming tutorial detailing DMP usage from a RAIL perspective.
Other multiprotocol solutions
You can find other types of multiprotocol mentioned in some Silicon Labs documents, but they are rarely used:
Programmable multiprotocol means multiple firmware images for each protocol, and the protocol is decided during the manufacturing process, i.e. a device is either programmed to be a Bluetooth or a Zigbee device.
Switched multiprotocol means two firmware images, one for each protocol. A bootloader loads the firmware needed for the protocol, and the two firmware can communicate with each other in a shared NVM storage. Protocol switching in switched multiprotocol is rather slow, and using DMP is almost always a better choice.
Concurrent multiprotocol means that the PHY and maybe the lower layers of the stacks are the same, but the upper layers are different. E.g. You can run two proprietary 15.4 protocol on the same channel at the same time, but you don't really need multiprotocol specific APIs to support it.
Related documentation