Event Handling and Automatic State Transitions#

This tutorial is the continuation of a series started with the article Introduction. The previous chapter was about transmitting a packet.

Please read them first if you haven't.

The completed example for this article is available on GitHub. You can either import this example, as documented in the readme on Github, or follow this article and modify the example from the previous article.


In the previous tutorials, we set up RAIL to do various tasks through API commands. However, RAIL can inform the application on various events almost real time (basically, wiring interrupts through RAIL). So let's improve what we had in the previous tutorial.

The Event Handler Function#

You might have already seen this function in app_process.c:

void sl_rail_app_on_event(RAIL_Handle_t rail_handle, RAIL_Events_t events)

This is the event handler function, which is called by the RAIL library, and routed through the RAIL Utility, Initialization component.

The events variable is a bit field, holding all possible events. Be careful, this bit field does not fit into a 32-bit variable, which is the size of the int on EFR32 MCUs.

The event handler should be prepared for multiple events in a single call. There's no "clearFlag" mechanism. Each event will be signaled once, and the application is responsible for handling it.

Keep in mind that almost all events are generated from interrupts, so the event handler is usually running from interrupt context (i.e., interrupts are disabled, unless interrupt priorities are configured). Therefore, the event handler should run fast; if you have to do something that takes more time, set a flag, and do it from the main loop.

You should also be careful with RAIL interrupt safety. Calling state changing APIs (RAIL_Start*, RAIL_Stop* or RAIL_Idle) from the interrupt handler is allowed, but we recommend to read the interrupt safety article before doing so.

The events enabled by default are configured on the RAIL Utility, Initialization component (under Platform/Radio, press Configure):

Event ConfigurationEvent Configuration

However, using the RAIL API directly is actually simpler than using the event configuration capabilities of this component. The API for this is RAIL_ConfigEvents(). You can keep the event setup enabled; it doesn't really matter.

RAIL_ConfigEvents() has a mask and an events parameter, making it simple to configure multiple events at the same time; e.g., to enable or disable a single event, leaving the others untouched:

//disable RAIL_EVENT_TX_PACKET_SENT
RAIL_ConfigEvents(railHandle, RAIL_EVENT_TX_PACKET_SENT, 0);

//enable RAIL_EVENT_TX_PACKET_SENT
RAIL_ConfigEvents(railHandle, RAIL_EVENT_TX_PACKET_SENT, RAIL_EVENT_TX_PACKET_SENT);

RAIL also defines a few event groups (defined in rail_types.h, the most useful are:

  • RAIL_EVENTS_ALL includes all the events

  • RAIL_EVENTS_NONE includes no events (equivalent to 0)

  • RAIL_EVENTS_TX_COMPLETION includes all possible events after a successful RAIL_StartTx

  • RAIL_EVENTS_RX_COMPLETION includes all possible events that can result in state transition from Rx mode

This can be used, for example, to disable or enable group of events:

//disable all events
RAIL_ConfigEvents(railHandle, RAIL_EVENTS_ALL, 0);

//enable all events
RAIL_ConfigEvents(railHandle, RAIL_EVENTS_TX_COMPLETION, RAIL_EVENTS_TX_COMPLETION);

If you enable/disable events in group, it's a good practice to create similar macro group of events in your application.

Note that enabling all events is not recommended. Some events - depending on the radio config - might happen very often, and even if you don't do anything on that event in the handler, just the execution of the event handler takes some CPU time.

RAIL has no API to flush pending events, but disabling and enabling them will have that result:

//flush RAIL_EVENT_TX_PACKET_SENT
RAIL_ConfigEvents(railHandle, RAIL_EVENT_TX_PACKET_SENT, 0);
RAIL_ConfigEvents(railHandle, RAIL_EVENT_TX_PACKET_SENT, RAIL_EVENT_TX_PACKET_SENT);

However, you only need to do this in rare cases. Enabling events is not like unmasking interrupts. Enabling them will not trigger the event handler on past events.

Tx Error Handling#

In the last tutorial, we sent out a frame, but we didn't check the results because it would have needed the event handler. Let's do the following:

  • Toggle led0 on success (Tx or Rx)

  • Toggle led1 on error (StartTX, Rx or Tx error)

Nb: at startup, all leds are set off by the stack.

First, for the LEDs, enable the Simple LED component with led0 and led1 instances.

In app_process.c (to get access to the led instances, now declared in autogen/sl_simple_led_instances.h):

#include "sl_simple_led_instances.h"

For the actual radio related changes, we'll need to enable the success and error events. We can do that during init since we don't really need to disable anything at runtime for such simple applications:

RAIL_Handle_t app_init(void)
{
  //...
  RAIL_ConfigEvents(rail_handle, RAIL_EVENTS_ALL, RAIL_EVENTS_TX_COMPLETION);
}

Next, let's check for errors returned by the StartTx call:

RAIL_Status_t status = RAIL_StartTx(rail_handle, 0, RAIL_TX_OPTIONS_DEFAULT, NULL);
if ( status != RAIL_STATUS_NO_ERROR ) {
  sl_led_toggle(&sl_led_led1);
}

Finally, let's check the events for success and errors:

void sl_rail_util_on_event(RAIL_Handle_t rail_handle, RAIL_Events_t events)
{
  (void)rail_handle;
  if ( events & RAIL_EVENTS_TX_COMPLETION ) {
    if ( events & RAIL_EVENT_TX_PACKET_SENT ) {
      sl_led_toggle(&sl_led_led0);
    } else {
      sl_led_toggle(&sl_led_led1); //all other events in RAIL_EVENTS_TX_COMPLETION are errors
    }
  }
}

With this, you should see led0 toggling on every transmitted packet.

Note that the example on Github has more error handling added to it, e.g., the return value of RAIL_SetTxFifo() is also checked.

Receiving Packets#

Let's add receive capabilities to this application! Again, use led0 to report success and led1 for errors.

First, we need to modify the init code.

In app_process.c (to get access to the led instances):

#include "sl_simple_led_instances.h"

We need Rx success/error events enabled, and we need to start the radio in Rx mode:

RAIL_Handle_t app_init(void)
{
  //...

  RAIL_Status_t status = RAIL_ConfigEvents(rail_handle,
                                           RAIL_EVENTS_ALL,
                                           RAIL_EVENTS_TX_COMPLETION
                                           | RAIL_EVENTS_RX_COMPLETION);
  if (status != RAIL_STATUS_NO_ERROR) {
    // Turn led1 ON on ConfigEvents Error
    sl_led_toggle(&sl_led_led1);
  }
  status = RAIL_StartRx(rail_handle,
                        0,
                        NULL);
  if (status != RAIL_STATUS_NO_ERROR) {
    // Turn led1 ON on StartRx Error
    sl_led_toggle(&sl_led_led1);
  }
}

The API RAIL_StartRx() is similar to Tx. The second argument of the function indicates which channel is going to be used.

Finally, let's check the events for success and errors (in app_process.c):

void sl_rail_util_on_event(RAIL_Handle_t rail_handle, RAIL_Events_t events)
{
  //...
  if ( events & RAIL_EVENTS_RX_COMPLETION ) {
    if ( events & RAIL_EVENT_RX_PACKET_RECEIVED ) {
      sl_led_toggle(&sl_led_led0);
    } else {
      sl_led_toggle(&sl_led_led1); //all other events in RAIL_EVENTS_RX_COMPLETION are errors
    }
  }
}

Automatic State Transitions#

By default, after receiving or transmitting a packet, RAIL will switch to idle mode (i.e., turn off the radio). Since we want it to keep receiving future packets, we can use RAIL's auto state transition feature. This can be configured on the RAIL Utility, Initialization component, let's set all dropdowns to RX:

Auto State transitionsAuto State transitions

We're basically saying that after the packets are received/transmitted, we want to return to Rx. These configurations can be also changed in run-time, using the RAIL_SetRxTransitions() and RAIL_SetTxTransitions() APIs.

If you compile and flash the program after this modification, you should see that now both WSTK toggles led0 on every button press.

The following illustrates a high level flowchart of the radio controller:

Radio controller high level flowchartRadio controller high level flowchart

Note that in the Tx state, calling RAIL_Idle() will operate a transition to the radio OFF state.

Testing and Conclusion#

If you install this example to two boards (make sure you attach the antennas if you use sub-GHz kits on sub-GHz config), you will see that on the press of btn0 on either device, led0 on both devices toggle. However, we can't see what we're receiving, since we never actually download the messages.

We're going to fix this next time.

API Introduced in this Article#

Functions#

Types and Enums#