Task Code Language Reference

Introduction

Sensor Controller tasks are implemented using a programming language with syntax similar to C. Sensor Controller Studio is used to edit, test and debug this task code. The Sensor Controller Studio output is a set of C source files, called the Sensor Controller Interface (SCIF) driver, that must be used by the System CPU application to operate the Sensor Controller. These source files contain the AUX RAM image, information for internal driver use, data structure type definitions for data exchange, and a generic API for initialization, task control, data exchange and so on.

The task code is divided into blocks for task initialization, scheduled execution, event handling with programmable trigger(s), and task termination. The code blocks are, using C terminology, function bodies that are called through the Sensor Controller’s firmware framework.

  • The System CPU application starts Sensor Controller tasks by triggering the Initialization Code block
  • The Initialization Code must schedule the first execution and/or setup the initial event trigger(s)
  • The Execution Code and Event Handler Code blocks must keep the task alive by scheduling the next execution and/or setup new event triggers
  • The System CPU application stops Sensor Controller tasks by triggering the Termination Code block
  • The Termination Code must cancel any currently enabled event triggers (scheduling is deactivated automatically)

The Execution Code is triggered by events from AON_RTC channel 2.

The Event Handler Code can be triggered by events from timers, I/O input pins, and other signals on the AUX event bus. The CC13x0/CC26x0 devices have one programmable event trigger, and therefore support only one Event Handler Code block. The CC13x2/CC26x2 and CC13x4/CC26x4 devices have three programmable event triggers, and therefore support up to three Event Handler Code blocks.

The Sensor Controller firmware framework enters standby mode when not running task code blocks. The Sensor Controller does not enter standby mode while executing a task code block.

The Sensor Controller does not support interrupts with preemption. All code blocks run to completion, and context switching (for example running the execution code for another task) can only occur when the Sensor Controller is ready to enter or has already entered standby mode.

The basic concept is illustrated below, using C syntax.

void initialization(void) {
    ... the "Initialization Code" goes here ...
}

void execution(void) {
    ... the "Execution Code" goes here ...
}

void termination(void) {
    ... the "Termination Code" goes here ...
}

// Triggered by scifStartTasksNbl()
void startTask(void) {
    initialization();
}

// Triggered by scifStopTasksNbl()
void stopTask(void) {
    termination();
}

// Triggered by scifExecuteTasksOnceNbl()
void executeTaskOnce(void) {
    initialization();
    execution();
    termination();
}

// Triggered by a tick from AON_RTC channel 2
void rtcTick(void) {
    for (... each active task ...) {
        if (... task scheduled for execution now ...) {
            execution();
        }
    }
}

For a Sensor Controller task that uses task event handling, the following comes in addition:

void eventHandling(void) {
    ... the "Event Handler Code" goes here ...
}

// Triggered for example by a falling edge on a GPIO input pin
void eventTrigger(void) {
    eventHandling();
}

Programming Language

The task code programming language uses a syntax that is similar to C, but has limited features compared to C since it specifically targets the Sensor Controller Engine’s instruction set and architecture. For example, there is only support for 16-bit variables.

The language works as a sandbox in the sense that it does not allow direct access to hardware or firmware framework functionality. This must be done via the procedure library provided, which may be regarded as the “DriverLib” for the Sensor Controller. The library procedures are written in assembly, and are located in the proc_defs installation directory. The procedures become available for use by enabling the associated resources in the project’s Task Panels .

The current syntax/feature limitations described below are likely to be improved in future versions of the Sensor Controller Studio.

Current General Limitations

Statements may not be split over multiple lines.

Comments

Single-line code comments are supported, at the start or at the end of lines:

// Comment
statement; // Comment

Current Limitations

Block code comments ( /* ... */ ) are not supported.

Variable Types

The task code can declare variables of the following types:

  • U16 - 16-bit unsigned integer
  • S16 - 16-bit signed integer
  • U16* - Pointer to a 16-bit unsigned integer variable in RAM
  • S16* - Pointer to a 16-bit signed integer variable in RAM

Data structure variables will be of type S16 if specified as signed type, and otherwise U16 .

There is no type checking. The variable type (signed or unsigned) is only relevant in right-shift operations and compare operations.

Syntax

Only one variable can be declared per line. Below, U16 , S16 , U16* and S16* are commonly referred to as TYPE .

// Uninitialized:
TYPE varName;

// Initialized:
TYPE varName = <expression>;

Current Limitations

Variable casting is not supported:

U16 x;
S16 y = 15;
y = x;       // OK
y = (S16) x; // Unsupported

RAM and Register Variable Access

Any variable access may be one of the following:

  • varName - Register variable value ( U16 or S16 )
  • pStructVar - RAM variable pointer ( U16* or S16* )
  • *pStructVar - Indirect access to RAM variable
  • *(pStructVar++) - Indirect access to RAM variable, with pointer post-increment
  • struct.var - Direct access to a data structure member
  • struct.pArrayVar[n] - Direct access to a data structure array member, with variable index n
  • struct.pArrayVar[42] - Direct access to a data structure array member, with constant index, for example 42 or ARRAY_SIZE - 1

All RAM variable accesses are treated as volatile.

The variable n is special:

  • It is always stored in the R0 register
  • It is used for data structure array indexing. No other variables can be used for this purpose.
  • It is used as counter variable in for -loops. No other variables can be used for this purpose.

All RAM variables accessible from task code are declared statically in the following data structures:

  • cfg - Task configuration, if present
  • input - Task input data, if present
  • output - Task output data, if present
  • state - Task state, if present

When using the Multiple-Buffered Output Data Exchange resource, any references to the output data structure will automatically select the correct buffer to be accessed by the Sensor Controller. The compiled task code will then access the data structure members indirectly via hidden control variables stored immediately before the data structure buffers.

To create a pointer to a data structure member, use the # operator. For arrays, specify either the start of the array, #struct.pArrayVar , or a specific element, #struct.pArrayVar[ARRAY_SIZE - 1] . Note that a pointer to a multiple-buffered data structure member is no longer valid after buffer switching.

Constant array indexes can be expressions, as described below under Variable Assignment. Constant indexes array are validated at compile time, and must be in range 0 to array size - 1 .

Current Limitations

The task code compiler currently does not spill registers to RAM, and this limits the number of concurrently live variables. A detailed error message is displayed if the compiler cannot assign a register to a variable.

Variable Assignment

The following variable assignment operators are available:

  • y = expr; - Assignment
  • y += expr; - Compound addition
  • y -= expr; - Compound subtraction
  • y |= expr; - Compound OR
  • y &= expr; - Compound AND
  • y ^= expr; - Compound XOR
  • y <<= expr; - Compound left-shift
  • y >>= expr; - Compound right-shift (arithmetic if y is signed, logical otherwise)

In these assignment operations, expr may refer to a numeric value, defined constant, RAM/register variable or any combination of these used in expressions. Expressions of arbitrary complexity may be constructed from the following sub-expressions, where a and b may represent a numeric value, defined constant, RAM/register variable or another sub-expression:

  • Arithmetic operations:
    • (a + b) - Addition
    • (a - b) - Subtraction
    • (-a) - Negation
    • (@a) - Absolute value
    • Only when both a and b are constant values:
      • (a * b) - Multiplication
      • (a / b) - Division
      • (a % b) - Modulus
  • Logical operations:
    • (a | b) - Bitwise OR
    • (a & b) - Bitwise AND
    • (a ^ b) - Bitwise XOR
    • (~a) - Bitwise inversion
  • Shift operations:
    • (a << b) - Left-shift
    • (a >> b) - Right-shift (arithmetic if a is a signed variable or a negtive constant, logical otherwise)
  • Compare operations:
    • Only when both a and b are constant values:
      • a < b - 1 if a is smaller than b , otherwise 0
      • a <= b - 1 if a is smaller than or equal to b , otherwise 0
      • a == b - 1 if a is equal to b , otherwise 0
      • a != b - 1 if a is not equal to b , otherwise 0
      • a >= b - 1 if a is greater than or equal to b , otherwise 0
      • a > b - 1 if a is greater than b , otherwise 0

The result of a sub-expression can either be signed or unsigned. This is determined as follows, by priority:

  • For shift operations: If a is a constant, then the result is signed if a is negative, and unsigned otherwise
  • If a is a variable, then the result has the same type as a
  • If a is constant and b is a variable, then the result has the same type as b
  • If a and b are constants, the sub-expression is simply reduced as described in the Constant Operands section below.

Operator precedence is not supported. Enclosing parentheses are required for all sub-expressions:

a = b + c;
a = b + (c + d);
a = (b + c) + d;
a = (b + (@c)) << (d - (~e));

Constant Operands

For sub-expressions that contain only numeric values or constants, the operands are treated as 32-bit signed integers, with the following exceptions:

  • Bitwise inversion: The result is AND-ed with 0xFFFF .

For sub-expressions that contain RAM/register variable(s), all operations are 16-bit, and errors will be reported for out-of-range numeric or constant value.

Shift Operation Limitations

Observe the following range limitations for shift operations:

  • Constant shifted by constant: The shift value must be between 0 and 31 . Bit 5 and higher in the shift value are ignored.
  • Variable shifted by constant: The shift value must be between 0 and 16 . An out-of-range shift value generates error during code generation.
  • Constant or variable shifted by variable: The shift value must be between 0 and 15 . Bit 4 and higher in the shift value are ignored.

Conditional Execution

Values can be tested by if and ifnot statements in order to execute blocks of task code conditionally. These statements can be nested if needed and/or used in combination with loop execution:

if (condition) {
    ... executed if the condition is true ...
}

ifnot (condition) {
    ... executed if the condition is false ...
}

if (condition) {
    ... executed if the condition is true ...
} else {
    ... executed otherwise ...
}

ifnot (condition) {
    ... executed if the condition is false ...
} else {
    ... executed otherwise ...
}

For more compact and more readable code, if-else and ifnot-else statements can be chained:

if (conditionA) {
    ... executed if conditionA is true ...
} else if (conditionB) {
    ... executed if conditionA is false and conditionB is true ...
} else ifnot (conditionC) {
    ... executed if conditionA, conditionB and conditionC are false ...
} else {
    ... executed otherwise ...
}

The following conditions may be used, where a and b are numeric values, constants, numeric/constant sub-expressions (as in variable assignment) or RAM/register variables:

  • Comparison:
    • a < b - True if a is smaller than b
    • a <= b - True if a is smaller than or equal to b
    • a == b - True if a is equal to b
    • a != b - True if a is not equal to b
    • a >= b - True if a is greater than or equal to b
    • a > b - True if a is greater than b
  • Bit-test:
    • a & b - True if a AND’ed with b is not equal to zero

Comparison can either be signed or unsigned. This is determined as follows, by priority:

  • If a is a variable, then a determines whether the operation is signed or unsigned 16-bit
  • If a is constant and b is a variable, then b determines whether the operation is signed or unsigned 16-bit
  • If a and b are constants, the operation is always signed 32-bit

Conditions that only contain numeric/constant values are not restricted to the comparison and bit-test operations listed above, but are evaluated in the same way as expressions in variable assignment. If the expression value is non-zero, the condition is true. Otherwise the condition is false.

If the condition only contains numeric/constant values, the if / ifnot / else statement itself will be optimized away, and the code is either included or excluded from the generated firmware. The effect is equivalent to use of C preprocessor #if / #else . For example:

// The follow Sensor Controller code ...
if (BUTTON_COUNT >= 1) {
    doSomething;
} else {
    doSomethingElse;
}

// ... is equivalent to the following in C:
#if (BUTTON_COUNT >= 1)
    doSomething;
#else
    doSomethingElse;
#endif

Current Limitations

Curly braces must be written as done above.

Use of expressions and boolean operators ( ! , && and || ) in conditions is not supported.

Loop Execution

Values can be tested by for , while and do-while statements in order to execute blocks of task code zero or more times (that is until the specified condition is false). These statements can be nested if needed and/or used in combination with conditional execution:

for (U16 n = 0; n < CONSTANT; n++) {
    ... executed "CONSTANT" times, with n going from 0 to "CONSTANT - 1" ...
}

while (condition) {
    ... executed repeatedly as long as the condition is true ...
}

do {
    ... executed once and then repeatedly as long as the condition is true ...
} while (condition);

The while and do-while loop condition syntax is the same as for conditional execution statements, except that constant conditions are not allowed.

A variable declared inside a do-while loop can be tested in the loop condition.

The counter variable n is special in that it can be used to index data structure arrays, for example to iterate over a number of analog inputs and sample each with the ADC, or to compare a sensor value against a series of threshold values.

The for -loop is bound to use n as counter variable, and this may only count from 0 to a constant value (minus 1). This means implicitly that for loops cannot be nested. More flexibility, but with less compact syntax, can be achieved by using a while -loop instead; for example:

S16 n = x;
while (n >= 0) {
    ...
    n -= 2;
}

Current Limitations

There are currently no options to break out of an iteration ( continue; ) or a loop ( break; ).

Procedure Calls

Procedures encapsulate access to hardware peripherals, firmware framework functionality and optimized algorithms.

Procedures are similar to C functions, but there are some differences:

  • There can be more than one return value, and the syntax is different from C for return values
  • Some parameters may be required to be given as immediate values (and not through register variables). In procedure documentation this is denoted by a # before the parameter name - not to be confused with the address-of operator.
  • Due to hardware-based program counter stack, nested procedure calls from task code is not possible

Parameters may be given as expressions, as in variable assignment.

Syntax

// Without parameters or return value(s):
procName();

// With parameter(s), but no return values:
procName(param0);
procName(param0, param1);

// With parameter(s) and return value(s):
procName(param0; return0);
procName(param0, param1; return0, return1);

// With return value(s), but no parameter(s):
procName(return0);
procName(return0, return1);

Parameters are passed by value , while return values are passed by reference . Procedures that modify variables have these variables both as parameter and return value:

// Increment variable "x" by 1 and saturate at 42
utilIncrAndSat(x, 42; x);

Macro Declarations and Macro Calls

Macros allow for fragments of Sensor Controller task code to be encapsulated and reused, in order to make the task code more readable and reduce source code duplication.

Sensor Controller macros are a hybrid of C macros and inline functions:

  • The macro name must be unique, and cannot match any language keywords, variables, procedures and so on
  • Parameters behave in the same way as in C macros, and may represent ingoing and outgoing variables, constants, procedure names, variable types and so on. A macro can have zero or more parameters.
  • The macro declaration adds a variable scope level, meaning that variables declared in the macro are not visible outside of the macro (as in C inline functions)
  • In code generation, the macro contents are duplicated for each macro call
  • User-friendly compiler error messages and debugging

Macros are local to task code blocks, meaning that a macro declaration in one task code block is not visible to other task code blocks.

A macro declaration can be placed anywhere in the task code, as long as it is declared before the first line that calls it.

A macro can call other macros, however recursive calls are not allowed.

Syntax

// Macro declaration without parameters:
macro pulseLedPinOnce() {
    gpioSetOutput(AUXIO_O_LED);
    gpioClearOutput(AUXIO_O_LED);
}

// Macro declarations with parameter(s):
macro pulseLedPinMultiple(pulseCount) {
    U16 pulsesLeft = pulseCount;
    do {
        gpioSetOutput(AUXIO_O_LED);
        gpioClearOutput(AUXIO_O_LED);
        pulsesLeft -= 1;
    } while (pulsesLeft > 0);
}

macro adcGetScaledSample(result, leftShift) {
    U16 adcValue;
    adcGenManualTrigger();
    adcReadFifo(adcValue);
    result = adcValue << leftShift;
}

// Pulse the LED pin twice
pulseLedPinOnce();
pulseLedPinOnce();

// Sample the ADC and store the value shifted left by 4 bits
readAdcValueScaled(output.sensorValue, 4);

// Pulse the LED pin 8 times
pulseLedPinMultiple(8);