Technical topic: TASTE on MSP430 with FreeRTOS
MSP430 is a family of cheap, ultra-low-power, mixed-signal microcontrollers, which includes space-grade radiation hardened parts (e.g. MSP430FR5969 rated for 50krad, with non-volatile FRAM). The above traits make them good candidates for deployment in small satellites.
FreeRTOS is a real-time operating system for embedded microcontrollers. FreeRTOS provides API for threads, mutexes, semaphores, queues and timers with very low memory requirements.
This page describes MSP430 TASTE Runtime based on FreeRTOS, which is included in the current releases of the TASTE.
- 1 Hardware Description
- 2 MSP430 Runtime limitations and trade-offs
- 3 MSP430 Runtime implementation details
- 3.1 AADL library modifications
- 3.2 Kazoo templates for MSP430
- 3.3 msp430_freertos_gpr
- 3.4 msp430_freertos_files
- 3.5 msp430_freertos_config
- 3.6 msp430_freertos_main
- 3.7 msp430_freertos_thread_header and msp430_freertos_thread_body
- 3.8 msp430_freertos_wrappers_header and msp430_freertos_wrappers_body
- 3.9 msp430_freertos_transport_body msp430_transport_header
- 3.10 msp430_freertos_eusci_a_serial_minimal_header and msp430_freertos_eusci_a_serial_minimal_header
- 4 Device drivers
- 5 Ada support
- 16-bit RISC architecture;
- 16‑MHz clock;
- 64KB FRAM / 2KB SRAM.
On this very limited platform using PolyORB-HI-Ada or PolyORB-HI-C is impossible, due to memory requirements. In the next sections the different approach of code generation and middleware use is described.
Memory models in MSP430FR5969
The MSP430FR5969 can operate in two memory models:
- small memory model;
- large memory model.
In the small memory model only 48 KB of FRAM are available for programmer with additional 2 KB of SRAM (default location of stack and .bss). This limitation is caused by 16-bit addressing mode available in the small memory model. Although 16-bit should allow to address 64 KB of memory, some addressing areas are reserved for bootloader and peripherals.
In large memory model, the programmer can use additional 16 KB of FRAM, which gives total 64 KB of FRAM memory and additional 2 KB of SRAM for stack and .bss. In this model all 20 bits of address line are used but as a result the size of pointers is bigger - 4 bytes instead of 2. This memory model also enforces different instruction set. It means that change from small memory model to large memory model gives access to more memory but at cost of increased size of generated code and data structures containing pointers.
In small memory model the global variables are allocated in the 2KB of so called RAM segment (representing SRAM memory), which may not be enough to hold all variables. The selected variables may be allocated inside the FRAM segment instead by adding the special attribute, as in the example bellow:
__attribute__ ((persistent)) int global_variable = 0;
As those variables survive reset of the device, their values should be manually reinitialized after reset if necessary.
In large memory model the variables may be allocated outside the RAM memory segment (SRAM), which allows the programmer to declare larger number of variables.
There's also a compiler option
-mdata-region=upper to use only higher memory segment for data and as a result leave whole RAM segment for stack.
The problem with 2 KB of RAM segment is that the stack may overwrite the other variables allocated in this segment during runtime. This may happen before start of FreeRTOS scheduler, during initialization. Currently about 200 bytes of stack are required. The programmer should make sure that, there's enough space for stack in the RAM segment after building the partition image.
msp430-elf-ld does not allow to set the stack size the bash script presented below checks this memory constraint on compiled executable.
#!/bin/bash if [ -z $1 ] then echo "No executable specified." echo "Usage:" echo " check_stack.sh <elf file>" exit 1 fi stack_ptr=$(msp430-elf-nm -n -S --special-syms --synthetic $1 | grep '__stack' | cut -d ' ' -f1) heap_limit=$(msp430-elf-nm -n -S --special-syms --synthetic $1 | grep '__HeapLimit' | cut -d ' ' -f1) printf "Stack addresses: 0x%s\n" $stack_ptr printf "Heap limit: 0x%s\n" $heap_limit available_stack_memory=$((16#$stack_ptr - 16#$heap_limit)) printf "Available memory for stack: %d bytes \n" $available_stack_memory
$ bash check_stack.sh work/binaries/msp430fr5969_partition Stack addrres: 0x00002400 Heap limit: 0x00001c06 Available memory for stack: 2042 bytes
FreeRTOS port for MSP430FR5969
The TASTE runtime for MSP430 requires the FreeRTOS port for GCC (msp430-elf-gcc) and the architecture MSP430X.
The suitable port was prepared as a part of this project by N7Space team.
Currently this port is installed by
taste-setup as a patch to the FreeRTOS, but Pull Request to FreeRTOS-kernel repository on GitHub with this port was submitted.
The FreeRTOS port for MSP430FR5969 supports both small memory model and large memory model. When large memory model is used the following options should be passed to the compiler:
There are options to specify in which memory segment the code and data should be placed:
-mcode-region=either -mdata-region=upper are recommended for TASTE.
For small memory model, the only required compiler option is
-msmall. It implies
Memory allocation in FreeRTOS
FreeRTOS may be configured to use dynamic memory allocation or static memory allocation or both. When the dynamic memory allocation is used the programmer should provide memory region for special FreeRTOS heap. When static memory allocation is used the programmer should allocate required structures before creating FreeRTOS objects. For TASTE the dynamic memory allocation is disabled, the generated code uses only static memory allocation.
More information at Static Vs Dynamic Memory Allocation.
MSP430 Runtime limitations and trade-offs
The origins of all problems discovered during development of the demo application can be traced back to limited memory on the device. Although many potentials problems can be resolved using larger memory model or static allocation, as described above, some limitations to MSP430 Runtime still applies.
Available priorities of threads
To ensure low memory usage the number of available priorities is limited. There are 5 available priorities: from 0 (lowest priority) to 4 (highest priority).
Parameters of remote sporadic interfaces
The node may provide multiple sporadic interfaces and may require multiple sporadic interfaces. The UART driver for Linux (linux-serial-minimal) imposes a requirement that all provided sporadic interfaces in the node should have the parameter of one exact type. For an example, the Satellite (MSP430) node may provide many sporadic interfaces with parameter of type Telecommand while the EGSE (Linux) node may provide sporadic interfaces with parameter of type Telemetry. Also, the total size of Telemetry should be bigger than total size of Telecommand. Otherwise, the Linux driver will not be able to decode the message.
Stack sizes of threads
The default stack size for the thread in TASTE is 50kb. This value almost exceeds the available memory. The user should manually set the appropriate stack size for every thread on MSP430 partition. The minimal value is 280 bytes for small memory model and 340 bytes for large memory model. This should be enough for small projects, however for more complex examples the value may be increased to 1024 bytes and more. During the development the programmer may use FreeRTOS API for detecting stack overflows .
Number of threads and sizes of queues
There's no strict limitation of number of threads or sizes of queues. The only limitation is available memory. Following parameters have an impact on total memory consumption:
- The largest size of parameter sporadic interface after encode;
- The sizes of queues;
- The number of threads and its stacks.
Every thread has own stack of size Stack_Size and own queue. Every queue contains Queue_Size of requests. Every request has size equal to the size of the largest of parameters. This coarse estimation shows how these parameters have an influence on total memory usage by TASTE MSP430 Runtime.
Nevertheless, the MSP430 TASTE Runtime should handle about 10 threads and there should be a free memory for implementation of all interfaces in C.
MSP430 Runtime implementation details
AADL library modifications
The image below shows the modification in ocarina_components.aadl required to add new runtime to the TASTE toolchain.
This requires some changes in the Ocarina source code which are described at Technical topic: Add a new target platform to TASTE.
Kazoo templates for MSP430
The rest of the code, which should be generated to create image of the partition, comes from Concurrency View. This code is responsible for device initialization, the initialization of drivers and function blocks from Interface View, and communication between these objects. To cope with this the following entities should be used:
Support for MSP430 is based on FreeRTOS constructs:
- For threads the FreeRTOS Task API;
- For queues the FreeRTOS Queues are used.
- For timers the Software Timers are used;
- For semaphores/mutexes Semaphores API is used.
This set of constructs is enough to create code which ensures cooperation of function blocks and communication with remote partitions.
For every function block from Interface View the generated code should create mutex or semaphore, which is used for synchronization. For every interface of the function block, the set of function for passing messages should be generated, and in additional:
- if the interface is cyclic, the generated code should create timer and message queue, and
- if the interface is sporadic, the generated code should create message queue;
- if the interface is protected, the code should acquire and release lock before calling the function;
- if the interface is unprotected there's no additional constrains.
For every thread the
Priority properties should be included.
For every queue the property
Queue_Length should be taken into account.
If the node from Deployment View contains devices, then the generated code should include driver implementation code.
The templates should also generate the
Makefile and GNAT Project Manager configuration file, which are responsible for building process of the partition image.
Following sections describes the kazoo templates.
The sole purpose of this template is to create following files:
The only purpose of Makefile is to run grpbuild to build whore partition binary and to run script which copies required FreeRTOS source files into partition source directory (required FreeRTOS version can be installed using TASTE add-ons scripts).
The generated gpr file contains configuration for GNAT project manager. In this file following sections are generated:
- List of directories of source files, relative to the location of gpr file , see Source_Dirs
- Directory for object files, relative to the location of gpr file, see Object_Dir
- Directory for outpt file, relative to the location of gpr file, see Exec_Dir
- Configuration of the compilers see package Compiler, which includes:
- paths to the compiler executables
- flags for the compilers
- Configuration of the linker, see package Linker,
- path to the linker executable
- flags for the linker
See other templates like
This template generates file
build/node/gather_freertos_files.sh, which is executed by
The sole purpose of this file is to copy required FreeRTOS source files to the partition directory.
The location of FreeRTOS directory is expressed by
FREERTOS_PATH environment variable.
If the files cannot be copied, the script prints an error message, and the build process is terminated.
The purpose of this template is to produce file
build/node/partition/FreeRTOSCOnfig.h. This file is required by FreeRTOS.
The file determines minimal set of FreeRTOS construct, which are required to build partition.
Almost all options are disabled.
This template generates file
build/node/partition/main.c, which is entry point for the partition.
This file contains following functions:
- function prvSetupHardware, which configures MSP430FR5969 MCU and calls device drivers initialization routines.
- function vApplicationStackOverflowHook, which may be used to check stack overflow during debugging (see ).
- function vApplicationSetupTimerInterrupt, which configures Timer A for FreeRTOS system ticks.
- function vApplicationGetIdleTaskMemory and vApplicationGetTimerTaskMemory, which provides memory for Ilde task and Timer task. Before these function there's allocated Task Control Block structure (TCB) and actual stack buffers (see )
- declaration of semaphores for all function blocks from interface view
- functions which initializes all the threads (see thread.tmplt) with their stacks and queues.
- the function main, which is actual entry point of the partition. The main function:
- calls function prvSetupHardware
- initializes all previously declared semaphores (see xCreateMutexStatic).
- After hardware initialization the subroutine main calls initialization subroutines for every function block from the Interface View
- calls previously defined functions which initializes all the tasks
- calls vTaskStartScheduler.
The subroutine prvSetupHardware initializes oscillators and clocks:
- oscillator DCO frequency is set to 8 MHz;
- oscillator LFXTCLK frequency is set to 32768 Hz;
- clock MCLK uses DCO with divider 1;
- clock SMCLK uses DCO with divider 1;
- clock ACLK uses LFXTCLK with divider 1.
msp430_freertos_thread_header and msp430_freertos_thread_body
These templates generates following files for each thread:
These files contains definition of thread entry subroutine:
void prv_@_Thread_Name_@Task(void* prParameters);
If the coresponding interface from InterfaceView is cyclic interface then additional timer callback function is generated:
void prv_@_Thread_Name_@_Timer_Callback(TimerHandle_t timer)
The purpose of this function is to create an new <Request> and put it into thread queue using FreeRTOS function xQueueSend. This function is passed as a callback to the timer created using xTimerCreateStatic from FreeRTOS API. The created timer is started using xTimerStart function.
The generated thread entry function contains a loop which waits for incoming tasks using xQueueReceive and calls subroutine
call_@_LOWER:Thread_Name_@ to execute the task.
msp430_freertos_wrappers_header and msp430_freertos_wrappers_body
These templtes creates following files:
These files contains:
Functions with name with prefix
pro_, which are generated for corresponding protected interfaces.
These functions are generated by pi.tmplt file.
Following part of code show how the name of the function is generated:
Every provided interface has corresponding function with prefix
These functions are generated by file ri.tmplt.
Following part of code show how the name of the function is generated:
These functions are used by code skeletons for calling required interfaces.
The body of the function depends on type of the provided interface.
If the interface is sporadic, then the structure
Request is created and filled with parameter value. Then, the function with prefix
deliver_to_ is called, which is responsible for putting the task into corresponding queue or passing the task to the device driver.
If the interface is protected, then the sole purpose of this function is to call corresponding function with prefix
pro_ (see above).
If the interface is unprotected, then the function calls directly corresponding skeleton function.
The functions with prefix
call_ are generated for sporadic interfaces.
Following part of code show how the name of the function is generated:
void call_@_LOWER:Thread_Name_@ (const char* buf, size_t len)
The file thread.tmplt is responsible for generation of these functions. This function is responsible for acquiring and releasing lock on function mutex.
These templates generates following files:
transport.h contains generated structure
This structure is used only to calculate the size of the filed
m_data in the structures
Request is used to pass task to the task queues in local partition.
Message is used to pass task to the sporadic interfaces on remote partition.
This structure contains additional field
m_port which denotes number of the port.
transport.h contains function
process_incomming_message which is used by
communication device driver to proceed incoming message.
Also the device driver should use function
which is used to pass the structure
Message outside the partition.
For the moment, only one device driver on the msp430 node is supported and only one device driver for MSP430 is created.
transport.c the subroutines deliver_to_ are defined.
These functions are used to transport the
Request to corresponding queue or to the device driver.
The file generated for PolORB
deployment.h is used to obtain a port number for remote port.
Also the function
process_incoming_message uses enum from
deployment.h to decide which thread shoud be used
msp430_freertos_eusci_a_serial_minimal_header and msp430_freertos_eusci_a_serial_minimal_header
These templates generates following files:
These files contains a source code of the communication driver Which uses MSP430 e_USCI_A to provide UART communication with other nodes.
Following functions are defined:
void msp430_eusci_a_serial_minimal_init(void); void msp430_eusci_a_serial_minimal_poller(void* param); void msp430_eusci_a_serial_minimal_sender(uint8_t* data, uint32_t length, uint32_t port);
More about device driver in the next chapter.
The devices and their drivers are described in file ocarina_components.aadl. This file describes subprograms required by device drivers. The procedure of adding device drivers is described in article Writing driver for PolyORB-HI-C.
For MSP430FR5969 the communication with remote nodes was achieved via UART.
The UART driver for Linux was implemented as PolORB-HI-C driver:
The UART driver for MSP430 was implemented without PolyORB:
The file ocarina_components.aadl is part of the taste-setup project. On the Taste VM the location of this file is /home/taste/tool-src/install/misc/aadl-library/ocarina_components.aadl.
For the MSP430 the new bus implementation serial.minimal was added.
Together with this bus, two devices was added with ability to use this bus:
Ada in TASTE can be used as:
- a function implementation language,
- SDL generation target language,
- a device driver implementation language.
Providing Ada support in TASTE requires integration of an Ada compiler for the target platform within the TASTE environment. Several candidates were considered (the presented status is as of the beginning of 2020):
- Texas Instruments GCC for MSP430 - does not support Ada,
- AdaCore GNAT Community Edition - does not support MSP430,
- GNU GCC available in Debian repositories - is out of date and contains bugs affecting MSP430FRXXX chips.
Proprietary compilers were not considered, as their licensing may put constraints on distribution and usage within TASTE.
In order to enable Ada support on the MSP430FR5969 chip, a new Ada compilation toolchain was assembled – adac-hybrid-msp430. It works by combining AdaCore GNAT LLVM project, the LLVM project and Texas Instruments GCC (TI GCC). The compilation process is as follows:
- Ada files are compiled into LLVM Intermediate Representation (encoded as BitCode) using GNAT LLVM,
- the BitCode is translated into MSP430 assembly using LLC utility of the LLVM project
- the generated assembly is postprocessed,
- the processed assembly is compiled using TI GCC.
The use of TI GCC allows to use the compiler with all MSP430 family chips. The modular architecture of the toolchain offers the possibility to swap its parts e.g. to support different MCU architectures (by changing LLC generation target and replacing TI GCC with an MCU specific last stage compiler).
The introduced assembly postprocessing is responsible for finding all Ada elaboration functions and putting them into .init_array section. This way the elaboration functions are handled exactly as C constructors/initializers, allowing seamless integration with C code - adac-hybrid-msp430 can be used exactly as GCC, without the need for an Ada binder, simply using standard GCC linker scripts.
The LLVM IR generated from Ada files relies on the presence of several Ada-specific library functions – the GNAT runtime. Similarly, the MSP430 assembly generated by LLC relies on several LLVM-specific library functions. These are provided by libgnatmsp430 and libllvmmsp430 respectively.
Libgnatmsp430 is derived from libgnat sourced from GCC with additions from MSP430-Ada project (mainly system configuration). It provides several functions related to e.g. bound checking, math operations, as well as pointer sizes and basic types.
Libllvmmsp430 is derived from llvm-runtime and focuses on emulating several high-level operations absent from the MSP430 Instruction Set Architecture (ISA). However, as the linking process is done by TI GCC, most of the required functions are provided by gcc-runtime. Therefore, libllvmmsp430 provides just the missing ones.
Both libgnatmsp430 and libllvmmsp430 are precompiled for MSP430 and MSP430X ISAs and tested with MSP430FR5969 and emulated MSP430G2553.
Adac-hybrid-msp430 is designed so that it can be used together with TI GCC and in similar fashion to TI GCC, with any build system – its repository contains multiple tests, which demonstrate both Ada standalone compilation, as well as C to Ada and Ada to C interop, all using simple Makefiles. However, many Ada based projects, as well as TASTE, rely on gprbuild build system, which assumes the presence of an Ada binder. As the assembly postprocessing handles the elaboration handling, the work normally performed by the binder is not needed. However, in order to provide gprbuild with the required intermediate artefacts, a custom stub msp430-elf-adabind utility is provided.
Please refer to adac-hybrid-msp430 README.
The MSP430 specific assembly is generated by LLC, which (for version 9.0.49 of LLVM used within the project) does not support MSP430 large memory model. This limits the available address space to 64 kB, which in turns restricts the available memory to a maximum of 48 kB, as part of the address space is reserved for peripherals. If large memory support is added to LLC in the future, adac-hybrid-msp430 should be able to take advantage of it, by passing additional flags to LLC (see adac-hybrid-msp430 README for details). Please note that given the current constraints of the toolchain and the need for consistency within a linked binary, if Ada language is used within an MSP430 partition in a TASTE project, the partition will be built with small memory model.
MSP430 is a 16-bit MCU – this implies that “base” integer types, enumerations and pointers are 16-bit (20-bit pointers were not tested, as the large memory model is not supported). The Ada code that the toolchain was tested with assumed certain constructs to be 32- or 64-bit. AdaCore’s GNAT LLVM also assumes 32-bit as a “base” size. This led to suboptimal memory layout and more importantly, type size mismatches when combining Ada and C (user-provided or runtime) code. This was resolved by:
- requiring Ada code to explicitly state data type sizes when used together with user-provided C code (this affected ASN1SCC runtime),
- adjusting libllvm430 runtime and Ada system configuration to match the ABI.
The GNAT runtime provided by GCC cannot be used as-is on an MSP430 MCU. The System.ads had to be adjusted (which was done with the help of MSP430 Ada project code) for a bare-metal MSP430. Several modules had to be modified to work with 32- and 64-bit floats provided on MSP430. Also, AdaCore GNAT LLVM cannot compile some of the modules – they had to be removed from the runtime. However, their removal shouldn’t affect embedded applications.
The GNAT runtime is quite large considering the memory available on MSP430 targets. Most of the toolchain validation was done on an emulated MSP430G2553, which provides only 16 kB of flash. While MSP43FR5969 provides 64 kB of FRAM, the assumed priority is to provide as much space for user code as possible. Therefore, libgnatmsp430 is compiled with
–fdata-sections, so that the unneeded code (in practice, a great majority of the library) can be trimmed during the linking process, resulting in binaries fitting the target MCUs.
In order to ensure that the provided toolchain works correctly, multiple test cases were provided. The testing is performed by compiling Ada source code and verifying that it works as expected on an MSP430 target. While testing on real hardware provides a higher degree of certainty, it is also more difficult to setup (especially when considering a Continuous Integration server) and more time consuming (while 16 MHz, 16-bit MCU is slow by itself, the code upload takes seconds and needs to be repeated for each test). With continuous testing in mind, it was decided to deploy most of the tests on an emulator. The emulator of choice was MSP43-Emulator by Rudolf Geosits. The project was forked in order to provide several modifications:
- enable command-line usage (for use in automated testing),
- USCI module rewrite (for use in automated testing using files/pipes),
- memory access flags (for more accurate support of peripherals),
- several bugfixes (SR register handling, long jumps, program entry point),
- integrations tests and Dockerfile.
The use of an emulator also enabled faster and simpler debugging - especially instruction and call trace was a great help in resolving some issues. However, the emulator itself caused some issues on its own – the original version contained several unexpected bugs, which needed to be fixed. Also, the presence of the discovered bugs decreased the confidence in the results of the emulation. Therefore, automated testing on the hardware is a must to raise the confidence in the correctness of the toolchain. However, for performance and setup reasons, most of the tests are still executed on the emulator. Hardware testing is assumed to be the “final” validation.
It must be noted that the emulator does not implement all MSP430G2553 peripherals, as well as interrupts, and so it is unable to run FreeRTOS-based applications, such as complete MSP430 based TASTE projects. Therefore, only bare-metal use cases are subject to testing with the scope of the toolchain project. Further development of the emulator could remove this limitation.
End-to-end validation of the toolchain, within TASTE environment and on the target MSP430FR5969 hardware, was done using a sample TASTE project. TASTE repositories contain a skeleton project for integration testing.