In this article we are going to take a deep look to ChibiOS default demos explaining how they works. We will also see how to create a new project and how to modify it in in order to create our own applications.
ChibiOS projects’ anatomy
ChibiOS default demo are usually composed by some different folders and files. As example, in figure we can see the resources of the default demo for STM32 Nucleo F401RE. In general all the ChibiOS’ projects are characterised by a similar anatomy.
They all have some folders, some configuration headers, a source file named main.c and a makefile. Additional notes about the demo are usually reported in a “read me” file which is actually a plain text file (readme.txt).
Some of the folders contained in our project are actually links. You can notice they are, because there is a small arrow on their icons in the bottom left corner.
These folders are usually shared between different demos and projects. This means that editing a file within these folders we will affect more than a project. In general:
It is not a good idea to edit the linked folders and their content.
Let’s make an example to clarify this concept. The os folder is linked to the folder C:\ChibiStudio\chibios176\os. In our case, it links to ChibiOS176 because, during the previous article (Developing on STM32: introducing ChibiStudio), we have imported demos from this release. Anyway, it contains the ChibiOS’s sources: if we edit this code we can potentially broke the code and, thus, every project linked to this folder.
If you broke ChibiOS accidentally editing its code you can always restore it downloading the latest relase from Sourceforge and manually replacing the broken folder with the new one taking care to preserve the folder name and its hierarchy.
The debug folder
The debug folder instead is a real folder. It is specifically related to our project because it contains a .launch file. We already have used this file before: do you remember the steps required by flash and run? Just after the start-up of OpenOCD, we have to select the the Launch Configuration from the the Debug Menu. This .launch file represent the Launch Configuration.
The Launch Configuration contains some information about the flash and run procedure required from the debugger. It is actually an XML file contained in the debug folder.
Each project comes with some configuration headers. All the configuration contained in these files are actually pre-processor directives (a series of constant definitions and macros). To act on these configuration we usually have to edit these files, save them and recompile the entire project.
Working with embedded systems,it is extremely usual to deal with textual configuration headers. From here and out, we will often talk about pre-processor switches. A switch is just a boolean constant definition which include/exclude piece of code from the compilation process. As example what follows are two switches: the first one is enabled and includes all the code related to Serial Driver, the second one is disabled and excludes all the code related to PWM Driver.
These switches are related to HAL drivers and are often called Driver Switches. In general pre-processor switches are very important because they allow to remove entire sub-module from our binary when we do not need them.
If our aim is to create a marketable product based on embedded systems we need to pay attention to resource usage. If our embedded software uses less resources we can use a smallest MCU, and this mean that our product will require smaller memories, lower clock frequencies and most likely will require less energy: this means it will be cheaper, smaller, thinner and thus more competitive.
In embedded systems, the less resources are used the more cheap the hardware will be. Cheaper hardware means lower final price and higher profit. The software is a key point in this philosophy!
ChibiOS offers a lot of mechanism to keep you code nimble and performance-oriented but there aren’t magic bullets: the user application must embrace the cause to keep up this ideology.
Kernel configuration header: chconf.h
The chconf.h contains mainly setting related to kernel: there are some settings related to system timer and kernel parameters, a lot of switches to enable kernel features and some interesting hooks. This file is configured to suite a generic case of use. Except for debugging features, we will rarely edit it, especially in these basic tutorials.
Note that even if the kernel configuration header for ChibiOS/RT and ChibiOS/NIL is named at the same way, configurations are extremely different.
ChibiOS/RT comes with a series of debugging features which are very useful during the development phase. By default these features are disabled because when enabled kernel execute a big number of additional check: this introduces a reduction of performances in exchange of a debugging aid. Usually these feature should be enabled when needed and disabled before to deploy our firmware. We take a deeper look to this part later.
Here reported a piece of chconf.h containing a series of switch related to debug options.
HAL configuration header: halconf.h
This halconf.h contains all the configuration related to ChibiOS/HAL drivers. Acting on this file, it is possible to enable/disable entire drivers: this usually reduces the size of the final firmware and thus the size of flash memory occupancy.
Remember to disable unused driver in halconf.h before to deploy your firmware.
We will often act on halconf.h to enable/disable driver. Note that disabling a driver which is actually used the build procedure will fail with a lot of error: this because when a driver is disable it will be excluded from compilation and all its APIs will become unavailable.
What follows is an excerpt of halconf.h. In this example PAL Driver and CAN Driver are enabled, ADC Driver and DAC Driver are disabled.
MCU configuration header: mcuconf.h
The MCU configuration header contains all the configuration strictly related to our MCU. This file is extremely hardware dependent: it is not so unusual that STM32 belonging to the same sub-family have a different mcuconf.h.
This file contains configurations for the whole clock tree, plus a series of configuration related to DMA, IRQ priorities, peripheral assignments and much more.
We will often act on peripheral assignments. ChibiOS/HAL drivers rely on hardware peripherals and often different drivers relay on the same kind of hardware, so
To use a ChibiOS/HAL driver, usually we have to enable it in halconf.h and assign it an hardware peripheral in mcuconf.h
Don’t worry if this concept is not completely clear right now: it will be clarified later when we will properly introduce ChibiOS/HAL drivers.
We have already introduced the makefile: it is a script file which contains a series of directives used by GCC make to build our code. It has a syntax which is completely documented on the GNU make manual. For our purposes we will edit it only partially so do not worry to much about that: we will face this problem at proper moment.
The most important part of our project is definitely the main.c source file. In this source file there is the application entry point. We will spent most of our development phase on that file.
Just a note, the main.c file contains a namesake function int main(). In what follows I will refer to the whole file as main.c and to the contained function as main().
A more detailed look to default demo
In the previous article we have already got a glimpse of ChibiOS demos for STM32. We have used both STM32 Nucleo F401RE and STM32F3 Discovery kit, and related projects which are RT-STM32F401RE-NUCLEO64 and RT-STM32F303-DISCOVERY-REVC. After the flash and run we have resumed the code execution and the one or more LEDs started to blink on our development board.
Actually, the default demo is able to do more than blink a LED. Indeed, that demos executes also a test suite which prints data on a serial port. We can try this demo relaunching the demo. In brief steps are:
- Execute again the flash and run of the default demo for your development board;
- In the Debug perspective press the Resume button to run the code execution: one or more LEDs will start to blink;
- In the device manager, under LPT and COM Ports check the COM port assigned to the STMicroelectonics STLink Virtual COM port;
- In the Debug perspective, in the bottom part, open the terminal tab and connect to that COM port with default configurations (Bad rate 38400, Data bits 8, Stop bit 1, no parity, no hardware flow control);
- Press the User Button and the test suite will be executed printing test results on terminal.
Lets take a look to this short video in which we are using STM32 Nucleo F401RE:
A brief explanation
What we have just seen in the previous video is not immediate but is even enough complicated to explain that it would require a separate article. I will explain this in detail in an article about serial communications but right now let me barely introduce this mechanism.
In the first article of this series, we have seen that STM32 is equipped with a lot of peripherals. A very common peripheral is the Universal synchronous/asynchronous receiver/transmitter also known as USART, a peripheral capable to implement a standard serial protocol communication known as RS232. This standard was largely used in old Computers and is commonly known as COM port.
The ST-Link V2-1 offers some additional features in comparison to ST-Link V2: one of this is the STLink Virtual COM port. The debugger is physically connected to on of the USARTs of the STM32 and acts as a bridge between the PC and the STM32 emulating a COM port over the USB.
To make things easier, connecting the ST-Link V2-1, we will see the STLink Virtual COM port under device manager (identified by a progressive number) connecting to this port using a terminal we can exchange strings with our STM32.
In a future article we will see how to this mechanism could be used to print escaped strings on the terminal like we usually do with the printf function.
Into the code
In what follow we will refer to the demo RT-STM32F401RE-NUCLEO64 but what we are saying is applicable as well to every demos for STM32 (to be clear that which are under the folder \demos\STM32).
Something about the multi-threading
It should be clear that this demo performs two different tasks apparently at the same moment:
- It blinks the Green LED with a precise cadency;
- It constantly checks the User button status and if pressed starts the ChibiOS’ Test Suite.
The management of more tasks which apparently are executed at the same time is easily achievable by user. One of most important feature of ChibiOS/RT is the multi-threading. Oversimplifying, a thread could be imagined like a sequence of instructions and multi-threading means kernel can manage more than a thread independently, executing them in a parallel fashion (apparently simultaneously) even if actually the kernel is running on a single core processor. This is possible because ChibiOS/RT plans an operation sequence: this task is commonly called scheduling.
In ChibiOS/RT a thread is actually a function. When it is started, the thread is associated to a priority level and to a working area. The working area is a piece of memory dedicated to our thread and priority level is a relative number which we can use to influence the scheduling. Priority follows a simple rule:
Among all the threads ready for execution, the one with the highest priority is the one being executed, no exceptions to this rule.
I have also written a detailed article about ChibiOS/RT multi-threading in case you are interested on this topic. It is important to understand the working principle of ChibiOS/RT’ scheduling and understand how to choose a proper priority level for each thread. This is more true in case we need to make the maximum use of our MCU. Anyway, at this level, our MCU is so underutilised that we can almost ignore these details and we can choose priority levels sloppy applying this simple rule:
Every thread must have a sleep or a suspending function inside its loop. In this way the CPU ownership is switched among threads and the scheduling can proceed.
ChibiOS/RT offers different sleep functions but the one suitable in many scenarios certainly is
This function suspend a thread for <xxx> milliseconds, where the argument is an unsigned integer. To conclude this brief introduction to multi-threading this piece of code is a piece of the main.c of the demo RT-STM32F401RE-NUCLEO64.
In this case we are declaring a working area named waThread1 having size 128 bytes. We are also declaring a function named Thread1 which in its loop:
- turns off the green LED,
- sleeps for 500 ms,
- turns on the green LED,
- sleeps for 500 ms,
- turns off the green LED,
- sleeps for 500 ms,
- turns on the green LED,
- … (continues indefinitely)
Note that the function
is executed only once before the loop and has debugging purposes: with this function the thread assigns itself an identificative. We will deepen this point later.
Note also that these lines just declare a space memory and a function. In simple words this code in not enough to create a thread. As we will see the thread creation happens into the main().
In brief we need to know that:
allocates a working area where <name> is its identifier and <size> its size expressed in Bytes.
declares a function where <name> is its identifier <args> is a argument passed to the function and <code> its implementation.
For who is interested in details, the working area and the function declaration is performed through these macros because some architecture-dependent details are hidden inside. As example, the working are is an array bigger than the size indicated in the macro.
This because the parameter <size> represent the user’s usable part of the working area. Again for the ARM Cortex-M architecture functions must maintain an eight-byte aligned stack address. This detail and others are hidden in that macro.
About the argument of the tread function, it could be used to create a parameterized function: this allow to create more than a thread using the same function. These details are not relevant at this moment but we will surely deepen this point later.
Right below the thread function declaration there is the main() which is the application entry point: we could consider this like the starting point of our code execution.
When the application starts the two functions (halInit() and chSysInit()) perform the system initialisation: while halInit() is an API of ChibiOS/HAL and initialises the HAL subsystem, chSysInit() is an API of ChibiOS/RT and initialises the kernel.
After after chSysInit() the main() becomes a thread itself and another thread named idle is created: idle is a dummy thread which runs when all the other threads are not ready to run.
It is important that these halInit() and chSysInit() are executed in the beginning of main(). Using any API before of them will cause a system crash.
Note that every application based on ChibiOS/RT and ChibiOS/HAL begins in the same way. The code right after these lines is instead application-depend. In case of our demo (RT-STM32F401RE-NUCLEO64) we have
Ignoring details, this code:
- initialises the Serial Driver 2 with a default configuration;
- creates the blinker thread (Thread1);
- enters in the main() loop. Here it checks the User Button: when it is pressed it launches the ChibiOS’ Test Suite over Serial Driver 2.
As we can see the function and the working area we have declared before are used in main() to create the thread.
This API is commonly used and requires 5 arguments:
- A pointer to the working area (waThread1, the one we have declared before),
- The size of the working area,
- The priority of thread,
- A function to execute as a thread (Thread1, which has been declared before),
- A parameter which will be passed to the Thread1 function (in this case we are passing nothing).
After the execution of this code our kernel have to manage three threads: main, Thread1 and idle.
To conclude let’s take a look to the code of the demo RT-STM32F303-DISCOVERY-REVC
The main difference we can see is that here there are 8 LEDs which are managed through 2 threads. The interesting part is that each thread manages 4 LEDs and nonetheless the game light remains in sync along time. This is due to the reliability of hard real-time scheduling: ChibiOS/RT’s is an hard real-time operating systems: this means that its code execution is deterministic and predicable.
An interesting practice could be to make out all the differences between the two proposed demos: note that most of them depends on board differences.
Creating our first project
Until now we have seen how to import existing demos and how to flash and run them. The development process requires actually the creation of new projects. The best way to do that is to duplicate the default demo for your development board.
This can be done opening the demo, copy and paste it (CTRL + C and CTRL + V) and choosing a new name. To complete the procedure we have also to change a relative path in the makefile otherwise the new project will not compile.
In short explanation is the you have to change this
The long explanation is that CHIBIOS is a relative path. A relative path is a way to specify the location of a directory relative to another directory. Talking of relative paths the symbol “.” (dot) means this folder, the symbol “..” (two dots) means the parent folder.
In this case CHIBIOS represent the path of the ChibiOS main directory (C:\ChibiStudio\chibios176) reported to the project path (C:\ChibiStudio\chibios176\demos\STM32\RT-STM32F401RE-NUCLEO64) hence in this case the absolute path is equal to three steps up (../../..).
After the copy, the newly created project will be placed in our workspace (C:\ChibiStudio\workspace_user\<name of the new project>\) hence the new relative path will be two step back then enter in the folder chibios176 (../../chibios176).
Duplicating a default project we have to edit makefile and fix the CHIBIOS relative path otherwise the new project will not compile.
After this modification we will be able to compile our new project but we will not be able to flash and run it without editing the Launch Configuration. Duplicating the project we have also duplicated the folder named debug which contains the .launch file: unfortunately this file is still associated to the old project. To fix this we have to open the launch configuration file search the name of the old project and replace it with the name of the new project: it should replace three times.
It is a good idea to rename the Launch Configuration to avoid confusion. Indeed, in the Debug Menu, we can see the launch configuration of all projects currently open. If the old project and the new one are simultaneously open we will see two entries having the same name in the menu.
Duplicating a default project we have to edit the Launch Configuration to make it point to our new project. It is also a good idea to rename it using the name of the new project to avoid confusion.
Let us resume steps required by this procedure:
- Open the default demo for your development board;
- Copy and paste the demo choosing a name for the newly created demo;
- Open the makefile, and fix the CHIBIOS relative path;
- Rename the debug configuration using the name of the new project instead of the starting one;
- Open the debug configuration, search and replace the name of the old project with the new one: it should replace three times.
The following video will dissipate all doubts
Important notes about Flash and Run
In the previous article we already talked about launching OpenOCD and the flash and run procedure. I would like to add something: when we develop a new project, every time we edit our code, we need to re-flash our microcontroller with the newer version of the firmware.
We have not to relaunch the OpenOCD, even more it is not possible to launch OpenOCD twice. If we try to launch OpenOCD when it is already running we will receive an error.
We cannot even relaunch the flash and run if we do not terminate the previous instance. To do this we have to right click on the previous instance of our debug section in the Debug Window and choose Terminate and Remove.
We can recognise the old instance because it has the same name of the Launch Configuration we have used to start it. Note that is possible to terminate and remove it also selecting it and pressing DEL on the keyboard.
Right now this is all we need to know, anyway we will deepen this argument in a future article dedicated to debugging.
- ChibiOS projects’ anatomy
- A more detailed look to default demo
- Into the code
- Creating our first project
- Important notes about Flash and Run