CC13xx Driver Library
Introduction

Introduction

The CC13xx Driver Library from Texas Instruments®  (also referred to as "DriverLib") is a set of low level drivers for accessing the registers found on the CC13xx family of ARM® Cortex™-M based devices. The DriverLib functions are grouped in APIs based on either specific on-chip peripheral module (e.g. SPI, UART, etc ) or more general functionality (e.g. system control, oscillator settings, etc). Each API uses a unique pre-fix for all the DriverLib functions in that API to indicate which module or type of functionality being accessed (with a few exceptions).

A few things to note about DriverLib:

  • The functions are written entirely in C language except where absolutely not possible.
  • The functions are not necessarily the most efficient way to perform a given task from a code size and/or execution speed point of view. Some functions, typically used for configuration/initialization, prioritize usability and might perform a few run-time checks which are not essential for a given application. However, DriverLib functions are relatively small thus optimization potential of individual functions is limited.
  • The functions do not always support the full capabilities of the hardware. Some of the peripherals provide complex capabilities which cannot be used by the DriverLib.
  • The APIs have a means of removing all error-checking code. Because the error checking is usually useful only during initial program development, it can be removed to significantly reduce code size and increase speed.

For most applications the drivers can be used as is. But in some cases the drivers must be enhanced or rewritten to meet the functionality, memory, or processing requirements of the application. If so, the existing driver can be used as a reference on how to operate the peripheral.

Source Code Overview

A brief overview of the organization of the driver library source code follows:

driverlib/

  • This directory contains the source code for the drivers. The API of each module consists of a .c file and a .h file.

inc/

  • This directory contains the header files used for the direct register access programming model. The hw_*.h header files, one per peripheral, describe all the registers and the bit fields within those registers for each peripheral. These header files are used by the drivers to directly access a peripheral, and can be used by application code to bypass the driver library API if necessary.

Currently, DriverLib source code supports the following tools:

  • CCS
  • IAR
  • GCC
  • Keil

Pre-compiled Driver Library

Besides the source code for the driver library, Texas Instruments also provides pre-compiled libraries of the driver library. These libraries are found in driverlib/bin/.

Currently, pre-compiled libraries are provided for the following tools:

  • CCS
  • IAR
  • GCC
  • Keil

Programming Model

DriverLib provides support for two programming models:

  • Direct register access model: Access registers and bit fields directly.
  • Software driver model: Use the provided APIs to indirectly access registers and bit fields.

Each model can be used independently or combined, based on the needs of the application or the programming environment desired by the developer.

Each programming model has advantages and disadvantages. Use of the direct register access model generally results in smaller and more efficient code than using the software driver model. However, the direct register access model requires detailed knowledge of the operation of each register and bit field, as well as their interactions and any sequencing required for proper operation of the peripheral; the software driver model insulates the developer from these details, thus generally requiring less time to develop applications.

Direct Register Access Model

In the direct register access model, the peripherals are programmed by the application by writing values directly into the registers in the peripheral. A set of defines, that simplify this process, is provided.

These defines are located in the inc/ directory and there is a single hw_<module>.h header file for each peripheral type. For example, the defines for SSI are located in the hw_ssi.h header file.

The defines used by the direct register access model follow a naming convention that makes it easier to know how to use a particular macro. The rules are as follows:

  • All register name macros start with the module name (for example, SSI for the SSI module) and are followed by the name of the register as it appears in the data sheet.
  • The register defines are offset values relative to the base address of a peripheral instance. If an offset is used, this will be identified in the register name using _O_ (for example, CR0 register in the data sheet results in SSI_O_CR0). The base address of each peripheral is defined in the memory map header file located in the inc/ directory, with the name hw_memmap.h.
  • All register defines for a given peripheral are listed in the first section of the corresponding header file (for example, in the first section of the hw_ssi.h file for the SSI registers).
  • All register bit fields start with the module name, followed by the register name, and then followed by the bit field name as it appears in the data sheet. For example, the SCR bit field in the CR0 register in the SSI module is identified by SSI_CR0_SCR.
  • Defines that end in _M represent the mask for a bit field in a register.
  • Defines that end in _S represent the number of bits to shift a value in order to align it with a bit field. These values match the macro with the same base name but ending with _M.
  • Defines that end in _BITN have the same value as _S but are only defined for single-bit bit fields thus representing the bit number. These defines should be used for bit band operations to ensure that only single-bit bit fields are accessed.
  • Defines that end in _W represent the width of the bit field.
  • If a bit field has enumerated values the enumeration names are appended to the name of the bit field define. For example, the SSI_CR0_DSS bit field has a set of enumerations that specify the allowed values of this bit field pre-shifted to the correct bit positions. E.g. the enumeration "7_BIT" can be set using the define SSI_CR0_DSS_7_BIT. This improves readability and also helps the programmer select valid values for specific bit fields.

A set of macros is provided in hw_types.h to use together with the register defines to read and write the corresponding addresses:

  • HWREG(x) : Access (read or write) a full word (32 bits) at address x in the memory map.
  • HWREGH(x) : Access a halfword (16 bits) at address x in the memory map.
  • HWREGB(x) : Access a byte (8 bits) at address x in the memory map.
  • HWREGBITW(x, b) : Access bit-band region for bit-band operation using full word access.
  • HWREGBITH(x, b) : Access bit-band region for bit-band operation using halfword access.
  • HWREGBITB(x, b) : Access bit-band region for bit-band operation using byte access.

Given these defines and macros, the CR0 register, in the first instance of the SSI peripheral (SSI0), can be programmed as follows:

    HWREG(SSI0_BASE + SSI_O_CR0) = ((5 << SSI_CR0_SCR_S) | SSI_CR0_SPH | SSI_CR0_SPO);

Alternatively, the following has the same effect (although it is not as easy to understand):

    HWREG(SSI0_BASE + SSI_O_CR0) = 0x000005c0;

The value of the SCR field from the CR0 register can be extracted as follows:

    ulValue = (HWREG(SSI0_BASE + SSI_O_CR0) & SSI_CR0_SCR_M) >> SSI_CR0_SCR_S;
Note
These examples all use constants (defines) as argument for the macro which means that the macro can be resolved at compile-time for very efficient memory accesses. If using variables (e.g. a count value for accessing continuous addresses) the macro can only be resolved at run-time thus resulting in more code and less efficient execution.

Bit-Band Operations

The device supports ARM's so-called "bit-band operations" which is a mechanism that makes a read-modify-write operation an atomic operation seen from the CPU's perspective. Thus the CPU can write a single bit in a register/memory without executing the usual read-modify-write operation but instead the CPU only needs to perform a single write operation, and then the bus performs the actual read-modify-write towards the memory or peripheral module. Same goes for reading single bits as the bus takes care of masking and shifting such that the CPU receives a word only containing the bit of interest located at the LSB.

Bit-band operations are only possible in the bit-band regions where the LSB of each word represents a single bit in a bit-band alias region. The term "bit-band alias" describes the registers or memory locations to which the bus performs the actual read-modify-write operation. In other words, if a programmer wants to perform a bit operation on a register, the register must be in the bit-band alias region, and then he can perform a bit-band operation to the bit-band region and then the bus remaps it to the bit-band alias address.

The provided macros, HWREGBITW etc, "hide" the bit-band regions to the programmer such that the programmer only needs to program which bit of a register (in the bit-band alias region) he wants to write or read. Thus the macro maps the register and bit number to an address in the bit-band region and then the bus performs the bit-band operation by remapping back to the bit-band alias address.

The device has two bit-band alias regions, each spanning 1MB, which are:

  • SRAM : 0x2000_0000 - 0x200F_FFFF
  • Peripheral : 0x4000_0000 - 0x400F_FFFF
Note
Not all addresses in the two ranges are accessible thus accessing an address in the bit-band region which does not have a valid bit-band alias will result in a bus error. See the memory map of the device to make sure which addresses are accessible!

The corresponding bit-band regions, each spanning 32MB, (accessed through the macros) are:

  • SRAM : 0x2200_0000 - 0x23FF_FFFF
  • Peripheral : 0x4200_0000 - 0x43FF_FFFF

Thus, instead of using the usual read-modify-write to clear a bit:

    HWREG(SSI0_BASE + SSI_O_CR0) &= ~(SSI_CR0_SPO_M);

it is possible to use bit-banding:

    HWREGBITW(SSI0_BASE + SSI_O_CR0, SSI_CR0_SPO_BITN) = 0;
Note
The macros for bit-banding are a little more complex thus using variables instead of constants (defines) as arguments for the macros will result in more code in order to calculate the address in the bit-band region at run time. If it is necessary to use variables with bit-banding then consider accessing the bit-band region directly instead of using the provided macros if code size or execution time are critical.

Software Driver Model

In the software driver model, the APIs provided in DriverLib are used by applications to control the peripherals. Because these drivers provide complete control of the peripherals in their normal mode of operation, it is possible to write an entire application without direct access to the registers. This method provides for rapid development of the application without requiring knowledge of how to program the peripheral registers.

Corresponding to the direct register access model example, the following call also programs the CR0 register in the SSI module (though the register name is hidden by the API):

    SSIConfigSetExpClk(SSI0_BASE, 50000000, SSI_FRF_MOTO_MODE_3,
                       SSI_MODE_MASTER, 1000000, 8);

Combining the Models

The direct register access model and software driver model can be used together in a single application, thus applying the most appropriate model as needed to any particular situation within the application. For example, the software driver model can be used to configure the peripherals (because this is not performance critical) and the direct register access model can be used to operate the peripheral (which may be more performance critical). Or, the software driver model can be used for peripherals that are not performance critical (such as a UART used for data logging) and the direct register access model can be used for performance critical peripherals.

ROM Functions

In order to free up code space in the flash memory for user applications a part of the driver library has been stored in the on-chip ROM which allows the user to chose between calling certain drivers from either ROM or flash. Calling the ROM version of a driver function prevents the compiler from putting the function in flash and thus leaving more flash memory for applications.

Besides saving space in flash other benefits of using the ROM version of a driver library function are faster execution and reduced power consumption as flash is both slower and more power consuming than the ROM. A minor disadvantage of calling a ROM function is a small overhead in the function call as it does a table look-up to find the address of the ROM function. However, only driver library functions of a certain size have been selected to be stored in ROM which means that the ROM version of a function is always faster and less power consuming than its flash equivalent.

Default DriverLib Call

Because of the benefits of using the ROM version of a given driver a mechanism to select the ROM version as default has been implemented. This mechanism uses defines to effectively rename functions and function calls to distinguish between ROM and flash versions.

The mechanism works like this:

  • The file rom.h contains the list of ROM functions available in the device and each ROM function has a define that tells the compiler where to find the function in ROM (via a look-up table). All ROM functions have the prefix ROM_.
  • In the beginning of the .h file for every module all functions that are not static inline are defined (renamed) from FunctionName() to NOROM_FunctionName() thus all flash functions now have the prefix NOROM_ - except static inline functions which will keep the original name.
  • At the end of the .h file all the defines (function names) are re-defined to the ROM version with the prefix ROM_ thus when a user includes the .h file in his .c file then all DriverLib calls are redefined to call the ROM function. The default redefinition to ROM functions can be avoided by defining DRIVERLIB_NOROM in the project. This will prevent the redefinition to ROM_ functions and keep the definition to NOROM_ functions and thus always use the flash version.
  • In the beginning of the .c file for a module all non static inline functions are redefined to NOROM_FunctionName in order to make sure that the functions in the DriverLib .c file are always redefined to have the prefix NOROM_ i.e. calling a NOROM_ function will always call the flash version.

In summary: By default, the compiler will use DriverLib in ROM whenever possible and if the user wishes to avoid the ROM versions entirely (e.g. test and debugging) he can make a project-wide define called DRIVERLIB_NOROM which forces DriverLib to ignore the ROM functions. However, some functions from the flash API will always execute from ROM as they are not allowed to execute from flash (e.g. program and erase).

Note
See Using Pre-compiled Driver Library when you are not compiling DriverLib yourself but using pre-compiled library instead.

Direct ROM Call

If a user wishes to avoid ROM versions of DriverLib by default but only use specific DriverLib functions from ROM this is possible:

  • Define DRIVERLIB_NOROM in the project to use flash versions as default.
  • Include rom.h in the user .c file.
  • Use prefix ROM_ whenever a ROM function is wanted.

Direct Flash Call

If ROM functions are used as default then it is possible to use the flash version of a specific function:

  • Use prefix NOROM_ whenever a flash function is wanted.
Note
If you use NOROM_ explicitly to call the flash version of a DriverLib function (FunctionA) and that function makes a function call to another DriverLib function (FunctionB) then the internal function call (to FunctionB) will use the global selection; in this case the ROM version.

Using Pre-compiled Driver Library

When using the pre-compiled version of DriverLib then setting the define DRIVERLIB_NOROM in the project has no effect on the library itself. Thus all internal calls in the pre-compiled library to other DriverLib functions will always be to ROM version - if it exists.

HAPI Functions

Besides the functions available in the DriverLib a number of functions are also available in a ROM-only version. These functions are called HAPI functions (Hard API) and are generally used by various tools. Source code is not available for the HAPI functions but the functions are available for users to call. The list of HAPI functions available can be found in rom.h.

Error Checking

Invalid arguments and error conditions are handled in a non-traditional manner in DriverLib. Typically, a function would check its arguments to make sure that they are valid (if required; some may be unconditionally valid such as a 32-bit value used as the load value for a 32-bit timer). If an invalid argument is provided, an error code would be returned. The caller then has to check the return code from each invocation of the function to make sure that it succeeded.

This method results in a significant amount of argument-checking code in each function and return-code-checking code at each call site. For a self-contained application, this extra code becomes an unneeded overhead once the application is debugged. Having a means of removing it allows the final code to be smaller and therefore run faster and consume less power.

In this driver library, most functions do not return error status. Argument checking is done via a call to the ASSERT macro (provided in debug.h). This macro has the usual definition of an assert macro; it takes an expression that must be true. By making this macro empty, the argument checking is completely removed from the code thus avoiding the error checking overhead.

There are two definitions of the ASSERT macro provided in debug.h; one that is empty (used for normal/release builds) and one that evaluates the expression (used when the library is built for debugging). The debug version calls the __error__ function whenever the expression is not true, passing the file name and line number of the ASSERT macro invocation. The __error__ function is prototyped in debug.h and must be provided by the application because it is the application's responsibility to deal with error conditions.

To enable the ASSERT macro define the symbol DRIVERLIB_DEBUG within your project and/or compiler setup.

Note
Defining DRIVERLIB_DEBUG for the entire project might result in a significant increase in code size depending on the number of modules used in the project. Defining DRIVERLIB_DEBUG for specific files is possible if a project-wide define results in an unacceptable code size increase.

By setting a breakpoint on the __error__ function, the debugger immediately stops whenever an error occurs anywhere in the application (something that would be very difficult to do with other error checking methods). When the debugger stops, the arguments to the __error__ function and the backtrace of the stack pinpoint the function that found an error, what it found to be a problem, and where it was called from. As an example:

void
UARTParityModeSet(uint32_t ui32Base, uint32_t ui32Parity)
{
//
// Check the arguments.
//
ASSERT(UARTBaseValid(ui32Base));
ASSERT((ui32Parity == UART_CONFIG_PAR_NONE) ||
(ui32Parity == UART_CONFIG_PAR_EVEN) ||
(ui32Parity == UART_CONFIG_PAR_ODD) ||
(ui32Parity == UART_CONFIG_PAR_ONE) ||
(ui32Parity == UART_CONFIG_PAR_ZERO));

Each argument is individually checked, so the line number of the failing ASSERT indicates the argument that is invalid. The debugger is able to display the values of the arguments (from the stack backtrace) as well as the caller of the function that had the argument error. This method allows the problem to be quickly identified at the cost of a small amount of code.

Device Setup

DriverLib includes a special function called SetupTrimDevice() which must always be called right after the ROM boot sequence in order to apply trim settings and certain customer configurations (from CCFG) to the device.

Note
SetupTrimDevice() is called by the startup file provided as part of CC13/26xxWare.

Customer Configuration (CCFG)

CC13/26xxWare also includes a customer configuration file (ccfg.c) which contains settings that are being applied mainly by ROM boot sequence, trimDevice() during startup, and radio SW. The configuration settings are stored in a specially allocated address range in flash referred to as "CCFG area". The user must edit the CCFG to fit the needs of the specific design and application.

Typically, a user application does not need to read the settings in CCFG; however, a few settings might be interesting for a user to read thus DriverLib provides an API that allows simple access to a subset of the CCFG settings (see the CCFGRead API). The remaining settings not covered by the DriverLib API can of course be read using the "Direct Register Access Model" described earlier. Although located in flash the CCFG settings are documented in the register descriptions as part of the CPU Domain Memory Map.

TI RTOS

Texas Instruments provides a Real-Time Operating System (RTOS) for CC13xx called TI-RTOS which uses the DriverLib as the main interface to access the hardware registers thus minimizing the need for direct register accesses from TI-RTOS itself. The TI-RTOS package provides its own set of RTOS drivers that call DriverLib functions but RTOS drivers can be used only together with TI-RTOS and not stand-alone in a non-RTOS application.

When using TI-RTOS and the included RTOS drivers it is important that a user application does not "bypass" TI-RTOS drivers by calling DriverLib functions directly to configure any hardware that is controlled by TI-RTOS. Doing so can cause a conflict that may result in unexpected behavior by the RTOS.