Dynamic Configurator is a Simplicity Studio feature that allows experimentation with radio configurations on EFR32 devices.
What is the typical use case?
Typically dynamic configuration is used to easily reconfigure the EFR32 of a Silabs development board or a custom radio board
to evaluate the radio of the EFR32 (the other node of the link is a generator or spectrum analyzer)
to find compatible configuration (the other node of the link is a fixed configuration/third party radio ).
How does it work?
There are CLI (Command Line Interface) commands implemented in a plugin of the RAILTest sample application, called Ram Modem Reconfiguration Commands.
Using these commands Simplicity Studio generates scripts of the user configured radio settings and applies them over a virtual COM port to the target EFR32 to dynamically change the radio modem configuration, without the need for recompiling the RAILTest application and reflashing the target.
The virtual COM port consists of a physical UART between the target device and the board controller of the WSTK (Wireless Starter Kit) Mainboard, and a logical function in the board controller that makes the serial port available to the host PC, over USB or Ethernet.
How to use it?
Set up your development environment and create a new or open an existing RAILTest sample application for your connected Silabs or custom HW, according to the description in https://www.silabs.com/documents/public/quick-start-guides/qsg138-flex-efr32.pdf. You will end up in the Simplicity IDE perspective of Simplicity Studio with a RAILTest project open.
Open the isc file and in the configurator window set the initial configuration according to your needs. Make sure that the "Railtest Ram Modem Reconfiguration" plugin is enabled. Generate and compile the application.
Upload the application to the target without starting a debug session.
Open a terminal window for the virtual COM port of the target by right clicking on the target adapter in the "Debug Adapters window" and selecting "Launch Console…"
In the console window select the "Serial 1" tab and by pressing Enter in the text box check if the connection with the RAILTest is alive.
When you need to modify the configuration, do it in the configurator window. Note that the dynamic configurator can support only the first Channel Group of the first Protocol configuration.
In the configurator window click on the Dynamic Radio Configuration button of the isc editor, open the "Adapters" tab and select your target adapter.
Then go to the "Create configuration" tab. It will create and display the dynamic configuration script as soon as you click on the "Generate dynamic Railtest Configuration" button.
To send the script to the target, go to the "Apply Configuration" tab and click the "Apply Configuration" button. The commands of the script and the responses will appear in the previously opened console window. In case of success the radio will start to behave according to the modified configuration.
Note that the dynamic config modifies only radio registers, so getConfigIndex, printDataRates or similar CLI commands that get values from other sources may return incorrect values. The overwritten configuration can be reverted by a reset or setConfigIndex CLI command.
Note also that the RAM Modem Reconfiguration CLI Commands (writeRmrStructure, updateConfigurationPointer and reconfigureModem) are intended to be used only by Simplicity Studio and not directly from the CLI.)
The function RAIL_Idle() takes a mode argument, with which you select one of the four available methods of idling the radio to use. Though the API documentation briefly describes each mode, it might be difficult to understand their differences without seeing examples and usecases. This document aims to help you more clearly understand each mode.
General recommendations
In most cases, RAIL_IDLE is the recommended mode to use with this API. RAIL_IDLE_ABORT is helpful when you want to also abort an ongoing ("active") tx or rx operation.
On the other hand, RAIL_IDLE_FORCE_SHUTDOWN is not recommended for use, and RAIL_IDLE_FORCE_SHUTDOWN_CLEAR_FLAGS should be handled with care.
RAIL_IDLE
The mode RAIL_IDLE turns off the radio after the current operation is finished. It also cancels any transmit or receive scheduled in the future. Current operations that won't be aborted include:
active transmit, which starts when the first bit (usually preamble) is transmitted and ends when the last bit (usually CRC) is transmitted.
active receive, which starts at sync word detect, and ends when the last bit is received (which depends on the decoded length of the packet).
RAIL_IDLE_ABORT
The mode RAIL_IDLE_ABORT works the same as RAIL_IDLE, but it will also abort active operations. However, RAIL_IDLE_ABORT will always wait for a stable state before turning off the radio, e.g. if the radio was preparing to enter rx mode and is waiting for the PLL to lock, RAIL will wait until the rx state is reached before idling the radio.
RAIL_IDLE_FORCE_SHUTDOWN
Unlike RAIL_IDLE_ABORT (which waits for a stable radio state), RAIL_IDLE_FORCE_SHUTDOWN immediately forces the radio off. As this is an abrupt transition, it may corrupt data in the receive or transmit buffers. This buffer corruption can only be resolved by completely clearing the FIFO - which loses data, and can also consume additional time.
In our experience, it's almost always slower than RAIL_IDLE_ABORT, so the costs typically outweigh the benefit of using RAIL_IDLE_FORCE_SHUTDOWN (except when recommended by our support team for diagnostic purposes).
RAIL_IDLE_FORCE_SHUTDOWN_CLEAR_FLAGS
The mode RAIL_IDLE_FORCE_SHUTDOWN_CLEAR_FLAGS works the same as RAIL_IDLE_FORCE_SHUTDOWN, but it also clears any pending events. For more details on scenarios where this can be useful, see the article on RAIL interrupt safety.
In embedded software development, some of the most complicated debug challenges are caused by calling non-reentrant functions in interrupt context. Hence, it's in the developer's best interest to carefully design their application to avoid these scenarios.
To do so, however, requires sufficiently detailed knowledge of the interrupts and API functions - which are not accessible in a closed-source product like RAIL. This document aims to provide the information required to develop interrupt- and thread-safe applications in RAIL.
Thread Safety
In an application without a task scheduler, only an interrupt request can interrupt the main program. If you use a preemptive scheduler, like the scheduler available in most embedded OSes (including Micrium OS), higher priority tasks can interrupt lower priority tasks as well. Regardless, when looking at the RAIL APIs, the same concerns are present in either case:
Is it safe to interrupt this API?
Is it safe to call this API from a thread/interrupt which interrupted something?
The Event Handler
RAIL uses an event handler, which is set up by RAIL_Init(). In our examples, it's usually called RAILCb_Generic(). This function is called by the RAIL library, and it's almost always called from an interrupt handler. This means the event handler should be used with care:
It should be kept in mind that interrupts are disabled when the event handler is running, so the function must not take long to return
More importantly, the function might be interrupting the main loop (or some other task)
Note that the first point above might not be completely true if interrupt priorities are used, in which case only interrupts at the same and lower priorities are disabled. However, the event handler will never be interrupted by another event handler as all RAIL interrupts must be used at the same priority.
General Rules for the RAIL API
First, let's collect the general rules of the API, and we'll detail exceptions in later points:
Calling any RAIL API from the main thread (or a single OS thread) is safe.
Calling any API from multiple threads is unsafe, except for DMP.
Calling most APIs from an interrupt handler is safe (see exceptions below).
Dynamic Multiprotocol (DMP)
In general, if you have a multi-threaded application, you should use RAIL from a single thread. The exception to this guidance is DMP, where in most cases each protocol runs in its own thread. In this scenario, using RAIL from each thread is safe, as each protocol has its own railHandle. So, a more generalized wording of rule 2 is:
Calling any API from multiple threads is only safe if each thread has a dedicated railHandle, and each thread only accesses RAIL with its own handle.
The few APIs that don't use railHandle - like RAIL_GetTime(), RAIL_Sleep(), or RAIL_Wake() - can be called from any thread.
Interrupt Safety in general
In general, calling an API which changes the radio state (i.e. between rx, idle and tx) can be risky. The simplest way to write interrupt safe application is to not call state changing APIs from any interrupt handler, including the RAIL event handler. This can be achieved by setting a flag or changing a state variable in the event handler instead of calling an API directly:
typedef enum {
S_IDLE,
S_START_RX,
S_START_TX,
} state_t;
volatile state_t state;
volatile RAIL_Time_t lastEvent;
int main(){
//init code
state = S_START_TX;
while(1){
switch(state){
case S_START_TX:
RAIL_StartTx(railHandle, 0, RAIL_TX_OPTIONS_DEFAULT, NULL);
state = S_IDLE;
break;
case S_START_RX:
RAIL_StartRx(railHandle, 0, NULL);
state = S_IDLE;
break;
default:
break;
}
}
return 0;
}
void RAILCb_Generic(RAIL_Handle_t railHandle, RAIL_Events_t events)
{
lastEvent = RAIL_GetTime();
if ( events & RAIL_EVENTS_TX_COMPLETION ){
state = S_START_RX;
RAIL_SetTxPower(railHandle, 200);
}
}
Note that some RAIL API was called from the event handler, but none of those were state changing APIs.
Interrupt safety with state changing APIs
In some (usually time critical) cases however, it's not possible to avoid calling state changing APIs from the event handler (or other interrupt handler). State changing APIs are not always risky: Some APIs might be safe, as long as they don't interrupt another specific API.
Hence, in the following list, we identify the risky API after first specifying which initially-running (i.e., "interrupted") API makes it risky (and how). We've included in this list some interrupt combinations that might be "safe", but the end result is not predictable - i.e. the radio might be in rx or in idle, depending on which API is called first.
Interrupting RAIL_Start<something>() with another RAIL_Start<something>() is risky, especially if they would start on different channels.
Interrupting RAIL_Idle(handle, <something>, true) with any RAIL_Start<something>() is risky.
Interrupting RAIL_Idle(handle, <something>, false) with any RAIL_Start<something>() is safe, but the end result is not predictable (i.e. the radio will either be in Idle, or start the requested operation).
Interrupting RAIL_Start<something>() with RAIL_Idle() is safe but the end result is not predictable, and might cause strange events (see the next section for details).
Interrupting RAIL_StopTxStream() with any RAIL_Start<something>() is very risky (the radio might remain in test configuration and start transmitting/receiving).
Interrupting RAIL_StopTx() is safe. Interrupting RAIL_StopTx() with RAIL_Start<something>() is safe but the end result is not predictable (i.e. the radio will either be in Idle, or start the requested operation).
Interrupting anything with RAIL_StopTx() is safe (see next section for important clarification). Interrupting RAIL_StartTx() with RAIL_StopTx(handle, RAIL_STOP_MODE_ACTIVE) is safe, but not predictable.
Interrupting anything with RAIL_StopTxStream() is safe. Interrupting RAIL_StartTxStream() with RAIL_StopTxStream() is safe but not predictable.
RAIL_Idle in the Event Handler
Calling RAIL_Idle() or RAIL_StopTx(handle, RAIL_STOP_MODE_ACTIVE) from the event handler might cause strange results. For example, let's say you're receiving on a channel and want to detect preambles using the event RAIL_EVENT_RX_PREAMBLE_DETECT and RAIL_EVENT_RX_PREAMBLE_LOST. The following scenario may unfold:
Preamble lost interrupt is received, so (at least) other radio interrupts are temporarily disabled.
You enter the event handler with RAIL_EVENT_RX_PREAMBLE_LOST.
At this point, the radio detects a preamble. The interrupt is logged, but the handler cannot run since the interrupts are masked.
Still in the event handler, you decide to turn off the radio with RAIL_Idle(railHandle, RAIL_IDLE_ABORT, true).
The radio turning off will generate a preamble lost interrupt.
The radio is now off, and you return from the event handler.
Interrupts are enabled again, so the pending preamble detect interrupt handler starts running.
You enter the event handler with RAIL_EVENT_RX_PREAMBLE_DETECT and RAIL_EVENT_RX_PREAMBLE_LOST both set at the same time
So you end up with a preamble detect event, even though the radio is off. This is usually harmless, since you always have the _LOST or _ABORTED event as well - but this demonstrates why your design must carefully consider in what order to handle events.
The easiest way to avoid this conflicted outcome is to disable the events that might cause problems when turning off the radio.
Another way to avoid this issue is to use RAIL_Idle(handle, RAIL_IDLE_FORCE_SHUTDOWN_CLEAR_FLAGS, true), which will clear the pending interrupts. However, using RAIL_IDLE_FORCE_SHUTDOWN_CLEAR_FLAGS has other drawbacks. It does force the radio state machine to idle state, and it might corrupt the transmit or receive FIFOs - in which case it must clear them, losing all data that might already be in there. It could also take more time to finish running than RAIL_IDLE_ABORT.
Critical Blocks
One usual way to avoid internal safety issues is to create critical (a.k.a. atomic) blocks, in which interrupts are disabled, in the main thread to make sure some code segment is never interrupted. However, this can create other problems, so it should be used carefully. There's no general rule to avoid this kind of "collateral damage", but here's an example that should be avoided:
RSSI averaging is running, and just before it finishes, we interrupt it with RAIL_StartTx() which is called from a critical block. The following race condition could happen:
We enter the critical block, interrupts are disabled.
RSSI averaging done interrupt is received, but the interrupt handler won't start since interrupts are masked.
StartTx turns off the radio, prepares it for transmit, then starts transmitting.
We leave the critical block, interrupts are enabled again.
RSSI averaging done interrupt handler runs at this point which will turn off the radio, aborting the current transmit.
One way to avoid the problem above is to clear interrupts in the critical block. This can be done by using RAIL_Idle(handle, RAIL_IDLE_FORCE_SHUTDOWN_CLEAR_FLAGS, true) at the beginning of the critical block, but the drawbacks of doing so (mentioned above) should be kept in mind. In general, it's better to avoid risky interrupts without using critical blocks in the main thread.
Using FORCE_SHUTDOWN
In the two sections above we mentioned two usecases where RAIL_IDLE_FORCE_SHUTDOWN_CLEAR_FLAGS can be useful. In general however, RAIL_IDLE or RAIL_IDLE_ABORT is a sufficient and preferred way to stop transmitting/receiving - therefore the FORCE_SHUTDOWN modes should be only used when they are really needed (as in the specific scenarios described here). For more details, see the article on the idle modes.
Proprietary Knowledge Base
EFR32 Dynamic Configurator in Simplicity Studio
What is the Dynamic Configurator?
Dynamic Configurator is a Simplicity Studio feature that allows experimentation with radio configurations on EFR32 devices.
What is the typical use case?
Typically dynamic configuration is used to easily reconfigure the EFR32 of a Silabs development board or a custom radio board
How does it work?
There are CLI (Command Line Interface) commands implemented in a plugin of the RAILTest sample application, called Ram Modem Reconfiguration Commands.
Using these commands Simplicity Studio generates scripts of the user configured radio settings and applies them over a virtual COM port to the target EFR32 to dynamically change the radio modem configuration, without the need for recompiling the RAILTest application and reflashing the target.
The virtual COM port consists of a physical UART between the target device and the board controller of the WSTK (Wireless Starter Kit) Mainboard, and a logical function in the board controller that makes the serial port available to the host PC, over USB or Ethernet.
How to use it?
Note that the dynamic config modifies only radio registers, so getConfigIndex, printDataRates or similar CLI commands that get values from other sources may return incorrect values. The overwritten configuration can be reverted by a reset or setConfigIndex CLI command.
Note also that the RAM Modem Reconfiguration CLI Commands (writeRmrStructure, updateConfigurationPointer and reconfigureModem) are intended to be used only by Simplicity Studio and not directly from the CLI.)
RAIL Idle modes
The function
RAIL_Idle()
takes a mode argument, with which you select one of the four available methods of idling the radio to use. Though the API documentation briefly describes each mode, it might be difficult to understand their differences without seeing examples and usecases. This document aims to help you more clearly understand each mode.General recommendations
In most cases,
RAIL_IDLE
is the recommended mode to use with this API.RAIL_IDLE_ABORT
is helpful when you want to also abort an ongoing ("active") tx or rx operation.On the other hand,
RAIL_IDLE_FORCE_SHUTDOWN
is not recommended for use, andRAIL_IDLE_FORCE_SHUTDOWN_CLEAR_FLAGS
should be handled with care.RAIL_IDLE
The mode
RAIL_IDLE
turns off the radio after the current operation is finished. It also cancels any transmit or receive scheduled in the future. Current operations that won't be aborted include:RAIL_IDLE_ABORT
The mode
RAIL_IDLE_ABORT
works the same asRAIL_IDLE
, but it will also abort active operations. However,RAIL_IDLE_ABORT
will always wait for a stable state before turning off the radio, e.g. if the radio was preparing to enter rx mode and is waiting for the PLL to lock, RAIL will wait until the rx state is reached before idling the radio.RAIL_IDLE_FORCE_SHUTDOWN
Unlike
RAIL_IDLE_ABORT
(which waits for a stable radio state),RAIL_IDLE_FORCE_SHUTDOWN
immediately forces the radio off. As this is an abrupt transition, it may corrupt data in the receive or transmit buffers. This buffer corruption can only be resolved by completely clearing the FIFO - which loses data, and can also consume additional time.In our experience, it's almost always slower than
RAIL_IDLE_ABORT
, so the costs typically outweigh the benefit of usingRAIL_IDLE_FORCE_SHUTDOWN
(except when recommended by our support team for diagnostic purposes).RAIL_IDLE_FORCE_SHUTDOWN_CLEAR_FLAGS
The mode
RAIL_IDLE_FORCE_SHUTDOWN_CLEAR_FLAGS
works the same asRAIL_IDLE_FORCE_SHUTDOWN
, but it also clears any pending events. For more details on scenarios where this can be useful, see the article on RAIL interrupt safety.Interrupt and thread safety in RAIL
In embedded software development, some of the most complicated debug challenges are caused by calling non-reentrant functions in interrupt context. Hence, it's in the developer's best interest to carefully design their application to avoid these scenarios.
To do so, however, requires sufficiently detailed knowledge of the interrupts and API functions - which are not accessible in a closed-source product like RAIL. This document aims to provide the information required to develop interrupt- and thread-safe applications in RAIL.
Thread Safety
In an application without a task scheduler, only an interrupt request can interrupt the main program. If you use a preemptive scheduler, like the scheduler available in most embedded OSes (including Micrium OS), higher priority tasks can interrupt lower priority tasks as well. Regardless, when looking at the RAIL APIs, the same concerns are present in either case:
The Event Handler
RAIL uses an event handler, which is set up by
RAIL_Init()
. In our examples, it's usually calledRAILCb_Generic()
. This function is called by the RAIL library, and it's almost always called from an interrupt handler. This means the event handler should be used with care:Note that the first point above might not be completely true if interrupt priorities are used, in which case only interrupts at the same and lower priorities are disabled. However, the event handler will never be interrupted by another event handler as all RAIL interrupts must be used at the same priority.
General Rules for the RAIL API
First, let's collect the general rules of the API, and we'll detail exceptions in later points:
Dynamic Multiprotocol (DMP)
In general, if you have a multi-threaded application, you should use RAIL from a single thread. The exception to this guidance is DMP, where in most cases each protocol runs in its own thread. In this scenario, using RAIL from each thread is safe, as each protocol has its own
railHandle
. So, a more generalized wording of rule 2 is:Calling any API from multiple threads is only safe if each thread has a dedicated
railHandle
, and each thread only accesses RAIL with its own handle.The few APIs that don't use
railHandle
- likeRAIL_GetTime()
,RAIL_Sleep()
, orRAIL_Wake()
- can be called from any thread.Interrupt Safety in general
In general, calling an API which changes the radio state (i.e. between rx, idle and tx) can be risky. The simplest way to write interrupt safe application is to not call state changing APIs from any interrupt handler, including the RAIL event handler. This can be achieved by setting a flag or changing a state variable in the event handler instead of calling an API directly:
Note that some RAIL API was called from the event handler, but none of those were state changing APIs.
Interrupt safety with state changing APIs
In some (usually time critical) cases however, it's not possible to avoid calling state changing APIs from the event handler (or other interrupt handler). State changing APIs are not always risky: Some APIs might be safe, as long as they don't interrupt another specific API.
Hence, in the following list, we identify the risky API after first specifying which initially-running (i.e., "interrupted") API makes it risky (and how). We've included in this list some interrupt combinations that might be "safe", but the end result is not predictable - i.e. the radio might be in rx or in idle, depending on which API is called first.
RAIL_Start<something>()
with anotherRAIL_Start<something>()
is risky, especially if they would start on different channels.RAIL_Idle(handle, <something>, true)
with anyRAIL_Start<something>()
is risky.RAIL_Idle(handle, <something>, false)
with anyRAIL_Start<something>()
is safe, but the end result is not predictable (i.e. the radio will either be in Idle, or start the requested operation).RAIL_Start<something>()
withRAIL_Idle()
is safe but the end result is not predictable, and might cause strange events (see the next section for details).RAIL_StopTxStream()
with anyRAIL_Start<something>()
is very risky (the radio might remain in test configuration and start transmitting/receiving).RAIL_StopTx()
is safe. InterruptingRAIL_StopTx()
withRAIL_Start<something>()
is safe but the end result is not predictable (i.e. the radio will either be in Idle, or start the requested operation).RAIL_StopTx()
is safe (see next section for important clarification). InterruptingRAIL_StartTx()
withRAIL_StopTx(handle, RAIL_STOP_MODE_ACTIVE)
is safe, but not predictable.RAIL_StopTxStream()
is safe. InterruptingRAIL_StartTxStream()
withRAIL_StopTxStream()
is safe but not predictable.RAIL_Idle in the Event Handler
Calling
RAIL_Idle()
orRAIL_StopTx(handle, RAIL_STOP_MODE_ACTIVE)
from the event handler might cause strange results. For example, let's say you're receiving on a channel and want to detect preambles using the eventRAIL_EVENT_RX_PREAMBLE_DETECT
andRAIL_EVENT_RX_PREAMBLE_LOST
. The following scenario may unfold:RAIL_EVENT_RX_PREAMBLE_LOST
.RAIL_Idle(railHandle, RAIL_IDLE_ABORT, true)
.RAIL_EVENT_RX_PREAMBLE_DETECT
andRAIL_EVENT_RX_PREAMBLE_LOST
both set at the same timeSo you end up with a preamble detect event, even though the radio is off. This is usually harmless, since you always have the
_LOST
or_ABORTED
event as well - but this demonstrates why your design must carefully consider in what order to handle events.The easiest way to avoid this conflicted outcome is to disable the events that might cause problems when turning off the radio.
Another way to avoid this issue is to use
RAIL_Idle(handle, RAIL_IDLE_FORCE_SHUTDOWN_CLEAR_FLAGS, true)
, which will clear the pending interrupts. However, usingRAIL_IDLE_FORCE_SHUTDOWN_CLEAR_FLAGS
has other drawbacks. It does force the radio state machine to idle state, and it might corrupt the transmit or receive FIFOs - in which case it must clear them, losing all data that might already be in there. It could also take more time to finish running thanRAIL_IDLE_ABORT
.Critical Blocks
One usual way to avoid internal safety issues is to create critical (a.k.a. atomic) blocks, in which interrupts are disabled, in the main thread to make sure some code segment is never interrupted. However, this can create other problems, so it should be used carefully. There's no general rule to avoid this kind of "collateral damage", but here's an example that should be avoided:
RSSI averaging is running, and just before it finishes, we interrupt it with
RAIL_StartTx()
which is called from a critical block. The following race condition could happen:One way to avoid the problem above is to clear interrupts in the critical block. This can be done by using
RAIL_Idle(handle, RAIL_IDLE_FORCE_SHUTDOWN_CLEAR_FLAGS, true)
at the beginning of the critical block, but the drawbacks of doing so (mentioned above) should be kept in mind. In general, it's better to avoid risky interrupts without using critical blocks in the main thread.Using FORCE_SHUTDOWN
In the two sections above we mentioned two usecases where
RAIL_IDLE_FORCE_SHUTDOWN_CLEAR_FLAGS
can be useful. In general however,RAIL_IDLE
orRAIL_IDLE_ABORT
is a sufficient and preferred way to stop transmitting/receiving - therefore theFORCE_SHUTDOWN
modes should be only used when they are really needed (as in the specific scenarios described here). For more details, see the article on the idle modes.