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.
All demos 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\chibios182\os. In our case, it links to ChibiOS182 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.
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 shall be designed properly to ensure optimisation.
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 aids. Usually these feature should be enabled when needed and disabled before to deploy our binary. We will 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 is completely excluded from compilation and all its APIs becomes unavailable.
What follows is a piece 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 very usual 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, thus
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: we will clarify those statement in a later moment 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 this 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. 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 (Baudrate 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 the ChibiOS test suite which is executed and prints its result over a virtual COM port: this is possible because the ST-Link V2-1 bridges one of the UARTs of the STM32 to the PC emulating a COM port over the USB: this is know as STLink Virtual COM port.
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.
Thanks to the bridge it also possible to exchange characters between a terminal and our STM32. In a later article we will see how to used this mechanism to print escaped strings on the terminal like we usually do with the printf function in C.
Something about the multi-threading
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 printing on a UART.
The management of more tasks apparently executed at the same time is easily achievable by user with an RTOS. 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 sequence of instructions independently, executing them in a parallel fashion even if actually the kernel is running on a single core processor. This is possible because the RTOS 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. If we are designing a complex application performance-oriented it is important to understand the working principle of ChibiOS/RT’ scheduling. Anyway, at this level, our MCU is so underutilised that we can almost ignore these details and we can choose priority levels 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
Into the code
In what follow we will refer to the demo RT-STM32F401RE-NUCLEO64 but what the following concepts are applicable to every demos for STM32 which, as reminder, are available under the folder \demos\STM32.
Browsing main.c we can easily see that it is composed by two main pieces:
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.
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. Note that every application based on ChibiOS/RT and ChibiOS/HAL begins in the same way.
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 ChibiOS API before of them will have an unwanted behaviour probably causing a system crash.
Considering the RT-STM32F401RE-NUCLEO64 demo, right after the initialisation we have:
- 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 the blinker thread is actually created inside the main using the API chThdCreateStatic()
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.
Comparing two different demo
To conclude this code walkthroug, let’s take a look to the code of the demo RT-STM32F303-DISCOVERY
The main difference is that the Discovery has 8 LEDs which here 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 considered an hard real-time operating systems: this means that its code execution is deterministic and predicable.
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.
The short explanation is: in the makefile 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 “..” (double dots) means the parent folder.
In this case CHIBIOS represent the path of the ChibiOS main directory (C:\ChibiStudio\chibios182) reported to the project path (C:\ChibiStudio\chibios182\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 chibios182 (../../chibios182).
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.
Remember to rename the Launch Configuration to avoid confusion between projects.
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. If we follow each time the flash and run procedure largely described we would attempt to launch OpenOCD more than one but this is not possible.
We cannot even relaunch the flash and run if we do not terminate the previous instance. So what is the right way?
You can notice that in the Debug windows you have two instances: the OpenOCD and the current debugging section (has the name of launch configuration used to start debugging).
You do not need to terminate OpenOCD unless:
- You have disconnected your board (in this case you will see OpenOCD which looks for the target continuously polling it and a lot of red lines in the Console windows).
- You have to change board.
- You have powered off the USB (e.g. sleep or hibernate your PC)
You have to terminate current debugging section: to do this right click on the previous instance of your debug section in the Debug Window and choose Terminate and Remove (as in Fig. 2).
Now you can launch a new Debug Configuration.
- ChibiOS projects’ anatomy
- A more detailed look to default demo
- Into the code
- Creating our first project
- Important notes about Flash and Run
- Using STM32’s GPIO with ChibiOS’ PAL Driver (next article)
- Developing on STM32: introducing ChibiStudio (previous article)