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
- You need to have completed the getting started with keil codelab.
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:
- Use Keil as documented in the getting started codelab. You need to open the project and build the application.
- Use cmsis-tools. For using it, you need to:
- Install the required tools as documented here. Should you have installed ARM Keil already, make sure to read the note below.
- 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
andninja
(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 reset handler that is executed after CPU reset and typically calls
the
- A “system_<device>.c” file containing the definition of the
SystemInit
andSystemCoreClockUpdate
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.
; 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 themain()
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
// <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: