Skip to content

Digging into RTX

Introduction

You now have run your first application developed with ARM Keil. It is as simple as blinky, but it is already implemented using a RTOS. The project was copied from a software pack or from a git repository and you now need to have a better understanding of what is behind.

What you’ll build

In this codelab, you will understand the required details of the blinky LED program. You will also learn how to enable and use event recording for dynamic program analysis.

What you’ll learn

  • How a CMSIS application is structured.
  • How the main CMIS software components are made.
  • How a CMSIS project can be configured.
  • How event recording can be enabled and used for understanding the dynamic behavior of a program.

What you’ll need

Start with an application skeleton

For the sake of simplicity, a skeleton of the project for developing an application on the target device with ARM Keil or cmsis-tools is made available here. You may clone this repository and start working with the skeleton.

Developers can of course start developing an application from scratch, but integration from STM32CubeMX is not straightforward.

Two options are available for building applications:

  1. Use Keil as documented in the getting started codelab. You need to open the project and build the application.
  2. Use cmsis-tools. For using it, you need to:
    1. Install the required tools as documented here. Should you have installed ARM Keil already, make sure to read the note below.
    2. Run the command below for building the application:
      cbuild car-sim.csolution.yml --packs --verbose --update-rte -c car-sim+DISCOL475
      

Note

In case you installed ARM Keil already, cmsis-tools is already installed on your system in the same base folder as ARM Keil. In this case, you need to :

  • add the cmsis-tools “bin” folder to the “path” environment variable,
  • install cmake and ninja (see requirements) as well as
  • register the toolchain (as documented here).

At this point, you should be able to compile your application without error and to produce a binary. If you configure the debugger correctly, you may flash your board with this binary or you may drag and drop the “car-sim.bin” file onto your device for flashing it.

At this point, you have built and flash a skeleton application. If you look at your main() function, you may see that it implements an infinite loop, but does nothing else.

The Structure of a CMSIS Project

ARM supplies CMSIS-Core device template files for specific supported Cortex-M processors. The template files include:

  • Register names of the Core Peripherals and names of the Core Exception Vectors.
  • Functions to access core peripherals.
  • Generic startup code and system configuration code.

The template files are either distributed by ARM - if generic - or by the silicon vendor if specific. The file structure of the CMSIS-Core device templates is shown below:

It is important to understand the following aspects of the template file structure that is the base of any ARM Keil project:

  • Software packs present on a Windows machine are installed in the “%USERPROFILE%/AppData/Local/Arm/Packs/Keil” folder.
  • A “startup_<device>.s” (“startup_<device>.c” in more recent versions) file for each supported toolchain (in our project “RTE\Device\STM32L475VGTx\startup_stm32l475xx.s”). This is the only toolchain specific CMSIS file - in our case, we use the ARM toolchain.
    This file contains:
    • A reset handler that is executed after CPU reset and typically calls the SystemInit function.
    • Setup values for the Main Stack Pointer (MSP).
    • Exception vectors of the Cortex-M Processor with weak functions that implement default routines.
    • Interrupt vectors that are device specific with weak functions that implement default routines.
  • A “system_<device>.c” file containing the definition of the SystemInit and SystemCoreClockUpdate functions. In our project this file is located under “RTE/Device/STM32L475VGTx/system_stm32l4xx.c”.
  • A “<device>.h” file defining device specific information related to interrupts and peripherals - in our case, the stm32l4xx.h” file is part of the “STM32L4xx_DFP” software pack.
  • A “core_cm4.h” file (for Cortex-M4) processors) specifying all processor specific attributes (core peripherals and helper functions for accessing core registers), as part of the “CMSIS” software pack.
  • A “main.c” file containing the definition of the main() function, located in the “RTE/Device/STM32L475VGTx/STCubeGenerated/Core/Src” folder.

The files contained in the project also define the way the application will be started on the device. This boot sequence is explained in more details in the next section.

How is the main function eventually called?

As explained above, after CPU reset, the reset handler function is executed. By reading the code of the Reset_Handler function, you can observe that it first calls the SystemInit and then the __main functions.

startup_stm32l475xx.s
; Reset handler
Reset_Handler    PROC
                 EXPORT  Reset_Handler             [WEAK]
        IMPORT  SystemInit
        IMPORT  __main

                 LDR     R0, =SystemInit
                 BLX     R0
                 LDR     R0, =__main
                 BX      R0
                 ENDP

The SystemInit() function is defined in the “system_stm32l4xx.c” file. The __main() function is the C library entry point. It should be distinguished from the application main() function and it does eventually call the main() function. It is important to note that after reset, Cortex-M4 processors are in Thread mode and priority is Privileged.

Note

Above it is stated that the function __main() is the entry point to the C library. Relevant details about this function are available in the following ARM application note.

Exercise: Startup code

Create your own application

As explained above, your project contains a “main.c” file that defines the application main() function. Based on the “blinky” program, you must now modify your application for reproducing the blinky behavior. With Keil RTX5, the way the system and the kernel need to be initialized and started is explained in full details here. Read the “System Startup” section carefully before proceeding further.

Note

Before modifying files that have been generated automatically, it is important to note that any change should be done within the space reserved for USER CODE. By doing so, you ensure that the files can be generated again while preserving the changes that you made. An example of the annotation for user code is shown below:

/* USER CODE BEGIN Includes */

/* USER CODE END Includes */

For our application, changes can be accomplished with the following steps:

  • Include the required header files at the top of the “main.c” file:
    /* USER CODE BEGIN Includes */
    #include "cmsis_os2.h"
    #include "RTE_Components.h"
    #include "cmsis_vio.h"
    /* USER CODE END Includes */
    
  • Add the calls for initializing the configured VIO peripherals in the main() function (after clock and peripherals initialization):
      /* USER CODE BEGIN 2 */
      // Initializes VIO
      vioInit();                     
      /* USER CODE END 2 */
    
  • Add a function named app_initialize() that creates an additional thread similar to the thread used for blinking the LED, together with the thread function itself:

    /* USER CODE BEGIN 0 */
    
    /*---------------------------------------------------------------------------
      thrLED: Thread function for blinking the LED
     *---------------------------------------------------------------------------*/
    __NO_RETURN static void thrLED (void *arg) {
      uint32_t active_flag = 0U;
    
      (void)arg;
    
      for (;;) {
            // Switch LED0 on
        vioSetSignal(vioLED0, vioLEDon);
            // Delay 500 ms
        osDelay(500U);
            // Switch LED0 off      
        vioSetSignal(vioLED0, vioLEDoff);
            // Delay 500 ms
        osDelay(500U);                            
      }
    }
    
    /*---------------------------------------------------------------------------
     * Application initialization
     *---------------------------------------------------------------------------*/
    void app_initialize (void) {
      osThreadId_t tid_thrLED = osThreadNew(thrLED, NULL, NULL);
        if (tid_thrLED == NULL) { 
          Error_Handler();
        }
    }
    
    /* USER CODE END 0 */
    

  • In the main() function, add the calls for initializing the kernel (osKernelInitialize()), for initializing the application (app_initializhe()) and for starting the kernel (osKernelStart())):

      /* USER CODE BEGIN 2 */
      ...
    
      // Initialize CMSIS-RTOS2
      osKernelInitialize();
      // Initialize application
      app_initialize();
      // Start thread execution
      osKernelStart();            
      ...
      /* USER CODE END 2 */
    

If you apply the changes above, build your application and flash the target device, you should observe that the green LED blinks.

Note

At this point, it is important to note that the main() function cannot execute the thrLED function itself, because it needs to execute the osKernelStart() function that does not return in case of successful execution.

Question

What would happen if you forgot to include the following statement in “main.c” or in another file that makes use of Run-Time environment components?

  #include "RTE_Components.h"

Calls to kernel functions

With Keil RTX5, when a task need to access a kernel function, it calls a osXXX() function that is part of the kernel public API. Any public API function will perform some operations and eventually make one or several service calls. A service call is a way for a task to access kernel objects using an SVC exception. This is illustrated in the following picture (source: https://arm-software.github.io/CMSIS_5/latest/RTOS2/html/pStackRequirements.html):

A service call is thus a way for a task to access kernel objects through a SVC exception that is served by the SVC_Handler. Given that Cortex-M processors have two stack pointers:

  • the Main Stack Pointer (MSP) is used when the processor is in Handler mode or in Thread / Privileged mode and
  • the Process Stack Pointer (PSP) is used when the processor is in Thread / Privileged & Unprivileged mode.

this increases system safety since two separate stacks are used for kernel and task operations.

Enabling event recording

Event recording is a very powerful for analyzing the dynamic behavior of a program. This is of great importance when developing real-time systems and software. ARM Keil and Keil RTX5 offer an EventRecorder mechanism for recording all activities of the kernel. This allows, for instance, to understand when a context switch occurs or when a mutex object is locked.

For enabling event recording, you first need to install the ARM:CMSIS-view pack, select and activate the associated run-time component, as illustrated in the figures below:

Install the CMSIS-View component: Select the CMSIS-View component (version 1.2.0): Activate the EventRecorder runtime component:

You must then configure event recording properly using the “RTX_Config.h” file located in the “CMSIS” folder, as follows (note that if you are using Keil, you may apply the changes through the Configuration Wizard):

//   <e>Global Initialization
//   <i> Initialize Event Recorder during 'osKernelInitialize'.
#ifndef OS_EVR_INIT
#define OS_EVR_INIT                 1
#endif
//     <q>Start recording
//     <i> Start event recording after initialization.
#ifndef OS_EVR_START
#define OS_EVR_START                1
#endif
You also need to enable the desired RTOS Event generation, as illustrated here:
//     <q>Thread
//     <i> Enables Thread event generation.
#ifndef OS_EVR_THREAD
#define OS_EVR_THREAD               1
#endif

If you rebuild your application and start a debugging session, you should be able to watch events in the Event Recorder window, as illustrated below:

The Event Recorder window can be opened by selecting it from the corresponding button in the toolbar:

Note

You may probably get a message indicating that the event recorder is not located in unitialized memory upon starting the debugging session. In this case, you need to follow the steps documented in here, by applying the following memory configuration for your target: .

The Zero Initialized Data section of the Event Recorder component must correspond to the section marked as “NoInit” in your memory configuration.

Finally, you can experience the different dynamic behavior analysis tools available such as the system analyzer or the event statistics to better understand what your application does. These tools are very important elements of the toolbox of any serious embedded system developer as they allow an understanding of the dynamic behavior of real-time software systems.

Question

You may quickly realize that the timestamps shown in the Event Recorder are incorrect - they do not correspond to real measured times. For getting correct times, you need to configure the EventRecorder Time Stamp Setting, as demonstrated here.

Hint: one possible configuration is to use the “CMSIS-RTOS2 System Timer”. With this configuration, the EventRecorder uses the kernel osKernelGetSysTimerFreq() and osKernelGetSysTimerCount() functions. If you choose to use this configuration, you then need to figure out what is the correct Time Stamp Clock Frequency to use. Note that you may also use another configuration.

If you configure the EventRecorder properly, the timestamps displayed in the EventRecorder window should be correct.

Event recording: going beyond

Later in this course we will look deep into event analysis. In order to help so doing so, we will use a dedicated tool for the purpose: Percepio® Tracealyzer.

You will get a license key that you can use in the scope of this lecture on the Teams channel. The link for downloading the latest version of the tool for Windows is download Tracealyzer for Windows.

Note

For getting correct timestamps when using Tracealyzer, you need to configure the Core Clock used for tracing with the Cortex-M Target. For this purpose, you must choose Options -> Debug -> (ST-Link Debugger) Settings -> Trace and enter the correct Clock settings as illustrated below: