CC13xx/CC26xx SimpleLink Core SDK User's Guide

Table of Contents

Introduction to SimpleLink™ MCU SDK

The SimpleLink™ MCU Software Development Kits (SDK) contain software development tools that enable engineers to develop applications on a range of microcontroller families from Texas Instruments. The SDK is provided as separate installations optimized for each SimpleLink microcontroller family. These versions of the SDK share most components and are engineered to support the creation of portable applications. These powerful software toolkits provide a cohesive and consistent software experience by packaging essential software components and easy-to-use examples in one easy-to-use software package.

If you have not already installed the SDK and performed the initial setup steps, see the Quick Start Guide.

Documentation and Support

Links to documentation for the SDK and its components are provided in the Documentation Overview.

The SimpleLink Academy provides step-by-step workshops and video tutorials that introduce many of the components of the SimpleLink SDK.

This SDK is supported on the TI E2E™ Community.

SDK Components

The SDK components are used together to build applications. The SDK components relate to each other as shown in the following diagram.

Starting from the bottom of this architecture diagram, the components are as follows:

Directory Structure

The SDK is installed in /ti or c:\ti by default. This is also the installation location for any plug-ins that are installed alongside the SDK. This is the top-level directory structure of the SDK installation:

Since the same directory structure is used across the different SDKs in the TI SimpleLink family, it is possible to move application code between the different devices without making significant changes to the file structure or include paths.

The following figure shows a high-level view of the “source” directories that are common to all device types. The SDK provides additional features for specific device families.

The following figure similarly provides an overview of the “examples” directory contents that are common to all device types.

Tools and Utilities Not Included

The SDK does not contain an IDE or code generation tools such as a compiler and linker. You should have one of the supported code generation tool suites available. The supported suites are Code Composer Studio with TI’s Code Generation Tools, IAR Workbench with its compilers, and the GNU Compiler Collection (gcc).

The SDK does not install FreeRTOS. You can download the FreeRTOS source files via the FreeRTOS website. Refer to the SimpleLink Core SDK Release Notes for information about supported versions of FreeRTOS. The SDK does install the Driver Porting Layer (DPL) and POSIX abstractions to allow FreeRTOS to be used with the other SDK components.

The SDK does install XDCtools, which includes configuration tools for the TI-RTOS Kernel. The XDCtools component contains the underlying tools and modules used by TI-RTOS and its kernel. When you install the SDK, XDCtools is installed in a parallel directory at the same level as the SDK’s installation directory.

Some versions of the SDK also offer functionality-specific tools, such as the BTool for interfacing with the TI Bluetooth Low Energy (BLE) stack from a PC.

Understanding a SimpleLink MCU SDK Application

There are three different project layouts with the SimpleLink MCU SDK’s examples:

The SimpleLink MCU SDK provides an example called demos\portable that serves as an introduction to creating your own applications using the SDK. This example is comprised of an application project and a kernel project. The example uses the UART and I2C drivers and low-power states. It illustrates multi-tasking, I/O drivers, and low-power states. The code uses POSIX so that it is easy to run the example using either the TI-RTOS Kernel or the FreeRTOS kernel.

The application has the following functionality.

The “portable” example is located in the <SDK_INSTALL_DIR>\examples\rtos\<board>\demos\portable directory. It is available for all supported boards, RTOS kernels, toolchains, and IDEs.

Importing, Building, and Running the Example

Import the example as described in the Quick Start Guide. Separate instructions are provided there for the Code Composer Studio (CCS) IDE, IAR Embedded Workbench, makefiles using the GCC compiler, and other supported development environments. Follow the instructions in the “Execute your First Application” section of the Quick Start Guide for your development environment, but choose the “portable” example for your board, RTOS (TI-RTOS or FreeRTOS), and compiler.

Build the “portable” example as described in the same section of the Quick Start Guide.

A README.html file is included with the example files you imported. View this file and follow the instructions in the “Example Usage” section to run the example.

For boards other than CC32XX, you’ll need the Sensors BoosterPack Module, which contains a TMP116 temperature sensor.

For CC32XX boards using CCS, let the device “Free Run” instead of “Run.”

Understanding the Example

This section examines the “portable” example’s code to understand how it was created.

To see how the example supports multiple IDEs and RTOS kernels, it is best to look at the example files in the SDK installation. The process of importing an example removes files used to support other IDEs and RTOS kernels than the one you are using.

Go to your <SDK_INSTALL_DIR>\examples\rtos\<board>\demos\portable directory.

The example contains the following source files:

(The remaining files provide documentation for the example and a default Runtime Object View layout.)

The following sections examine these parts of the application in more detail. Open the code files as you read these sections to better understand how an application uses the SDK.

main_tirtos.c and main_freertos.c

The two versions of the main() function are mostly identical. Because POSIX Pthreads are used instead of direct calls to kernel APIs, only minor differences are needed to run the TI-RTOS Kernel or the FreeRTOS kernel.

Header Files

Both versions of the file #include the following files:

The FreeRTOS version of the file also includes FreeRTOS.h and task.h.

The TI-RTOS version of the file also includes ti/sysbios/BIOS.h, which in this case locates the file within <SDK_INSTALL_DIR>/kernel/tirtos/packages.

The main() Function

  1. The main() function begins by calling Board_init(), which performs device specific initialization (e.g., initializing clocks and power management functionality).
  2. The function then sets various thread attributes in the attrs structure:
    • It calls pthread_attr_setdetachstate() with the PTHREAD_CREATE_DETACHED state to cause the threads created to be in the detached state, since the threads used by this application will never need to join with another thread.
    • It calls pthread_attr_setschedparam() to set the default scheduling priority for the threads to 1 (the minimum priority).
    • It calls pthread_attr_setstacksize() to set the size of the thread’s stack to the value defined for the THREADSTACKSIZE constant, which is defined as a size in bytes earlier in the same file.
  3. The main() function then calls pthread_create() twice.
    • The first time it creates a thread that will run the consoleThread() function. This thread runs at the default priority (1, which is the minimum). (See the console.c file for actions performed by this thread.)
    • The second time it creates a thread that will run the temperatureThread() function. This thread is set to a higher priority (2) than consoleThread. As a result, if both threads are ready to run, the temperatureThread runs first. The console thread runs when the temperature thread is blocked or waiting for data from the I2C transfer. (See the temperature.c file for actions performed by this thread.) For example:
    priParam.sched_priority = 2;
    pthread_attr_setschedparam(&attrs, &priParam);
    
    retc = pthread_create(&thread, &attrs, temperatureThread, NULL);
    if (retc != 0) {
        /* pthread_create() failed */
        while (1);
    }
    See the TI-POSIX User’s Guide for details about which POSIX APIs are supported by the SDK.
  4. The main() function then creates a mutex that will be used to protect the temperature variables from unsafe updates by the consoleThread() and temperatureThread().
  5. Next the main() function calls GPIO_init() to initialize the GPIO driver. This initialization is performed in main() because both threads will use the GPIO driver.
  6. At this point, the RTOS-specific versions of the main() function diverge.
    • The TI-RTOS Kernel version calls BIOS_start() to start the BIOS scheduler. BIOS_start() does not return.
    • The FreeRTOS version calls vTaskStartScheduler() to start the FreeRTOS scheduler. vTaskStartScheduler() does not return.

Note: A “portableNative” variant of the “portable” example is provided for both TI-RTOS and FreeRTOS. These examples call the TI-RTOS Kernel or FreeRTOS APIs directly, instead of calling the POSIX APIs. Using the RTOS APIs directly saves both code and data memory. The only code differences for these native RTOS examples are located in the application source files: main_tirtos.c, main_freertos.c, console.c, and temperature.c.

For example, the TI-RTOS Kernel version of the portableNative example calls the Task_create() API instead of the pthread_create() API code shown in Step 3 as follows:

    Task_Params_init(&taskParams);
    taskParams.priority = 2;
    taskParams.stackSize = 768;

    temperatureTaskHandle = Task_create(temperatureThread, &taskParams, &eb);

See the TI-RTOS APIs for more about the TI-RTOS Kernel APIs.

console.c

This file is identical for all boards, RTOS kernels, and development environments. Remember to see the README.html file for information about how to open a serial session to run the example’s console.

The consoleThread() function runs when the RTOS scheduler starts. First it performs some setup tasks. Later it drops into a while loop that runs until the application is halted.

Within the setup portion of the function, the consoleThread() function performs these actions:

  1. Enables the Power Manager for CC32XX boards, because power management is disabled by default on the CC32XX for easier debugging.
  2. Configures Board_GPIO_BUTTON1 to run the gpioButtonFxn() to wake the board in response to a button press.
  3. Calls the POSIX function sem_init() to initialize an unnamed semaphore. The button press posts the semaphore and the while loop later in the consoleThread() function waits on the semaphore.
  4. Initializes the parameters for the UART communication.

Within the while loop, the consoleThread() function performs these actions:

  1. If uartEnabled is false (which happens when the console is closed), waits on the semaphore until the button is pressed.
  2. Opens a UART for the console in blocking read and write mode by setting parameters and then calling UART_open().
  3. Runs the simpleConsole() function, which uses UART_write() and UART_read() to manage the console’s user interface, which accepts the following commands:

    Valid Commands
    --------------
    h: help
    q: quit and shutdown UART
    c: clear the screen
    t: display current temperature
  4. If the “t” command is used, the values of both the temperatureC and temperatureF external variables provided by the temperatureThread are stored locally. A mutex is used to protect these operations. Without the mutex, the temperature thread, which has a higher priority, could interrupt the reading of the variables by the console thread. The result could be that the console thread printed a Celsius value that did not match the Fahrenheit value.
  5. The static itoa() function is used to convert the integer temperature values to strings, and UART_write() is used to output the values to the console.
  6. If the “q” command is used, the simpleConsole() function returns and the UART_close() is called. The Power Manager automatically goes to a lower-power state when the UART is closed. See the TI-RTOS Power Management User’s Guide for more about power states.

temperature.c

This file is identical for all boards, RTOS kernels, and development environments.

The temperatureThread() function runs when the RTOS scheduler starts. First it performs some setup tasks. Later it drops into a while loop that runs until the application is halted. The thread blocks on a semTimer semaphore at the end of the while loop. A timer running at one second posts the semTimer semaphore to unblock the task. A sleep() function could have been used, but a very small amount of drift would have occurred.

Within the setup portion of the function, the temperatureThread() function performs these actions:

  1. Initializes the I2C driver and opens the driver for use.
  2. Initializes the I2C_transaction structure that will be used in the I2C_transfer().
  3. Initializes the timer and semTimer semaphore.

Within the while loop, the temperatureThread() function performs these actions:

  1. If an I2C transfer succeeds, a mutex is locked to make the operations that follow thread safe.
  2. The temperature is extracted from the received data. (For CC32XX boards, the default is to read the temperature from the onboard TMP006 or TMP116 sensor. For all other boards, the example reads the temperature from the TMP116 sensor on the Sensors BoosterPack.)
  3. The temperature is scaled to be accurate in Celsius degrees and is also converted to Fahrenheit. Both values are stored for output.
  4. The mutex lock is released.
  5. If the temperature is higher than 30 C, an alert message is written using GPIO_write(). Otherwise, any previous alert message is cleared.
  6. The while loop blocks on the semTimer semaphore, which will be posted every second by the timer created by the thread. If the UART is closed, this provides enough time for the device to transition to a lower-power state under the control of the Power Manager.

portable.syscfg

This file specifies how SysConfig should be used to set up board-specific items. For example, the GPIO section configures the GPIO input pins, output pins, and LEDs. It creates an array of callback functions for the input pins and sets them to NULL. For each driver, it declares a configuration structure and sets the defaults for the attribute fields in the structure.

When the example is built, this file will be used to generate Board.c and Board.h files, which configure the structures used by the TI Drivers. These structures are similar for most TI Drivers. For example, a configuration data structure called <Driver>_Config is defined. Each driver’s configuration includes a pointer to a function table, a pointer to an object, and a pointer to a set of hardware attribute settings. For more about the driver configuration structures, see the detailed reference information for the TI Driver APIs.

Files for CCS, GCC, and IA

For each supported RTOS, the example contains files that are used to set up a project for the example when you import the example into a development environment. Projects for Code Composer Studio (CCS), IAR Embedded Workbench, and the GNU Compiler Collection (GCC) can be created.

Making Choices for Your Application

When using the SDK to create your own application, you should begin by making the following choices:

Choosing a Development Environment

Choose your preferred development environment. The SimpleLink MCU SDK supports all of the following development environments equally well:

The SDK examples can be imported into any of these environments as described in the Quick Start Guide.

Choosing an RTOS

You may use either the TI-RTOS Kernel or FreeRTOS with SDK applications that use the TI Drivers. Or, you can use the NoRTOS option, which allows you to use the TI Drivers without an underlying operating system (OS).

The TI-RTOS Kernel is a scalable real-time kernel. It is designed to be used by applications that require real-time scheduling and synchronization or real-time instrumentation. It provides preemptive multi-threading, hardware abstraction, real-time analysis, and configuration tools. The Kernel is designed to minimize memory and CPU requirements on the target. The TI-RTOS Kernel was previously called SYS/BIOS. It is described in the TI-RTOS Kernel User’s Guide. This Texas Instruments E2E site has links to training videos and more for the TI-RTOS Kernel.

FreeRTOS is an open-source, real-time operating system kernel for embedded devices. It implements a minimalist set of functions, basic task handling and memory management. If you want to use the native FreeRTOS routines without the abstractions provided by the SDK, see the FreeRTOS website.

NoRTOS enables the use of the TI Drivers without an underlying operating system (OS). Since there is no OS in this scenario, there can only be one main thread. Interrupts can preempt this main thread, but typical “multithreading” is not available.

Choosing Whether to Use POSIX

To enable re-use of code across the TI-RTOS and FreeRTOS kernels, the SDK provides a POSIX layer.

All of the kernel-based driver examples in the SDK are POSIX-based (except the portableNative examples). This allows the same code to be shared by the applications using the TI-RTOS and FreeRTOS kernels. If you want your applications to have this type of portability across kernels, we recommend that you use POSIX.

However, not all kernel features are available in the POSIX interface. If you need any OS-specific feature that is not available in the POSIX implementation, you can use a direct OS call during part of the application or the entire application.

See the TI-POSIX User’s Guide for details about which POSIX APIs are supported.

Choosing a Driver Access Method

You can choose a driver access method to fit the needs of that application. Factors that can be used to determine which method to use include:

You can use a combination of access methods. For example, you can use TI Drivers and Driverlib together. However, be aware that power management and resource allocation differ between access methods. For this reason, resource conflicts can arise. As long as you remain aware of the issues when using multiple access methods, it is possible to use the best features of each one.

TI Drivers

The TI Drivers provide an API interface that is compatible across the supported MCUs. Your applications can use this API with either the TI-RTOS or FreeRTOS kernel (or no RTOS).

In order to provide a cross-MCU compatible set of APIs, some device-specific features are not available in the TI Drivers. Instead, the focus is on ease of migration. The TI Drivers abstract the common features rather than providing feature-completeness.

Driverlib

Driverlib provides very low-level, device-specific APIs that abstract register and peripheral details. They are intended to ease TI Driver implementation and supplement TI Driver functionality for use cases that are too specific for a general driver.

Users should check if the appropriate TI Driver solves their use case before attempting to use driverlib as that requires significantly deeper understanding of the target’s hardware.

Since APIs and features are closely tied to the hardware, API compatibility across devices is not guaranteed.

This API interface provides an abstracted way to configure hardware registers. This access method acts as a translation layer for setting/resetting a register bit or set of register bits.

Register Access

This access method configures hardware registers directly by setting/resetting specific bit combinations. This method provides 100% feature coverage with no abstraction at all.

When using this method, refer to the Device Family Technical Reference Manual and the Device Datasheet. Set and reset specific bits in the registers using the definitions in the device header files included with the SDK.

TI Drivers

The SDK includes drivers for a number of peripherals. These drivers are provided in the <SDK_INSTALL_DIR>/source/ti/drivers directory. The driver examples show how to use these drivers.

Some of the drivers are available only for certain target families. Some examples of drivers supported on several target families include GPIO, I2C, Power, SPI, UART, and Watchdog. There are many more. See the detailed reference information for the complete list of TI Drivers for your target family along with full information about the APIs and configuration structures.

Driver Framework

TI drivers have a common framework for configuration and a set of APIs that all drivers implement. Applications interface with a TI driver using a top-level driver interface. This interface is configured via a set of data structures that specify one or more specific lower-level driver implementations. TI Drivers can be configured manually or using the SysConfig tool.

TI drivers can be split into two categories: instance-based drivers and singleton drivers.

Singleton drivers such as GPIO and Power only have a single instance for the entire application. They only need to be initialized with Driver_init() once and can then be called without the need for a handle.

Instance based drivers support multiple instances of the same driver coexisting in the same application. These might each target their own hardware peripheral on the device such as UART or allow time-division multiplexed access to a single hardware peripheral by the driver. Such instance-based drivers support all the standard APIs given below.

Drivers Porting Layer (DPL)

The Drivers Porting Layer allows the TI drivers to work with any supported RTOS kernel.

The TI Drivers use DPL APIs provided by various RTOS Kernels for clock, interrupt, mutex, semaphore, and other services. However, the DPL abstracts the RTOS kernel functionality used by the drivers so that the application is not dependent on any particular RTOS.

Board Files

Unless you are using SysConfig, TI Driver examples contain a board-specific C file (and its companion header file). The filenames are board.c and board.h, where board is the name of the board. All the TI Driver examples for a specific board use the same board files. These files are considered part of the example application, and you can modify them as needed.

The board files perform board-specific configuration of the drivers provided by the SDK. For example, they typically configure the GPIO ports and pins and configure use of the board LEDs.

SysConfig

The SysConfig tool makes it easy to configure components like TI Drivers and device-specific components (such as the networking stack, EasyLink, and WiFi).

For this release, SysConfig is used in the majority of the examples and is the recommended mechanism for configuring TI Drivers and device-specific components. Please note that SysConfig is not required though. A project can still use the previous board file approach. Please refer to the rtos/<board>/drivers/empty_legacy example to see this approach.

You can choose to use SysConfig no matter what your choice is for the following:

This section focuses on configuring TI Drivers, since all devices support TI Drivers using a common configuration framework. For TI Drivers, the SysConfig tool generates source files–ti_drivers_config.c and ti_drivers_config.h–to be compiled and linked with the application during builds. Additional components, such as networking stacks, may be configurable using SysConfig for certain targets, and will cause additional source files to be generated. See the device-specific documentation for information about any additional components that SysConfig can be used to configure.

As shown above, SysConfig provides descriptions and links to access reference documentation for the drivers and other modules you can configure.

Besides the ease-of-use provided by SysConfig, the tool resolves conflicts on the fly. This ensures that you create a valid pin and TI Driver configuration. In addition, you can view diagrams of the pin configuration as you make changes.

SysConfig settings are stored in a file called <project>.syscfg. Its output files are stored in a syscfg folder within the build folder. For example, a “portableNative” application in CCS that uses TI-RTOS would contain the following files when built. Note that the Generated Sources files are a subset of the build folder syscfg directory. This was done to make finding and examining the generated source files easier outside of the SysConfig tool.

You open (with a simple double click) the <project>.syscfg file in SysConfig and modify it as needed. You can view the ti_drivers_config.c and ti_drivers_config.h file contents within SysConfig and see how configuration changes affect the generated code.

When you save the portableNative.syscfg file and build the project, the following actions occur:

  1. The ti_drivers_config.c and ti_drivers_config.h files* are generated from the <project>.syscfg file.
  2. The ti_drivers_config.c and ti_drivers_config.h files* are compiled to create a ti_drivers_config.o object file.
  3. The ti_drivers_config.o object file* is linked with the rest of the libraries to create the application.

* and any additional device-specific component files

Some users may choose to use SysConfig initially to generate the ti_drivers_config.c, ti_drivers_config.h (and other component configuration files) and then exclude the *.syscfg file from the build so that manual changes made to these files are not overwritten. Additionally user may want to add their custom board setup into SysConfig.

Additional information for SysConfig can be found on the System configuration tool product page.

Debugging Output

The Display middle-ware driver is the recommended strategy for providing “printf” style debugging. It is RTOS independent. The Display module supports sending debug output by different methods (for example, UART, IDE Console, and LCD). Application writers can also provide their own custom Display implementation.

Many of the TI Driver examples use the Display module to send debugging information out a UART or LCD. The main API is the Display_printf() functions. This function is very similar to the standard printf() function. The configuration for the Display module is in the board.c file. For details about the Display module functionality, refer to the reference documentation .

The Runtime Object View (ROV) tool is available for examining the runtime state of TI-RTOS and FreeRTOS Kernel objects and various important statistics, such as stack usage and CPU load. The ROV tool can be run standalone or within CCS 7.1 or later. For details on using the ROV tool, see “Runtime Object View (ROV)”.

Note: The default printf() API is not recommended for use with SDK applications due to the impact on real-time performance and code footprint size.

Note: The System module in the TI-RTOS Kernel is available for applications that use the TI-RTOS native APIs (instead of the POSIX APIs). The System module functionality is unchanged from previous versions and will continue to be supported. The TI-RTOS “release” configuration uses SysCallback as the System support proxy to minimize footprint and performance impacts. The TI-RTOS “debug” configuration uses SysMin as the System support proxy, which slightly impacts footprint and performance but allows viewing of the System_printf debug output in the RTOS Object Viewer (ROV). Refer to the Configuration with TI-RTOS section for more details about the release and debug versions.

TI-RTOS Kernel

The SDK includes the TI-RTOS Kernel.

TI-RTOS provides a rich set of system-level embedded software, including multi-tasking, power management, device drivers, and instrumentation. Through the use of industry-standard POSIX pthread APIs, TI-RTOS enables other kernels (schedulers) to be integrated. TI-RTOS is delivered in the SDK in a consistent manner across the various SimpleLink SDKs.

The TI-RTOS Kernel is a scalable real-time kernel. The kernel is designed to be used by applications that require real-time scheduling and synchronization or real-time instrumentation. It provides preemptive multi-threading, hardware abstraction, real-time analysis, and configuration tools. The Kernel is designed to minimize memory and CPU requirements on the target. This kernel was previously called SYS/BIOS.

The TI-RTOS Kernel is described in the TI-RTOS Kernel User’s Guide. Additionally, detailed reference information is provided for all the TI-RTOS Kernel APIs.

Organization of TI-RTOS Kernel Modules

The TI-RTOS Kernel is organized into sets of “packages,” each of which delivers a subset of the product’s functionality. The package names reflect the physical layout of the package within the SDK installation. For example, the ti.sysbios.knl package files can be found in the <SDK_INSTALL_DIR>/kernel/tirtos7/packages/ti/sysbios/knl directory.

Each package provides one or more modules. Each module, in turn, provides APIs for working with that module. APIs have function names of the form Module_actionDescription(). For example, Task_setPri() sets the priority of a Task thread.

Using TI-RTOS Kernel Modules

In order to use a module, your application must include the standard TI-RTOS Kernel header files and the header file for that particular module. For example, include these header files to enable use of the Task module APIs:

#include <ti/sysbios/BIOS.h> /* initializes TI-RTOS Kernel */
#include <ti/sysbios/knl/Task.h> /* initializes Task module */

Configuration with TI-RTOS

The TI-RTOS Kernel is configured through the SysConfig tool. This is used to enable and disable modules as well as configure device-specific settings. SysConfig will generate two files, and contribute to one more as part of the application project:

  1. ti_sysbios_config.c: This file contains a small amount of generated code that initialises the modules that have been included and enables any hook functions that have been configured. It also contains the source code that is to be compiled for TI-RTOS. This is done by inserting preprocessor statements to #include the C files of all selected modules.

  2. ti_sysbios_config.h: This file contains all the preprocessor defines and constants used to configure the Kernel modules when they are compiled.

  3. ti_utils_build_linker.cmd.genlibs: The assembly files used by the Kernel are precompiled into libraries. This is because we cannot #include assembly files safely. These libraries are then added to the linker invocation through an entry in the ti_utils_build_linker.cmd.genlibs file.

Changing the TI-RTOS Configuration

As with all SysConfig modules, you can either edit their configuration via the SysConfig GUI or by editing the .syscfg file directly in a text editor. A list of configurable module settings to use when editing the .syscfg file directly can be found in the TI-RTOS SysConfig Documentation

Since the Kernel configuration is stored in a project’s .syscfg file, each project has its own Kernel configuration. Changing one project’s configuration will not impact another project’s configuration.

Why might you want to change the default Kernel configuration?

FreeRTOS Kernel

FreeRTOS is an open-source, real-time operating system kernel for embedded devices. It implements a minimalist set of functions, basic task handling and memory management.

The FreeRTOS kernel has the following characteristics:

The SDK provides support for FreeRTOS but does not install FreeRTOS. You can download the FreeRTOS source files via the FreeRTOS website. Refer to the SimpleLink Core SDK Release Notes for information about supported versions of FreeRTOS. The SDK does install the Driver Porting Layer (DPL) and POSIX abstractions to allow FreeRTOS to be used with the other SDK components.

FreeRTOS does not provide drivers for external hardware or access to a file system. In addition, the network communication stack provided by FreeRTOS is not used by the SimpleLink SDK. These capabilities are all managed by the TI Driver layer.

In the SDK, all FreeRTOS application routines are abstracted using the following:

You can find example FreeRTOS projects that use the SDK in the examples/rtos/ folder for all supported IDEs.

No native FreeRTOS examples are provided in this SDK. If you want to use the native FreeRTOS routines without the abstractions provided by the SDK, documentation is provided on the www.freertos.org website.

Using FreeRTOS with CCS

In order to use FreeRTOS within CCS, you must specify the location of the FreeRTOS installation. To do this, follow these steps:

  1. In CCS, choose Window->Preferences from the menus.
  2. Select the General->Workspace->Linked Resource category.
  3. Click New and add a link with the following settings:
    • Name: FREERTOS_INSTALL_DIR
    • Value: the location of you FreeRTOS installation

These steps only need to be performed once per CCS workspace that you use.

The FreeRTOS Kernel is configured through the SysConfig tool. This is used to enable and disable modules as well as configure device-specific settings. SysConfig will generate three files, and contribute to one more as part of the application project:

  1. ti_freertos_config.c: Contains the source code that is to be compiled for FreeRTOS. This is done by inserting preprocessor statements to #include the C files of all selected modules. The file also contains several hooks used to enable and disable FreeRTOS features such as Queue registration in TI code.

  2. ti_freertos_portable_config.c: This file contains all C code for the application’s architecture and toolchain specific port.

  3. ti_utils_build_linker.cmd.genlibs: The ports of some architecture and toolchain combinations use assembly files. We compile these into prebuilt libraries. This is because we cannot #include assembly files safely. These libraries are then added to the linker invocation through an entry in the ti_utils_build_linker.cmd.genlibs file.

  4. FreeRTOSConfig.h: This is the standard FreeRTOS configuration header file that your application will typically #include. The entries in this file will change depending on the device and settings selected in SysConfig. You no longer need to edit FreeRTOSConfig.h yourself. SysConfig will maintain it for you. If you would like to change a FreeRTOS feature or setting that is not exposed in SysConfig, you can remove this file from SysConfig and maintain it yourself.

Changing the FreeRTOS Configuration

As with all SysConfig modules, you can either edit their configuration via the SysConfig GUI or by editing the .syscfg file directly in a text editor.

Since the FreeRTOS configuration is stored in a project’s .syscfg file, each project has its own FreeRTOS configuration. Changing one project’s configuration will not impact another project’s configuration.

Why might you want to change the default FreeRTOS configuration?

NoRTOS

SimpleLink contains support for using the TI Drivers without an underlying operating system (OS). This support is called “nortos”. Examples are provided under the “nortos” subdirectory in the product tree.

Since there is no OS in this usage scenario, there can only be one main thread. As with most any system, interrupts will preempt this main thread, but typical “multithreading” is not available.

Throughout this chapter the term RTOS (Real Time Operating System) will be used to refer to either TI-RTOS, FreeRTOS, and counterintuitively, even “nortos”.

DPL

While SimpleLink applications use the TI Drivers, these drivers use a set of APIs that collectively are named DPL (Driver Porting Layer).

In addition to “nortos”, there are DPL implementations for the supported RTOSes of TI-RTOS and FreeRTOS (For those familiar with TI-RTOS, the TI-RTOS DPL implementation is a thin layer on top of TI-RTOS Kernel APIs).

Applications should not explicitly make use of DPL APIs, as they are intended to be used only by the TI drivers. The DPL APIs are available for TI Driver usage as a set of libraries that are shipped with the SimpleLink SDK product. The makefiles and IDE projects (CCS & IAR) for the RTOS model in use point to the necessary RTOS-specific DPL library.

NoRTOS Threading

The application will consist of just a main thread, which is instantiated by way of the standard C main() function. There is no capability for the main thread to create other threads, including Hwi-like & Swi-like threads, since DPL is not intended for application use, but the application can plug an ISR into the vector table. The TI Drivers will themselves create Hwi-like and Swi-like threads via the DPL APIs.

When the main thread calls into certain TI Driver APIs, some “blocking” will be achieved via the SemaphoreP_pend API in the DPL library. In the “nortos” environment, this “blocking” is implemented by way of a spin loop that can enter low-power modes available to the particular device. This spin loop is similar to an RTOS’s idle loop.

Interrupt handling

In cases where no suitable high-level driver is available in the tidrivers package, it is necessary to implement a custom solution based on the low-level device support APIs. This usually involves hardware interrupt handling and requires to use the low-level device APIs. An example for a custom interrupt handler on a CC1310 is shown in the following code snippet. Other SimpleLink MCUs provide similar APIs.

#include <ti/devices/cc13x0/driverlib/timer.h>

void mainThread()
{
    /* ... */

    /* Register a custom interrupt handler using the
     * low-level device APIs */
    IntRegister(INT_GPT0A, &timerInterruptHandler);

    /* ... */
}

void timerInterruptHandler()
{
    /* Handle the interrupt */
}

NoRTOS Power Management

Power management is handled by the platform-specific power driver in the tidrivers package. The power driver provides a function Power_idleFunc() which tells the MCU to enter the lowest possible power mode and to wait for an hardware interrupt event. The lowest possible power mode depends on the peripherals that are currently active and the MCU architecture. More information about the power driver can be found in the tidrivers API documentation.

Whenever a NoRTOS application performs a blocking call into a peripheral driver, Power_idleFunc() is executed which puts the application into a low-power mode until the desired interrupt event occurs. The following code snippet shows an example for a blocking call into the UART driver:

/* == Example for UART read in blocking mode == */

#include <ti/drivers/Power.h>
#include <ti/drivers/UART.h>

/* System is in RUNNING state */

/* Read 32 bytes from the UART in blocking mode.
 * The MCU will enter a low-power mode while waiting
 * for data. */
int rxBytes = UART_read(handle, rxBuf, 32);

/* System is back in RUNNING state */

When using the peripheral drivers in callback mode, no automatic blocking is performed. This is often desired when implementing an event-driven application. In that case, the application needs to block explicitly by calling Power_idleFunc(). The following code snippets shows an example:

/* == Example for UART read in callback mode == */

#include <ti/drivers/Power.h>
#include <ti/drivers/UART.h>

volatile bool dataReceived = false;

void mainThread()
{
    /* System is in RUNNING state */

    /*
     * Read 32 bytes from the UART in callback mode.
     * The function returns immediately.
     */
    UART_read(handle, rxBuf, 32);

    /*
     * In parallel to waiting for data, more work
     * can be done here...
     */

     /*
      * Go to sleep mode if necessary and wait until the
      * data transfer is finished. Accessing 'dataReceived'
      * and calling 'Power_idleFunc()' needs to be atomic.
      */
     enterCriticalSection();
     while (dataReceived == false)
     {
        Power_idleFunc();
        leaveCriticalSection();
        /* Let 'readCallback()' execute... */
        enterCriticalSection();
     }
     dataReceived = false;
     leaveCriticalSection();
}

void readCallback(UART_Handle handle, void *rxBuf, size_t size)
{
    /* Notify the main thread that data has been received */
    dataReceived = true;
}

void enterCriticalSection()
{
    /* Disable hardware interrupts. Platform-specific. */
}

void leaveCriticalSection()
{
    /* Re-enable hardware interrupts. Platform-specific. */
}

The function Power_idleFunc() returns whenever a hardware interrupt has occurred. Depending on the driver implementation, this might happen several times during the waiting period. Thus, a flag dataReceived signals the main thread, that the blocking condition is fulfilled and allows the program to resume.

If an application simply wants to wait for certain time, then it can use the functions sleep() and usleep() offered by the POSIX API:

#include <posix/unistd.h>

/* Block and enter a sleep mode for 47 seconds */
sleep(47);

/* Block and enter a sleep mode for 1701 microseconds */
usleep(1701);

Entering main()

Regardless of the underlying RTOS, main() needs to perform some initial setup and initialization for the application and drivers. In order to use a common initialization scheme for the TI Drivers, main() needs to be running in a similar model for all the RTOSes. TI-RTOS and FreeRTOS ensure that main() is entered with interrupts disabled, which is important for the subsequent Board_init() function to run properly. TI-RTOS applications must finish up main() by calling BIOS_start(), during which interrupts will be enabled. Similarly, FreeRTOS applications must call vTaskStartScheduler().

In the “nortos” model, the application would typically inherit a boot file from the compiler’s RTS library or the devices driverlib installation, and this boot file would eventually call main() with interrupts enabled. Since SimpleLink needs to do some initialization with interrupts disabled (that is by calling the function Board_init() as stated above), the “nortos” DPL library contains a set of functions that achieve this model when called from the appropriate places in main(). These functions are prefixed with the module name “NoRTOS”. One of these functions is the “nortos” counterpart to BIOS_start() and vTaskStartScheduler(), named NoRTOS_start(), and should be called after all application setup and initialization in main() but just prior to launching the main application functionality (but, unlike BIOS_start() and vTaskStartScheduler(), NoRTOS_start() does indeed return back to the caller).

The other functions in the NoRTOS module should be called as one of the first statements in main(), and before any call to Board_init(). See the example listing of main() later in this chapter for a suggested reference implementation for calling these NoRTOS module functions.

Software Interrupts (SwiP module)

Note: Not available for FreeRTOS-based TI Drivers.

A “Software Interrupt” (Swi) is a commonly-used paradigm that is available in many general OSes and RTOSes. It is a “threading level” that sits between HW interrupts and the main thread (or threads for multithread-capable OSes).

A HW ISR can post Swis and the Swi threads will run to completion only after all HW ISRs have been processed and before the program control returns to the main thread. A Swi runs with interrupts enabled, so they can be preempted by HW interrupts.

The DPL APIs contain a module that implements Software Interrupts (SwiP). Note that this SwiP module is not available for the FreeRTOS environment. Since the “nortos” model doesn’t have a HW interrupt dispatcher, a different mechanism must be used to service Swis. The SwiP module in DPL uses an interrupt that is configured at the lowest available priority to trigger the SwiP dispatcher. This interrupt is triggered by the SwiP module when the SwiP scheduler needs to run. Since the interrupt is configured at the lowest available priority, and since no other interrupt can share this priority in order to maintain the appropriate Hwi/Swi threading levels, the HwiP module will prevent any creation of another interrupt with the lowest available priority. As a result, the lowest available priority for use by TI Drivers when they create interrupts is one priority level higher than the lowest available priority in HW.

All supported devices contain an interrupt vector named PENDSV. The HwiP module contains a variable which is initialized to the PENDSV interrupt. This interrupt is not used by any HW so it is available for SW use. SwiP will use this interrupt when creating the HwiP instance to service its dispatcher. If your application needs to use the PENDSV interrupt for a different purpose, it can override the default setting by assigning a different value to NoRTOS_config.swiIntNum (see below). This should be done early in main(), before calling any TI Driver functionality (such as Board_init()).

DPL Configuration

Even though the DPL library is for use by TI Drivers and not by the application, there are some exposed elements of DPL in the “nortos” model that the application writer can modify. While these elements are assigned default values that should suffice for most all applications, there will be times when it’s easier to reconfigure these elements instead of accommodating their default values and changing the application to suit.

The NoRTOS module contains two functions to achieve the DPL configuration settings.

Between these calls, the application can set any of the NoRTOS_Config structure members to the value that it wants, and the NoRTOS_setConfig() call will configure DPL with these settings. The configuration elements of this structure are detailed here.

NoRTOS_Config.clockTickPeriod

The ClockP DPL module contains a global variable named ClockP_tickPeriod. It contains the “period” of the ClockP tick in microseconds. Its value is device-dependent. The NoRTOS_Config.clockTickPeriod element is used as a “proxy” for the ClockP_tickPeriod variable. The application can change this, but it should be changed before any calls to into the TI Drivers. (Doing this early in main() is recommended.)

NoRTOS_Config.swiIntNum

The HwiP module contains a variable for an interrupt that is used by the SwiP module that is initialized to the PENDSV interrupt value (as mentioned above). SwiP will post this interrupt when its dispatcher needs to run. The NoRTOS_Config.swiIntNum element can be set to a different value to change the HwiP variable. If so desired, the application should make this change before any calls into the TI Drivers. (Doing this early in main() is recommended.)

NoRTOS_Config.idleCallback

NULL by default, NoRTOS_Config.idleCallback is intended to be set by the application to enable low-power modes. If non-NULL, it is used as a function pointer that is called whenever a SemaphoreP_pend() is called for a semaphore that is not available (hence the system is “going idle”). It will be repeatedly called as long as the semaphore is not available and the timeout has not expired. The intended purpose is for the function Power_idleFunc() to be assigned so that the device can enter low-power mode while waiting for the semaphore to be posted.

The application should set this to Power_idleFunc() if low-power modes are desired, and should set it early in main() before any call into the TI Drivers.

Clocks

CC13XX/CC26XX/CC23XX

The ClockP module in the “nortos” DPL for CC13XX/CC26XX uses the RTC timer, which can remain active (running) during low-power modes. Similarly to the TI-RTOS Kernel Clock module on these devices, the ClockP module uses “dynamic tick” mode, which does not generate an interrupt for every Clock “tick” but instead dynamically reprograms the compare register of the timer to generate an interrupt for only the next scheduled ClockP tick event. Unlike TI-RTOS’s Clock module, a periodic mode is not available.

CC32XX

The ClockP module in the “nortos” DPL for CC32XX uses the SysTick timer. This CC32XX ClockP module supports “periodic tick” mode, where an interrupt occurs for every periodic tick regardless of any active ClockP object instance expiring during that tick.

The SysTick timer does not remain active (running) during low-power modes. In order to allow low-power modes while still maintaining a valid time base, the CC32XX ClockP module utilizes a “wakeup” timer that remains active while in the low-power mode, and is scheduled to wake up the device based on the next scheduled ClockP instance expiration time. Once woken up, the ClockP module updates its tick count with the number of “missed” ticks, and resumes operation of the SysTick timer.

Startup Files

A “nortos” application should use the startup files and linker command files that come packaged with the “nortos” TI Driver examples. These files have been tailored for the devices and OS in use. The startup files contain the initialized vector table, which can be modified to suit the application needs. There is a different startup file and linker command file for each supported compiler (CCS, GCC, IAR).

The SimpleLink examples come packaged with the necessary startup and linker command files from the particular driverlib that is contained in the SDK.

Shutdown

Since there is no RTOS involvement, the application can terminate using the usual “C” mechanisms - either calling exit() directly or returning from the main() function.

Example main() for NoRTOS

    /*
     *  ======== main_nortos.c ========
     */
    #include <stdint.h>

    #include <NoRTOS.h>
    #include <ti/drivers/Power.h>

    /* Example/Board Header files */
    #include "Board.h"

    extern void *mainThread(void *arg0);

    /*
     *  ======== main ========
     */
    int main(void)
    {
        NoRTOS_Config config;

        /* Set config parameters for NoRTOS */
        NoRTOS_getConfig(&config);
        config.idleCallback = Power_idleFunc;
        NoRTOS_setConfig(&config);

        /* Call driver init functions */
        Board_init();

        /* Start NoRTOS */
        NoRTOS_start();

        /* Call mainThread function */
        mainThread(NULL);

        while (1);
    }

Third-Party Components

The following third-party components can be used with the SDK.

CMSIS

The Cortex Microcontroller Software Interface Standard (CMSIS) is a common hardware abstraction layer for the Cortex-M processor series. It defines generic tool interfaces.

The CMSIS provides consistent device support and simple software interfaces to the processor and the peripherals. This simplifies software re-use, reducing the learning curve for microcontroller developers, and reducing the time to market for new devices. The CMSIS was created to enable software components from multiple middleware vendors to be combined.

There are several components in the CMSIS specifications, many of which are supported by the SDK and its tools infrastructure. Detailed information on these CMSIS components can be found at http://www.keil.com/pack/doc/CMSIS/General/html/index.html

The ARMCLANG and CMSIS-SVD components are not part of the SDK. They are shipped by Keil.

The benefits of the CMSIS-Core and CMSIS include:

CMSIS Header Files

The SDK includes specific releases of CMSIS core files that have been tested and validated with the components the SDK. The CMSIS files include the following:

FatFs

FatFs is an open-source FAT file system module intended for use in embedded systems. The API used by your applications is generic to all FatFs implementations, and is described and documented at http://elm-chan.org/fsw/ff/00index_e.html. Details about the FatFs API are not discussed here. Instead, this section gives a high-level explanation about how it is integrated with the TI Drivers.

In order to use FatFs in SDK applications, use the files in the <SDK Install>/source/third_party/fatfs directory.

The FatFs drivers provided by the SDK enable you to store data on removable storage media, such as Secure Digital (SD) cards. Such storage may be a convenient way to transfer data between embedded devices and conventional PC workstations.

FatFs and TI Drivers

The SDK provides a FatFs module and extends this module by supplying “FatFs” drivers that link into the FatFs implementation. The FatFs drivers are aware of the multi-threaded environment and protect themselves with OS primitives.

From the start of this data flow to the end, the components involved behave as follows:

Using FatFs

The subsections that follow show how to configure FatFs, how to prepare the FatFs drivers for use in your application, and how to open files. For details about performing other file-based actions once you have opened a file, see the FatFs APIs described on http://elm-chan.org/fsw/ff/00index_e.html in the “Application Interface” section or the standard C I/O functions.

The FatSD and FatSDraw examples use FatFs with the SDFatFS driver.

Defining Drive Numbers

Calls to the open() functions of individual FatFs drivers - for example, SDFatFS_open() - require a drive number argument. Calls to the C I/O fopen() function and the FatFs APIs also use the drive number in the string that specifies the file path. The following C code defines driver numbers to be used in such functions:

/* Drive number used for FatFs */
#define SD_DRIVE_NUM 0

Here are some statements from the FatSD example that use these drive number definitions. Note that STR(SD_DRIVE_NUM) uses a MACRO that expands SD_DRIVE_NUMto 0.

SDFatFS_Handle sdfatfsHandle;
FILE *src;
const char inputfile[] = "fat:"STR(SD_DRIVE_NUM)":input.txt";

/* Mount and register the SD Card */
sdfatfsHandle = SDFatFS_open(Board_SDFatFS0, SD_DRIVE_NUM);

/* Open the source file */
src = fopen(inputfile, "r");

Preparing FatFs Drivers

In order to use a FatFs driver in an application, you must do the following:

See the SDFatFS Driver for details about the FatFs driver APIs.

Opening Files Using FatFs APIs

Details on the FatFs APIs can be found at http://elm-chan.org/fsw/ff/00index_e.html in the “Application Interface” section.

The drive number needs to be included as a prefix in the filename string when you call f_open() to open a file. The drive number used in this string needs to match the drive number used to open the FatFs driver. For example:

res = f_open(&fsrc, "SD_DRIVE_NUM:source.dat", FA_OPEN_EXISTING | FA_READ);

A number of other FatFs APIs require a path string that should include the drive number. For example, f_opendir(), f_mkdir(), f_unlink(), and f_chmod().

Although FatFs supports up to 10 (0-9) drive numbers, the diskIO function table supports only up to 4 (0-3) drives. You can modify this default by changing the definition of FF_VOLUMES in the ffconf.h file in the FatFs module. You will then need to rebuild the TI Drivers and FatFs modules.

It is important to use either the FatFs APIs or the C I/O APIs for file operations. Mixing the APIs in the same application can have unforeseen consequences.

Opening Files Using C I/O APIs

The C input/output runtime implementation for FatFs works similarly to the FatFs API. However, you must add the file name prefix configured for the FatFs module (“fat” by default) and the logical drive number as prefixes to the filename. The file name prefix is extracted from the filename before it gets passed to the FatFs API.

In this example, the default file name prefix is used and the drive number is 0:

fopen("fat:0:input.txt", "r");

It is important to use either the FatFs APIs or the C I/O APIs for file operations. Mixing the APIs in the same application can have unforeseen consequences.

Cautionary Notes

Number of Volumes Mounted: The FatFs implementation allows up to four unique volumes (or drives) to be registered and mounted.

FatFs actions and Low-priority Tasks: FatFs drivers perform data block transfers to and from physical media. Depending on the FatFs driver, writing to and reading from the disk could prevent lower-priority tasks from running during that time. If the FatFs driver blocks for the entire transfer time, only higher-priority TI-RTOS Kernel Tasks, Swis or Hwis can interrupt the Task making FatFs calls. In such cases, the application developer should consider how often and how much data needs to be read from or written to the media.

Buffering: RAM Usage vs. Disk Operations: By default the FatFs module keeps a complete sector buffered for each opened file. While this requires additional RAM, it helps mitigate frequent disk operations when operating on more than one file simultaneously.

Use of long file names or XFAT: The FatFs libraries are configured for use without Long File Name (VFAT) or XFAT support. If you chose to reconfigure and build FatFs to include these features, you may be required to license the appropriate patents from Microsoft. For more information, refer to: http://elm-chan.org/fsw/ff/en/appnote.html

SPI Flash File System (SPIFFS)

SPIFFS is an open source file system authored by Peter Andersson for use on SPI NOR flash devices in embedded systems. It was designed for low RAM usage on embedded targets with little RAM (or possibly no heap).

SPIFFS provides a set of POSIX-like APIs, for which this SDK provides API reference documentation. Further details about the SPIFFS API, the design specification, and file system customization can be found in the open source github project.

This document provides a high-level explanation about how SPIFFS is integrated with TI Drivers, in particular the driver for non-volatile storage (NVS).

SPIFFS is provided as both source and pre-built libraries in <SDK_INSTALL_DIR>/source/third_party/spiffs.

SPIFFS and TI Drivers

The SDK features a SPIFFSNVS module (for certain targets), which provides the interface functions required by SPIFFS to read, write, and erase flash memory. These functions are implemented using the TI NVS driver. By leveraging the NVS driver, application code using SPIFFS is not tied to a physical memory type (like SPI flash). Instead the NVS driver abstracts the physical memory interface so SPIFFS application code can run on either SPI flash or on a device’s internal flash memory by changing the NVS driver instance used.

SPIFFS operates within the memory region that has been allocated for the NVS driver instance. The SPIFFSNVS module and NVS drivers are aware of the multi-threaded environment and protect themselves with OS primitives.

As shown in the figure above, the components involved in the data flow are as follows:

Using SPIFFS

The subsections that follow show how to add SPIFFS to your application and how to open files. For details about performing other file-based actions after you have opened a file, see the SPIFFS API documentation.

The spiffsinternal and spiffsexternal examples use SPIFFS on internal flash and external flash memory (respectively) via NVS drivers.

SPIFFS Runtime Configuration

To mount the file system, the following configuration parameters and RAM must be provided at runtime:

(All sizes are in bytes.)

Because NVS drivers serve as the interface to the physical memory, we can rely on them to provide the physical block size and the amount of memory allocated. These values are set in the driver configuration portion of the application board files.

The logical block size is customizable by the application. It must be an integer multiple of the physical block size as shown in the following equation:

logical_block_size = n * physical_block_size

The logical page size is also customizable by the application. The logical block size must also be an integer multiple of the logical page size:

logical_block_size = i * logical_page_size

We recommend that you set both the logical block size and the logical page size equal to the physical page size when you are starting development with SPIFFS. These multiples can be changed later to optimize the file system for your application.

A statically allocated RAM work buffer must be provided. This buffer must be 2 * logical_page_size in length.

A statically allocated RAM file descriptor cache must also be provided. Typically, file descriptors are 44 bytes in size. The cache must be large enough to store as many file descriptors as desired for the application. We suggest that you begin with enough space for 4 file descriptors and later optimize this value for your application.

Finally, SPIFFS needs a read/write cache. Start with a (2 * logical_page_size) cache; this can be increased or reduced later.

For example, suppose you are using SPIFFS with an NVS driver instance that has 128 KB of memory and the physical block size is 4096 bytes.

You could set the logical block size to 8192 bytes, which results in 16 logical blocks in memory. SPIFFS reserves 2 free logical blocks to use for compaction. So, in this case you would have only 14 logical blocks worth of space available for storage (16 KB would be kept unused).