The RF driver

The RF driver provides access to the radio core on the CC13xx/CC26xx devices. It offers a high-level interface for command execution and to the radio timer (RAT). The RF driver ensures the lowest possible power consumption by providing automatic power management that is fully transparent for the application.

This document describes the features and usage of the RF driver API. For a detailed explanation of the RF core, please refer to the Technical Reference Manual or the RF core chapter.

Features:

  • Single-client and multi-client interface
  • Synchronous execution of direct and immediate radio commands
  • Synchronous and asynchronous execution of radio operation commands
  • Event subscription and execution of callback events
  • Automatic power management
  • Convenient access to the radio timer

Setup and configuration

The RF driver is configured in 4 different places:

  1. During build configuration by chosing either the single-client or multi-client driver version.
  2. At compile-time by setting hardware and software interrupt priorities in the board support file.
  3. During run-time initialization by setting RF_Params when calling RF_open()
  4. At run-time via RF_control()

The RF driver comes in two versions. The single-client version allowing only one driver instance to access the RF core at a time. The multi-client driver version allows concurrent access to the RF core with different RF settings. The multi-client driver has a larger footprint and is not needed for many proprietary applications. When using TI-RTOS, the driver version can be selected in the build configuration file (.cfg) as follows:

/* RF Driver selection */
driversConfig.rfDriverMode = driversConfig.RF_SingleMode;
//driversConfig.rfDriverMode = driversConfig.RF_MultiMode;

The RF driver handles RF core hardware interrupts and uses software interrupts for its internal state machine. For managing the interrupt priorities, it expects the existance of a global RFCC26XX_HWAttrs object. This is usually defined in the board support file, for example CC1310_LAUNCHXL.c, but might be kept anywhere in the application. By default, the priorities are set to the lowest possible value:

const RFCC26XX_HWAttrs RFCC26XX_hwAttrs = {
    .hwiCpe0Priority = INT_PRI_LEVEL7,    // lowest:  INT_PRI_LEVEL7
    .hwiHwPriority   = INT_PRI_LEVEL7,    // highest: INT_PRI_LEVEL1

    .swiCpe0Priority =  0,     // lowest: 0:
    .swiHwPriority   =  0,     // highest: Swi.numPriorities - 1
};

When initiating an RF driver instance, the function RF_open() accepts a pointer to an RF_Params object which might set several driver parameters. In addition, it expects an RF_Mode object and a setup command which is usually generated by SmartRF Studio:

RF_Params rfParams;
rfParams.nInactivityTimeout = 4;
RF_Params_init(&rfParams);
rfParams.nInactivityTimeout = RF_convertMsToRatTicks(2);

RF_Handle rfHandle = RF_open(&rfObject, &RF_prop, (RF_RadioSetup*)&RF_cmdPropRadioDivSetup, &rfParams);

The function :c:func`RF_open` returns a driver handle that is used for accessing the correct driver instance. Please note that the first RF operation command before an RX or TX operation command must be a CMD_FS to set the synthesizer frequency. The RF driver caches both the setup command from RF_open() and the CMD_FS for automatic power management.

While a driver instance is opened, it can be re-configured with the function RF_control().

Command execution

The RF core supports 3 different kinds of commands:

  1. Direct commands
  2. Immediate commands
  3. Radio operation commands

Direct and immediate commands are dispatched via RF_runDirectCmd() and RF_runImmediateCmd() respectively. These functions block until the command has completed and return a status code of the type RF_Stat when done.

#include <driverlib/rf_common_cmd.h>

RF_Stat status = RF_runDirectCmd(rfHandle, CMD_ABORT);

Radio operation commands are potentially long-running commands and support different triggers as well as conditional execution. Only one command can be executed at a time, but the RF driver provides an internal queue that stores commands until the RF core is free. Two interfaces are provided for radio operation commands:

  1. Asynchronous: RF_postCmd() and RF_pendCmd()
  2. Synchronous: RF_runCmd()

The asynchronous function RF_postCmd() posts a radio operation into the driver’s internal command queue and returns a command handle of the type RF_CmdHandle which is an index in the command queue. The command is dispatched as soon as the RF core has completed any previous radio operation command.

#include <driverlib/rf_common_cmd.h>

RF_Callback callback = NULL;
RF_EventMask subscribedEvents = 0;
RF_CmdHandle rxCommandHandle = RF_postCmd(rfHandle, (RF_Op*)&RF_cmdRx,
        RF_PriorityNormal, callback, subscribedEvents);

Assert(rxCommandHandle != RF_ALLOC_ERROR, "The command could not be queued.");

Command execution happens in background. The calling task may proceed with other work or execute direct and immediate commands to interact with the posted radio operation. But beware that the posted command might not have started, yet. By calling the function RF_pendCmd() and subscribing events of the type RF_EventMask, it is possible to re-synchronize to a posted command:

RF_EventMask result = RF_pendCmd(rfHandle, rxCommandHandle,
        RF_EventRxEntryDone);

// Program proceeds after RF_EventRxEntryDone, RF_EventCmdDone or RF_EventLastCmdDone.
// The two latter are always subscribed.

The function RF_runCmd() is a combination of both, RF_postCmd() and RF_pendCmd() and allows synchronous execution.

A pending or already running command might be aborted at any time by calling the function RF_cancelCmd() or RF_flushCmd(). These functions take command handles as parameters, but can also just abort anything in the RF driver’s queue:

uint8_t abortGraceful = 1;

// Abort a single command
RF_cancelCmd(rfHandle, rxCommandHandle, abortGraceful);

// Abort anything
RF_flushCmd(rfHandle, RF_CMDHANDLE_FLUSH_ALL, abortGraceful);

Callback events

The RF core generates multiple interrupts during command execution. The RF driver maps these interrupts 1:1 to callback events of the type RF_EventMask. Hence, it is unnecessary to implement own interrupt handlers. Callback events are divided into 3 groups:

  • Command-specific events, documented for each radio operation command. An example is the RF_EventRxEntryDone for the CMD_PROP_RX.
  • Generic events, defined for all radio operations and originating on the RF core. These are RF_EventCmdDone and RF_EventLastCmdDone. Both events indicate the termination of one or more RF operations.
  • Generic events, defined for all radio operations and originating in the RF driver, for instance RF_EventCmdCancelled.

How callback events are subscribed was shown in the previous section. The following snippet shows a typical event handler callback for a proprietary RX operation:

void rxCallback(RF_Handle handle, RF_CmdHandle command, RF_EventMask events)
{
    if (events & RF_EventRxEntryDone)
    {
        Semaphore_post(rxPacketSemaphore);
    }
    if (events & RF_EventLastCmdDone)
    {
        // ...
    }
}

Additionally, the RF driver can generate error and power-up events that do not relate directly to the execution of a radio command. Such events can be subscribed by specifying the callback function pointers pErrCb abd pPowerCb in RF_Params.

Power management

The RF core is a hardware peripheral and must be explicitly switched on and off. The RF driver handles that automatically and provides the following power-optimization features:

  • Lazy power-up and radio setup caching
  • Power-down on inactivity
  • Deferred dispatching of commands with absolute timing

Lazy power-up and radio setup caching

The RF core optimizes the power consumption by switching the RF core on as late as possible. For instance does RF_open() not power up the RF core immediately. Instead, it waits until another radio operation command is posted to the RF driver via RF_postCmd() or RF_runCmd().

The function RF_open() takes a radio setup command as parameter and expects a CMD_FS command as the following radio operation. This command is cached internally in the RF driver and will be used for every following power- up procedure. Whenever the client re-runs a setup command or a CMD_FS command, the driver updates its internal cache with the new settings.

 @startuml
 scale 0.8

 start
 if (RF_Mode.rfMode is known?) then (yes)
     :switch RF core on;
     :apply RF firmware patches;
     :run setup command;
     :start radio timer;

     if (FS command is pending?) then (no)
         :run cached FS command;

         if (success?) then (no)
             :RF_Params.pErrCb(\n    rfHandle,\n    RF_ERROR_CMDFS_SYNTH_PROG,\n    RF_EventError);;
             end
         else (yes)
         endif
     else (yes)

     endif

     :RF_Params.pPowerCb(\n    rfHandle,\n    NULL,\n    RF_EventPowerUp);;
     stop

 else (no)
     :RF_Params.pErrCb(\n    rfHandle,\n    RF_ERROR_INVALID_RFMODE,\n    RF_EventError);;
     end

 endif

 @enduml

Fig. 17 The RF core power-up sequence executed by the RF driver.

The power-up sequence is described in Fig. 17.

By default, the RF driver measures the time that it needs for the power-up procedure and uses that as an estimation for the next power cycle. On the CC13xx, power-up takes usually 1.6 ms. Automatic measurement can be suppressed by specifying a custom power-up time with nPowerUpDuration in RF_Params. In addition, the client might set nPowerUpDurationMargin to cover any uncertainity when doing automatic measurements. This is necessary in applications with a high hardware interrupt load which can delay the RF driver’s internal state machine execution.

Power-down on inactivity

Whenever a radio operation completes and there is no other radio operation in the queue, the RF core might be powered down. There are two options in the RF driver:

  • Automatic power-down by setting the parameter nInactivityTimeout in RF_Params. The RF core will then start a timer after the last command in the queue has completed. The default value is BIOS_WAIT_FOREVER and this feature is disabled.
  • Manual power-down by calling RF_yield(). The client should do this whenever it knows that no further radio operation will be executed for a couple of milliseconds.

 @startuml
 scale 0.8

 start
 :stop radio timer;
 :switch RF core off;
 stop

 @enduml

Fig. 18 The RF core power-down procedure executed by the RF driver.

During the power-down procedure, shown in Fig. 18, the RF driver stops the radio timer and saves a synchronization timestamp for the next power-up. This keeps the radio timer virtually in sync with the RTC even though it is not running all the time.

Deferred dispatching of commands with absolute timing

When dispatching a radio operation command with an absolute start trigger that is ahead in the future, the RF driver defers the execution and powers the RF core down until the command is due. It does that only, when:

  1. cmd.triggerType is set to TRIG_ABSTIME
  2. The difference between RF_getCurrentTime() and cmd.startTime is at not more than 3/4 of a full RAT cycle. Otherwise the driver assumes that cmd.startTime is in the past.
  3. There is enough time to run a full power cycle before cmd.startTime is due. That includes:
    • the power-down time (fixed value, 1 ms) if the RF core is already powered up,
    • the measured power-up duration or the value specified by nPowerUpDuration in RF_Params,
    • the power-up safety margin nPowerUpDurationMargin in RF_Params (default is 282 µs).

If one of the conditions are not fullfilled, the RF core is kept up and running and the command is dispatched immediately. This ensures, that the command will execute on-time and not miss the configured start trigger.

Convenience features

The RF driver simplifies often needed tasks and provides additional functions. For instance, it can read the RSSI while the RF core is in RX mode using the function RF_getRssi():

int8_t rssi = RF_getRssi(rfHandle);
Assert (rssi != RF_GET_RSSI_ERROR_VAL, "Could not read the RSSI");

For defining absolute triggers in radio operation commands, one often needs to know the current time. This can be achieved with the function RF_getCurrentTime(). When the RF core is not up and running and the radio timer is disabled, this function can still return a previse value:

uint32_t rfTime = RF_getCurrentTime();