(*) 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.
Driving FEMs usually require some logic signals, which can be driven from software, but generally that's not fast enough (e.g. for CSMA/CA): It needs some hardware based signals.
Since EFR32 is an integrated radio MCU, this is not handled in the radio module, but a more general purpose module: Peripheral Reflex System, or PRS for short.
EFR32s has 12 PRS channels, each of these channels can be driven by numerous signals from various sources, and even some logic operations can be done between them. These channels can be connected to PRS consumers, so it can trigger a timer start for example.
You can also wire PRS channels to some GPIOs, and this is the feature we'll use. For more information of PRS, see the reference manual.
Hardware configurator does provide an "External LNA" module, but it's only supported by the mesh stacks. Also, it only supports one TX/RX and one sleep pin, which is probably not enough for more complicated FEMs.
This article is stack independent, although some stacks might provide an integrated way, like the External LNA module.
The prs signals
Generally a FEM requires the following:
rx/tx switch
sleep
rx/tx bypass
The bypass signals are usually either compile time or application time settings, so they can be driven like any other GPIO from software, or exactly the same as sleep and rx/tx, therefore it's not part of this article.
The following PRS signals are useful for driving a FEM:
RAC_ACTIVE active high when radio is on (either RX or TX)
RAC_LNAEN active high when LNA is needed
RAC_PAEN active high when PA is needed
Note that all signals can be inverted
RAC_ACTIVE can be used to drive the sleep pin, but using (RAC_LNAEN or RAC_PAEN) is more energy efficient.
GPIOs usable for PRS
See the Alternate Functionality Pinout chapter or device pinout table in the datasheet. PRS channel pins has PRS_CHx functionality on it.
The hardware configurator can be also used as a dynamic pinout diagram in Simplicity Studio (in all Flex SDK examples)
Keep in mind that you need independent PRS channels for each signal, you can't configure a PRS channel to two locations.
Also, logical operations limited to channels next to each other:
OR operation is only supported between a channel and a previous one (i.e. channel1 = channel0 OR channel1).
AND operation is only supported between a channel and the next one (i.e. channel0 = channel0 AND channel1).
This limits the usable pins, e.g. it's recommended to drive a sleep pin as (RAC_LNAEN or RAC_PAEN), which means whatever channel you plan to use for sleep, the previous one MUST be set up to either RAC_LNAEN or RAC_PAEN. However, you can set up the same signal to multiple channels, and you don't have to drive a gpio with a channel.
Configuring GPIOs in RAILTest
Generally, RAILTest is recommended to test the radio's setup and performance, so energy efficiency is not a goal here. Therefor, it would be enough to just drive pins low and high, although it's a bit simpler to use the tx/rx switch from PRS.
RAILTest only supports a few GPIOs:
PC10, PC11, PF2, PF3, PF4, PF5, PC9, PD9, PD10, PD11, PD12
A number of PRS signals are supported, unfortunately drive low/high is not amongst them.
The following commands can be used
to drive pin high (when radio is on, using RACACTIVE):
setupDebugSignal RACACTIVE
to drive pin low (CUSTOM_LIB is used internally for RAIL debugging, but on the public versions of the SDK it just drives the pin low):
setDebugSignal CUSTOM_LIB 0
to drive rx/tx pin, high on rx:
setDebugSignal LNAEN
to drive rx/tx pin, high on tx:
setDebugSignal PAEN
to turn off a pin
setDebugSignal OFF
E.g. to drive the LNA on the MGM12P module (sleep pin on PD10, tx/rx on PD11)
A. What are the important parameters of a whip antenna used for evaluating the antenna?
B. What creating antennas from wire, what is length and size of the wire to be used?
Answer
A. The most important parameter is the impedance (which should be 50Ohm at the operation frequency) and the VSWR (lower is better). Antenna gain is also important.
Datasheets of whip antennas usually state 0-2dBi gain but these values are usually measured with the antenna mounted on a large perpendicular ground plane. The optimal ground plane size for whip antennas is half-wavelength or higher. Below quarter-wavelength ground plane size significant performance degradation can be expected.
In general, whip monopole antennas are quarter-wavelength antennas so a straight wire antenna for example for 434MHz should be approx 17cm long. Of course if some coating material is used around the wire (with larger dielectric constant) and/or helical wire is used then the physical length of the antenna is shorter. From gain point of view it is better if the physical size of the antenna is larger.
B. The length of the antenna should be measured from the point where it leaves close proximity to the ground, or from the input/output. If a whip is mounted on a box, and connected with simple wire, then that wire becomes part of the antenna.
In the case of the whip, there must be a connection to a ground, even if the ground plane area is nothing more than circuit traces and a battery. The whip and ground plane combine to form a complete circuit. The electromagnetic field is set up between the whip and the ground plane, with current flowing through the field, which completes the circuit. Ideally, a ground plane should spread out at least a quarter wavelength, or more, around the base of the whip, ground planes can be made smaller, but this will affect the performance. The ground plane area must be considered when designing an antenna.
The length is inversely proportional to the frequency and may be calculated by: wavelength in cm = 30,000 / frequency in MHz. Thus, for a quarter wavelength:
l (cm) = ¼ ( 30,000 / freq (MHz) )
The formula should only be considered as a course measurement however since the length may actually be shorter if the whip is overly thick or wide, has any kind of coating, or is not fed close to the ground. It may also need to be longer if the ground plane is too small.
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.
Driving FEMs (and PAs and LNAs) on EFR32
Driving FEMs usually require some logic signals, which can be driven from software, but generally that's not fast enough (e.g. for CSMA/CA): It needs some hardware based signals.
Since EFR32 is an integrated radio MCU, this is not handled in the radio module, but a more general purpose module: Peripheral Reflex System, or PRS for short.
EFR32s has 12 PRS channels, each of these channels can be driven by numerous signals from various sources, and even some logic operations can be done between them. These channels can be connected to PRS consumers, so it can trigger a timer start for example.
You can also wire PRS channels to some GPIOs, and this is the feature we'll use. For more information of PRS, see the reference manual.
Hardware configurator does provide an "External LNA" module, but it's only supported by the mesh stacks. Also, it only supports one TX/RX and one sleep pin, which is probably not enough for more complicated FEMs.
This article is stack independent, although some stacks might provide an integrated way, like the External LNA module.
The prs signals
Generally a FEM requires the following:
The bypass signals are usually either compile time or application time settings, so they can be driven like any other GPIO from software, or exactly the same as sleep and rx/tx, therefore it's not part of this article.
The following PRS signals are useful for driving a FEM:
Note that all signals can be inverted
RAC_ACTIVE can be used to drive the sleep pin, but using (RAC_LNAEN or RAC_PAEN) is more energy efficient.
GPIOs usable for PRS
See the Alternate Functionality Pinout chapter or device pinout table in the datasheet. PRS channel pins has PRS_CHx functionality on it.
The hardware configurator can be also used as a dynamic pinout diagram in Simplicity Studio (in all Flex SDK examples)
Keep in mind that you need independent PRS channels for each signal, you can't configure a PRS channel to two locations.
Also, logical operations limited to channels next to each other:
This limits the usable pins, e.g. it's recommended to drive a sleep pin as (RAC_LNAEN or RAC_PAEN), which means whatever channel you plan to use for sleep, the previous one MUST be set up to either RAC_LNAEN or RAC_PAEN. However, you can set up the same signal to multiple channels, and you don't have to drive a gpio with a channel.
Configuring GPIOs in RAILTest
Generally, RAILTest is recommended to test the radio's setup and performance, so energy efficiency is not a goal here. Therefor, it would be enough to just drive pins low and high, although it's a bit simpler to use the tx/rx switch from PRS.
RAILTest only supports a few GPIOs:
PC10, PC11, PF2, PF3, PF4, PF5, PC9, PD9, PD10, PD11, PD12
A number of PRS signals are supported, unfortunately drive low/high is not amongst them.
The following commands can be used
E.g. to drive the LNA on the MGM12P module (sleep pin on PD10, tx/rx on PD11)
Configuring GPIOs in application source
To use PRS channels, you first need to enable GPIO and PRS clock:
Set the GPIO we use to pushpull mode:
Enable PRS signal:
It's recommended to use async mode, since this is needed to have a working signal even in sleep states.
Set location of the PRS signal pin:
(the 12 PRS channels are divided into 3 ROUTELOC registers, so channel 0-3 is in ROUTELOC0, etc)
Enable the pin:
E.g. to drive the LNA on the MGM12P module (sleep pin on PD10, tx/rx on PD11):
Invert a signal
Inverting a signal is very simple. To create an inverted sleep signal for example (high in sleep):
Logical operation between signals
As above mentioned:
If you meet this limitation, the setup is pretty simple, just use PRS_CH_CTRL_ORPREV and PRS_CH_CTRL_ANDNEXT.
For example the energy efficient (RAC_LNAEN | RAC_PAEN) way to drive the LNA on MGM12P:
Whip antenna parameters
Questions
A. What are the important parameters of a whip antenna used for evaluating the antenna?
B. What creating antennas from wire, what is length and size of the wire to be used?
Answer
A. The most important parameter is the impedance (which should be 50Ohm at the operation frequency) and the VSWR (lower is better). Antenna gain is also important.
Datasheets of whip antennas usually state 0-2dBi gain but these values are usually measured with the antenna mounted on a large perpendicular ground plane. The optimal ground plane size for whip antennas is half-wavelength or higher. Below quarter-wavelength ground plane size significant performance degradation can be expected.
In general, whip monopole antennas are quarter-wavelength antennas so a straight wire antenna for example for 434MHz should be approx 17cm long. Of course if some coating material is used around the wire (with larger dielectric constant) and/or helical wire is used then the physical length of the antenna is shorter. From gain point of view it is better if the physical size of the antenna is larger.
B. The length of the antenna should be measured from the point where it leaves close proximity to the ground, or from the input/output. If a whip is mounted on a box, and connected with simple wire, then that wire becomes part of the antenna.
In the case of the whip, there must be a connection to a ground, even if the ground plane area is nothing more than circuit traces and a battery. The whip and ground plane combine to form a complete circuit. The electromagnetic field is set up between the whip and the ground plane, with current flowing through the field, which completes the circuit. Ideally, a ground plane should spread out at least a quarter wavelength, or more, around the base of the whip, ground planes can be made smaller, but this will affect the performance. The ground plane area must be considered when designing an antenna.
The length is inversely proportional to the frequency and may be calculated by: wavelength in cm = 30,000 / frequency in MHz. Thus, for a quarter wavelength:
l (cm) = ¼ ( 30,000 / freq (MHz) )
The formula should only be considered as a course measurement however since the length may actually be shorter if the whip is overly thick or wide, has any kind of coating, or is not fed close to the ground. It may also need to be longer if the ground plane is too small.