Technical topic: TASTE on MSP430 with FreeRTOS

From TASTE
Jump to: navigation, search

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.

Hardware Description

This article describes steps of adding support for MSP-430 platform using MSP43FR5969 processor as example. The MSP430FR5969 LaunchPad Development Kit was used for testing purposes.

Microcontroller's parameters:

  • 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 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 an 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.

Since the 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 addrres: 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

Possible usage:

$ 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 submited.

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:

  • -D__LARGE_DATA_MODEL__=1
  • -mlarge

There are options to specify in which memory segment the code and data should be placed:

  • -mcode-region=[either,lower,upper]
  • -mdata-region=[either,lower,upper]

The options -mcode-region=either,lower,upper -mdata-region=upper are recommended for TASTE.

For small memory model, the only required compiler option is -msmall.

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 [1].

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.

Msp430 ocarina processor.PNG

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

Kazoo generates multiplatform skeletons and glue source code for function blocks. The asn1scc generates code responsible for encoding/decoding data, which is also platform agnostic.

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:

  • Threads;
  • Queues;
  • Timers;
  • Mutexes/Semaphores.

Before support for MSP430 this was achieved by using code generated by Ocarina which uses PolyORB-HI-Ada or PolyORB-HI-C as middleware.

Support for MSP430 is based on FreeRTOS constructs:

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 Stack_Size and 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.

msp430_freertos_gpr

The sole purpose of this template is to create following files:

  • build/node/Makefile.node;
  • build/node/partition_partition.gpr.

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 c_pohi_gpr.

msp430_freertos_files

This template generates file build/node/gather_freertos_files.sh, which is executed by Makefile.node. 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.

msp430_freertos_config

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.

msp430_freertos_main

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 [2]).
  • 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 [3])
  • 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:

  • build/node/partition/thread_$thread_name$.h
  • build/node/partition/thread_$thread_name$.c

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:

  • build/node/partition/partition_interface.h
  • build/node/partition/partition_interface.h

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:

void pro_@_Parent_Function_@_@_Name_@

This function is responsible for acquiring and releasing lock on function block mutex. The functions | xSemaphoreTake and | xSemaphoreGive from FreeRTOS API are used to lock and unlock mutex.

Every provided interface has corresponding function with prefix vm_. These functions are generated by file ri.tmplt. Following part of code show how the name of the function is generated:

void vm_@_LOWER:Parent_Function_@_@_LOWER:Name_@

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.

msp430_freertos_transport_body msp430_transport_header

These templates generates following files:

  • build/node/partition/transport.h
  • build/node/partition/transport.c

The transport.h contains generated structure MSPAllParameters. This structure is used only to calculate the size of the filed m_data in the structures Request and Message

The structure Request is used to pass task to the task queues in local partition. The structure 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.

The file 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 register_remote_transport_func 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.

In file 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 to proceed Message.

msp430_freertos_eusci_a_serial_minimal_header and msp430_freertos_eusci_a_serial_minimal_header

These templates generates following files:

  • build/node/partition/drivers/msp430_eusci_a_serial_minimal/msp430_eusci_a_serial_minimal.c
  • build/node/partition/drivers/msp430_eusci_a_serial_minimal/msp430_eusci_a_serial_minimal.h

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.

Device drivers

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: linux_serial_minimal. The UART driver for MSP430 was implemented without PolyORB: msp430_eusci_a_serial_minimal.

ocarina_components.aadl

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: MSP430_serial and linux_serial.