Execution States and Status Codes

After delivering to the RF core, a radio operation passes several states. They are illustrated as a UML state chart in Figure 13. and explained in Table 1. Each state performs several activities which are explained during the next sections.

 @startuml
 scale 0.8

 state PENDING {
     state Parsing
     state WaitingForStartTrigger
     [*] -> Parsing
     Parsing --> WaitingForStartTrigger : ok
     Parsing --> [*] : failed
     Parsing --> [*] : skipped
     WaitingForStartTrigger -> [*] : triggered
 }
 state ACTIVE {
     state Executing
     note right
         command-specific
     end note
     state Evaluating : evaluate(result);
     note left
         might set skipping = true
         for chained commands
     end note

     [*] -> Executing
     Executing --> Evaluating : finished / result
     Evaluating -> [*]

 }
 state DONE as "DONE*"
 state ERROR as "ERROR*"

 [*] -> IDLE
 IDLE -> PENDING : posted
 PENDING --> ACTIVE : triggered
 PENDING -> SKIPPED :  skipped
 PENDING --> ERROR : parsing failed
 ACTIVE --> DONE : done [success]
 ACTIVE --> PENDING : done\n [skipping == true,\ncmd.condition.nSkip == 0]
 ACTIVE -> ERROR : done [fail]
 DONE -[hidden]-> ERROR
 DONE --> [*]
 SKIPPED --> [*]
 ERROR -> [*]

 @enduml

Figure 13. The life-cycle of a radio operation. The state is equivalent to the status code despite of states marked with a * .

Command Execution States

Table 1. Command status codes as defined in <ti/devices/${DEVICE_FAMILY}/driverlib/rf_mailbox.h>. Status codes marked with a (*) do not exist, but represent multiple command-specific variants.
Status Description
IDLE The command has not been posted to the RF core, yet. This is the default status.
PENDING The command is being parsed and the RF core waits for the start trigger to fire.
ACTIVE This state is very command-specific. The command executes until it terminates. Some commands provide also end triggers for alternative termination. Command execution produces a result, either of TRUE, FALSE or ABORT. After the execution phase, both, the result value and the command condition rule get evaluated. Several command-specific callback events may be generated during the ACTIVE state.
DONE* A command-specific status code is written to status and a callback event is generated for single commands or for the last command in a chain.
ERROR* Either parsing failed or command execution ended with an error. A command-specific error code is written to status and a callback event is generated.
SKIPPED The command was skipped due to a skipping rule in a previous command. command.status is set to SKIPPED. This applies only to commands in chains. A callback is not performed.

States marked with (*) do not exist as one explicit status code, but as various command-specific variants. The code PROP_DONE_OK, for instance, is specific to proprietary commands.

Status codes are defined in <ti/devices/${DEVICE_FAMILY}/driverlib/rf_mailbox.h> for common radio operations. Additional codes for proprietary commands are are defined in <ti/devices/${DEVICE_FAMILY}/driverlib/rf_prop_mailbox.h>.

The status is queried via the command’s status field that is available for every radio operation:

struct RF_Op {
   // ...
   uint16_t status; // An integer telling the status of the command. This value is
                    // updated by the RF core during operation and may be read by the
                    // system CPU at any time.
    // ...
};

It is recommended to read the status only after command completion:

// Assuming that 'command' points to a valid radio operation and
// execution has completed. Volatile is needed because 'status'
// is written on another processor.
volatile RF_Op* command;
if (((volatile RF_Op*)command)->status == PROP_DONE_RXTIMEOUT) {
    // ...
}

It is tempting to use the status member to synchronize to command execution, for instance, to wait until the command has actually started:

// Dangerous, don't do this!
while (((volatile RF_Op*)command)->status != ACTIVE) {}

However, this is strongly discouraged because the calling thread might get interrupted while the command has already finished in the meantime. Hence, the ACTIVE state is never seen and the while loop runs forever. Instead, use RF driver events to synchronize to command execution, either via callbacks or RF_pendCmd().

Error Cases

Whenever executing an RF operation, it is good practice to check the status code and test for possible error cases. Depending on whether it is a proprietary or a PHY-independent command, several various status codes apply. It is up to the application to take appropriate measures.

The following code snippet is an example of how to execute a TX operation and how to check the status:

#include <ti/devices/${DEVICE_FAMILY}/driverlib/rf_prop_mailbox.h>

// Execute the command
RF_EventMask terminationReason = RF_runCmd(rfHandle, (RF_Op*)&RF_cmdPropTx,
                                           RF_PriorityNormal, NULL, 0);

if ( terminationReason & RF_EventCmdDone )
{
    // A radio operation command in a chain finished
}
else if ( terminationReason & RF_EventLastCmdDone )
{
    // A stand-alone radio operation command or the last radio
    // operation command in a chain finished.
}
else if (terminationReason &  RF_EventCmdCancelled )
{
    // Command canceled before it was started; it can be caused
    // by RF_cancelCmd() or RF_flushCmd().
}
else if ( terminationReason & RF_EventCmdAborted )
{
    // Abrupt command termination caused by RF_cancelCmd() or
    // RF_flushCmd().
}
else if ( terminationReason & RF_EventCmdStopped)
{
    // Graceful command termination caused by RF_cancelCmd() or
    // RF_flushCmd().
}
else
{
    // Uncaught error event
}

uint32_t cmdStatus = ((volatile RF_Op*)&RF_cmdPropTx)->status;
switch(cmdStatus)
{
    case PROP_DONE_OK:
        // Packet transmitted successfully
        break;
    case PROP_DONE_STOPPED:
        // Received CMD_STOP while transmitting packet and finished
        // transmitting packet
        break;
    case PROP_DONE_ABORT:
        // Received CMD_ABORT while transmitting packet
        break;
    case PROP_ERROR_PAR:
        // Observed illegal parameter in the posted RF command
        break;
    case PROP_ERROR_NO_SETUP:
        // Command sent without setting up the radio in a supported
        // mode using CMD_PROP_RADIO_SETUP or CMD_RADIO_SETUP
        break;
    case PROP_ERROR_NO_FS:
        // Error case. Due to CMD_FS failing during power-up, CMD_PROP_TX
        // could not succeed. The RF core is powered up and the synth is not running.
        // One possible work-around is: Ignore it and power the RF core down
        // manually. CMD_FS will be re-executed on the next RF core power-up.
        RF_yield(rfHandle);
        break;
    case PROP_ERROR_TXUNF:
        // TX underflow observed during operation
        break;
    default:
        // Uncaught error event - these could come from the
        // pool of states defined in rf_mailbox.h
        while(1);
}

Error Callback

With the RF_Params struct, you can add an error callback:

void rfErrCb(RF_Handle rfHandle, RF_CmdHandle rfCommandHandle, RF_EventMask rfEventMask);
void *mainThread(void *arg0)
{
    RF_Params rfParams;
    RF_Params_init(&rfParams);
    rfParams.pErrCb = rfErrCb;

In the following example, a RF_ERROR_CMDFS_SYNTH_PROG (synth error) will be handled by posting a new CMD_FS. When you do RF_postCmd() in the error callback, the command will be appended to the RF driver’s command queue. There is no possibility to inject another CMD_FS before any command pending in the queue. When the error callback is executed, the RF core just came from power-down and there is (usually) an RX or TX command waiting in the RF driver queue. Thus, if we want the CMD_FS to be the next command executed, we should cancel any RF command in the queue.

void rfErrCb(RF_Handle rfHandle, RF_CmdHandle rfCommandHandle, RF_EventMask rfEventMask)
{
    switch (rfCommandHandle)
    {
    case RF_ERROR_INVALID_RFMODE:
        // Invalid RF_Mode.
        break;
    case RF_ERROR_CMDFS_SYNTH_PROG:
    {
        // Synthesizer error with CMD_FS.

        // Cancel any RF commands in the queue
        uint8_t stopMode = 1; //1: Stop gracefully, 0: abort abruptly
        RF_cancelCmd(rfHandle, RF_CMDHANDLE_FLUSH_ALL, stopMode);
        // Post a new CMD_FS
        RF_postCmd(rfHandle, (RF_Op*)&RF_cmdFs, RF_PriorityNormal, NULL, 0);
        break;
    }
    default:
        // Uncaught error handle
        break;
    }
}

Attention

You must not use any blocking RF driver calls (such as RF_runCmd() and RF_pendCmd()) in the error callback! Instead, use non-blocking RF driver calls such as RF_postCmd().