RTOS Application Design#

This document is intended to help you start your first FreeRTOS application on xcore. We assume you have read FreeRTOS Application Programming and that you are familiar with FreeRTOS.

RTOS Application Example#

A fully functional example application that can be found in the RTOS framework under the path examples/freertos/explorer_board. This application is a reference for how to use an RTOS drivers or software service, and serves as an example for how to structure an SMP RTOS application for xcore. Additional code to initialize the SoC platform for this example is provided by a board support configuration library modules/rtos/modules/board_support/XCORE-AI-EXPLORER_2V0/platform

This example application runs two instances of SMP FreeRTOS, one on each of the processor’s two tiles. Because each tile has its own memory which is not shared between them, this can be viewed as a single asymmetric multiprocessing (AMP) system that comprises two SMP systems. A FreeRTOS thread that is created on one tile will never be scheduled to run on the other tile. Similarly, an RTOS object that is created on a tile, such as a queue, can only be accessed by threads and ISRs that run on that tile and never by code running on the other tile.

That said, the example application is programmed and built as a single coherent application, which will be familiar to programmers who have previously programmed for the xcore in the XC programming language. Data that must be shared between threads running on different tiles is sent via a channel using the RTOS intertile driver, which under the hood uses a streaming channel between the tiles.

Most of the I/O interface drivers in fact provide a mechanism to share driver instances between tiles that utilizes this intertile driver. For those familiar with XC programming, this can be viewed as a C alternative to XC interfaces.

For example, a SPI interface might be available on tile 0. Normally, initialization code that runs on tile 0 sets this interface up and then starts the driver. Without any further initialization, code that runs on tile 1 will be unable to access this interface directly, due both to not having direct access to tile 0’s memory, as well as not having direct access to tile 0’s ports. The drivers, however, provide some additional initialization functions that can be used by the application to share the instance on tile 0 with tile 1. After this initialization is done, code running on tile 1 may use the instance with the same driver API as tile 0, almost as if it was actually running on tile 0.

The example application referenced above, as well as the RTOS driver documentation, should be consulted to see exactly how to initialize and share driver instances. Additionally, not all IO is capable of being shared between tiles directly through the driver API due to timing constraints.

The RTOS framework provides the ON_TILE(t) preprocessor macro. This macro may be used by applications to ensure certain code is included only on a specific tile at compile time. In the example application, there is a single task that is created on both tiles that starts the drivers and creates the remaining application tasks. While this function is written as a single function, various parts are inside #if ON_TILE() blocks. For example, consider the following code snippet found inside the i2c_init() function:

#if ON_TILE(I2C_TILE_NO)
    rtos_intertile_t *client_intertile_ctx[1] = {intertile_ctx};
    rtos_i2c_master_init(
            i2c_master_ctx,
            PORT_I2C_SCL, 0, 0,
            PORT_I2C_SDA, 0, 0,
            0,
            100);

    rtos_i2c_master_rpc_host_init(
            i2c_master_ctx,
            &i2c_rpc_config,
            client_intertile_ctx,
            1);
#else
    rtos_i2c_master_rpc_client_init(
            i2c_master_ctx,
            &i2c_rpc_config,
            intertile_ctx);
#endif

When this function is compiled for tile I2C_TILE_NO, only the first block is included. When it is compiled for the other tile, only the second block is included. When the application is run, tile I2C_TILE_NO performs the initialization of the the I2C master driver host, while the other tile initializes the I2C master driver client. Because the I2C driver instance is shared between the two tiles, it may in fact be set to either zero or one, providing a demonstration of the way that drivers instances may be shared between tiles.

The RTOS framework provides a single XC file that provides the main() function. This provided main() function calls main_tile0() through main_tile3(), depending on the number of tiles that the application requires and the number of tiles provided by the target xcore processor. The application must provide each of these tile entry point functions. Each one is provided with up to three channel ends that are connected to each of the other tiles.

The example application provides both main_tile0() and main_tile1(). Each one calls a common initialization function that initializes all the drivers for the interfaces specific to its tile. These functions also call the initialization functions to share these driver instances between the tiles. These initialization functions are found in the platform/platform_init.c source file.

Each tile then creates the startup_task() task and starts the FreeRTOS scheduler. The startup_task() completes the driver instance sharing and then starts all of the driver instances. The driver startup functions are found in the platform/platform_start.c source file.

Consult the RTOS driver documentation for the details on what exactly each of the RTOS API functions called by this application does.

Board Support Configurations#

xcore leverages its architecture to provide a flexible chip where many typically silicon based peripherals are found in software. This allows a chip to be reconfigured in a way that provides the specific IO required for a given application, thus resulting in a low cost yet incredibly silicon efficient solution. Board support configurations (bsp_configs) are the description for the hardware IO that exists in a given board. The bsp_configs provide the application programmer with an API to initialize and start the hardware configuration, as well as the supported RTOS driver contexts. The programming model in this FreeRTOS architecture is:

  • .xn files provide the mapping of ports, pins, and links

  • bsp_configs specify, setup, and start hardware IO and provide the application with RTOS driver contexts

  • applications use the bsp_config init/start code as well as RTOS driver contexts, similar to conventional microcontroller programming models.

To support any generic bsp_config, applications should call platform_init() before starting the scheduler, and then platform_start() after the scheduler is running and before any RTOS drivers are used.

The bsp_configs provided with the RTOS framework in modules/rtos/modules/bsp_config are an excellent starting point. They provide the most common peripheral drivers that are supported by the boards that support RTOS framework based applications. For advanced users, it is recommended that you copy one of these bsp_config into your application project and customize as needed.

Creating Custom bsp_configs#

To enable hardware portability, a minimal bsp_config should contain the following:

custom_config/
  platform/
    driver_instances.c
    driver_instances.h
    platform_conf.h
    platform_init.c
    platform_init.h
    platform_start.c
  custom_config.cmake
  custom_config_xn_file.xn

custom_config.cmake provides the CMake target of the configuration. This target should link the required RTOS framework libraries to support the configuration it defines.

custom_config_xn_file.xn provides various hardware parameters including but not limited to the chip package, IO mapping, and network information.

platform_conf.h provides default configuration of all header defined configuration macros. These may be overridden by compile definitions or application headers.

driver_instances.h provides the declaration of all RTOS drivers in the configuration. It may define XCORE hardware resources, such as ports and clockblocks. It may also define tile placements.

driver_instances.c provides the definition of all RTOS drivers in the configuration.

platform_init.h provides the declaration of platform_init(chanend_t other_tile_c) and platform_start(void)

platform_init.c provides the initialization of all drivers defined in the configuration through the definition of platform_init(chanend_t other_tile_c). This code is run before the scheduler is started and therefore will not be able to access all RTOS driver functionalities nor kernel objects.

platform_start.c provides the starting of all drivers defined in the configuration through the definition of platform_start(void). It may also perform any initialization setup, such as configuring the app_pll or setting up an on board DAC. This code is run once the kernel is running and is therefore subject to preemption and other dynamic scheduling SMP programming considerations.

Developing and Debugging Memory#

The XTC Tools provide compile time information to aid developers in creating and testing of their application.

Resource Usage#

One of these features if the -report option, which will Display a summary of resource usage. One of the outputs of this report is memory usage, split into the stack, code, and data requirements of the program. Unlike most XC applications, FreeRTOS makes heavy use of dynamic memory allocation. The FreeRTOS heap will appear as Data in the XTC Tools report. The heap size is determined by the compile time definition configTOTAL_HEAP_SIZE, which can be found in an application’s FreeRTOSConfig.h.

For AMP SMP FreeRTOS builds, which are created using the cmake macro merge_binaries(), there are actually multiple application builds, one per tile, which are then combined. While building a given AMP application, the console output will contain both of the individual tile build reports.

As an example, consider building the example_freertos_explorer_board target.

Constraint check for tile[0]:
  Memory available:       524288,   used:      318252 .  OKAY
    (Stack: 5260, Code: 42314, Data: 270678)
Constraints checks PASSED WITH CAVEATS.
Constraint check for tile[1]:
  Memory available:       524288,   used:       4060 .  OKAY
    (Stack: 356, Code: 3146, Data: 558)
Constraints checks PASSED.

Constraint check for tile[0]:
  Memory available:       524288,   used:       4836 .  OKAY
    (Stack: 356, Code: 3802, Data: 678)
Constraints checks PASSED.
Constraint check for tile[1]:
  Memory available:       524288,   used:      319476 .  OKAY
    (Stack: 14740, Code: 30730, Data: 274006)
Constraints checks PASSED WITH CAVEATS.

In this example, the cmake contains the command:

merge_binaries(example_freertos_explorer_board tile0_example_freertos_explorer_board tile1_example_freertos_explorer_board 1)

Which means the final application usage would be interpreted as:

Constraint check for tile[0]:
  Memory available:       524288,   used:      318252 .  OKAY
    (Stack: 5260, Code: 42314, Data: 270678)
Constraints checks PASSED WITH CAVEATS.
Constraint check for tile[1]:
  Memory available:       524288,   used:      319476 .  OKAY
    (Stack: 14740, Code: 30730, Data: 274006)
Constraints checks PASSED WITH CAVEATS.

Because the tile 1 portion of the tile1 target build replaces the tile 1 portion in the tile0 target build.

The XTC Tools also provide a method to examine the resource usage of a binary post build. This method will only work if used on the intermediate binaries.

$ xobjdump --resources tile0_example_freertos_explorer_board.xe
$ xobjdump --resources tile1_example_freertos_explorer_board.xe

Note: Because the resulting example_freertos_explorer_board.xe binary was created by merging into tile0_example_freertos_explorer_board.xe, the results of xobjdump –resources example_freertos_explorer_board.xe will be the exact same as xobjdump –resources tile0_example_freertos_explorer_board.xe and not account for the actual tile 1 requirements.

Building RTOS Applications#

Applications using the RTOS Framework are built using CMake. The RTOS framework provides many libraries, drivers and software services, all of which can be included by the application’s CMakeLists.txt file. The application’s CMakeLists can specify precisely which drivers and software services within the SDK should be included through the use of various CMake target aliases.

See the Build System Guide for more information on the build system.

See the Build System Guide - Targets for more information on the build system target aliases.