Introduction
L'objectif de cet article est la présentation d'un environnement de programmation open source pour les microcontrôleurs de type STM32. L'environnement se base sur les outils de GNU/Linux en ligne de commande et apporte une alternative aux outils proposés par ST-Microelectronics (dont ST-CubeIDE).
Le dépôt du framework est disponible sur Github : https://github.com/Openwide-Ingenierie/generic-microcontroller-project.git.
1. Présentation Générale
L'environnement de programmation (aussi appelé framework de build) regroupe un certain nombre d'éléments à la fois matériel et logiciel. La cible est un microcontrôleur de type STM32 qui est programmable sur une carte d'évaluation de type Nucléo. Pour illustrer le fonctionnement du framework, l'exemple le plus simple sera utilisé : le clignotement d'une LED. Mais il est d'abord important de connaître la structure du logiciel embarqué qui sera chargé dans le microcontrôleur.
1.1. Structure d'un logiciel embarqué
Le logiciel embarqué respecte une architecture où différentes couches d'abstraction se superposent :
- Hardware : la carte d'évaluation qui contient le microcontrôleur.
- HAL (Hardware Abstraction Layer) : contrôler et piloter les registres bas niveau (niveau microcontrôleur).
- BSP (Board Support Package) : gérer les composants périphériques (niveau carte d’évaluation).
- Third-Party : FreeRTOS dans notre cas qui est un système d'exploitation pour l'embarqué.
- Application Layer : le code applicatif.
Pour avoir plus d'informations sur la carte d'évaluation et le microcontrôleur, des documents précieux pour le développeur sont disponibles comme :
- Datasheets : décrit le matériel et les liaisons physiques entre les éléments du microcontrôleur et la carte d'évaluation.
- Reference Manual :
- Proposé par ST : décrit les registres bas niveau pour contrôler les périphériques et les mémoires du microcontrôleur.
- Proposé par ARM : décrit le langage assembleur interprétable par le processeur.
1.2. Carte d'évaluation : Nucléo-F446RE
Cette carte permet de faire du prototypage rapide en intégrant différents éléments électroniques comme :
- Le microcontrôleur cible (U5)
- Un ST-Link (partie supérieure de la carte et comportant un microcontrôleur U2)
- 2 Boutons-poussoirs (B1 et B2 dont un programmable)
- 3 Leds (LD1, LD2 et LD3) => LD2 est la LED programmable.
- Des connecteurs permettant d'insérer facilement des périphériques externes (CN5-10)
Ces spécifications sont décrites dans le User Manual spécifique à la carte d'évaluation : UM1724.
Lien pour trouver la carte d'évaluation sur le site du constructeur :
https://www.st.com/en/evaluation-tools/nucleo-f446re.html
1.3. Microcontrôleur : STM32F446RE
Notre logiciel embarqué sera stocké et exécuté par le microcontrôleur.
Le microcontrôleur comprend plusieurs éléments qui le caractérisent (il en existe de nombreuses autres versions) :
- Un processeur de type ARM Cortex-M4 avec 512kB de mémoire Flash (ROM) et 128 kB de mémoire SRAM (RAM).
- Des mémoires externes de type SDRAM.
- Des protocoles de debug : JTAG et SWD.
- Une RTC (Real Time Clock) pour stocker la date et l'heure.
- Des horloges externes.
- Des périphériques de communication : UART/SPI/I2C/CAN.
- Des entrées et sorties digitales et analogiques (GPIO).
Remarque : Notre LED sera pilotée par l'accès à sa GPIO.
1.4. HAL : Libopencm3
Comme vu précédemment, la HAL est une couche bas-niveau qui permet une utilisation des périphériques du microcontrôleur sans passer directement par la manipulation des registres.
Libopencm3 est une HAL open source, libre et sous licence LGPLv3. Elle est encore en cours de développement mais écrite de zéro à partir des documents constructeurs (Datasheets et Reference Manual de ARM et de ST). Elle succède à un ancien projet baptisé “libopenstm32” (comme son nom l’indique c'était une HAL libre pour les microcontrôleurs de ST) mais en élargissant son champ aux autres microcontrôleurs possédant un processeur du type ARM Cortex-M3 (les Atmel SAM3U par exemple) et même certains microcontrôleur possédant un processeur M0, M4 (comme le STM32F446RE).
Elle a aussi été conçue pour utiliser les outils GNU/Linux tels que le compilateur GCC pour ARM et les outils de flashage de la carte (OpenOCD).
1.5. OS embarqué : FreeRTOS
Le microcontrôleur peut être programmé en baremetal ou avec un système d'exploitation (OS) pour des systèmes plus complexes. FreeRTOS a été lancé par Richard Barry en 2003 mais le projet est géré par AWS (Amazon Web Service) depuis 2017 environ. C’est un des systèmes d’exploitations les plus utilisés dans le milieu des microcontrôleurs. Il est portable (33 architectures supportées), très léger (entre 4kB et 9kB), versatile et le noyau (en anglais kernel) est sous licence MIT. Des versions commerciales existent avec OpenRTOS et SafeRTOS qui sont basées sur le noyau de FreeRTOS. De nombreuses librairies et supports sont proposés par AWS pour faciliter l’interface du système embarqué avec le cloud d’AWS.
L’OS apporte la possibilité de créer des tâches puis de les ordonnancer en fonction de leurs priorités (si il est préemptif) grâce à l'ordonnanceur (en anglais scheduler) qui fonctionne en “Round-Robin” (une liste d’attente circulaire). Les interruptions ne sont pas gérées par le noyau de l’OS mais des fonctions spécifiques sont présentes pour lui permettre son interfaçage.
L'image ci-dessous permet d'avoir un aperçu du déroulement des tâches dans l'OS en fonction du temps.
2. Configuration du Framework
Le framework dispose d'une configuration de base qui peut être très facilement adaptable et personnalisable. De plus, la HAL ou l’OS peuvent être modifiés avec les règles écrites dans les fichiers de configuration (et les règles Makefile). Pour plus d’informations, il faut se référer au fichier README.md.
2.1. Arborescence du projet
Le projet s'organise en plusieurs dossiers et fichiers. L'application se trouve dans les fichiers blink.c et blink.h mais il est tout à fait imaginable (et même conseillé) de créer un nouveau dossier APP pour contenir les fichiers (sources et headers) lorsque le projet devient plus imposant. Les autres dossiers et fichiers sont pour la configuration du framework. Le fichier Makefile apporte les règles pour automatiser certaines tâches (tout comme le fichier config.mk qui seront présentés plus tard en détail). Les dossiers HAL et OS contiennent également des règles pour interfacer les sources spécifiques (libopencm3 et FreeRTOS dans notre cas).
.
├── blink.c
├── blink.h
├── Makefile
├── config
│ ├── config.mk
│ ├── FreeRTOSConfig.h
│ └── openocd.cfg
├── HAL
│ ├── hal.mk
│ ├── libopencm3
│ └── rules
│ ├── libopencm3.mk
│ └── template.mk
├── OS
│ ├── FreeRTOS
│ ├── os.mk
│ └── rules
│ ├── baremetal.mk
│ ├── freertos.mk
│ └── template.mk
└── README.md
2.2. Configuration de la Toolchain
Le fichier config/config.mk permet de rajouter des règles Makefile adaptées à l’environnement matériel : type de MCU, interface de programmation, etc...
# MCU definitions
MCU_TYPE=stm32
MCU_FAMILLY=f4
MCU_CORE=cortex-m4
MCU_PRECISION=46re
# Flash/debug tool configuration
INTERFACE=stlink-v2-1
TRANSPORT=hla_swd
TARGET=$(MCU_TYPE)$(MCU_FAMILLY)x
# firmware name
FIRMWARE=firmware.elf
# build dir name
BUILD_DIR=build
RELEASE_DIR=$(BUILD_DIR)/release
DEBUG_DIR=$(BUILD_DIR)/debug
# HAL (libopencm3)
HAL=libopencm3
# OS ("FreeRTOS" or "baremetal")
OS=FreeRTOS
# FreeRTOS config
# heap implementation
FREERTOS_HEAP_IMPLEM=4
# MCU port
FREERTOS_MCU_PORT=GCC/ARM_CM4F
# config file
FREERTOS_CONFIG=config/FreeRTOSConfig.h
# linker script name
LINK_FILE=$(LDSCRIPT)
# openocd config file
OPENOCD_CFG_FILE=config/openocd.cfg
# cross compile prefix
CROSS_COMPILE=arm-none-eabi-
# compilation flags
CC_OPT=-Os
CFLAGS=-W -Wall -Wextra
CFLAGS_DEBUG=-g -gdwarf-4
CC_DEFINES=
# link flags
LD_OPT=-flto
LDFLAGS=-Wl,--print-memory-usage,--gc-sections \
-nostartfiles -lc -lgcc -lnosys -Xlinker -Map=output.map
# arch specific flags
ARCH_FLAGS=-mcpu=$(MCU_CORE) -mthumb -mfloat-abi=hard -specs=nano.specs
Le compilateur GCC permet d’afficher des informations utiles au développeur lors de la compilation comme par exemple la taille en mémoire avec les flags suivants :
-Wl,--print-memory-usage # Permet d’afficher à la compilation la taille de la mémoire utilisée (RAM et ROM)
Memory region Used Size Region Size %age Used
ram: 15704 B 128 KB 11.98%
rom: 7660 B 512 KB 1.46%
Remarque : Avec un code minimal, libopencm3 et certaines fonctionnalités de FreeRTOS, l'empreinte en mémoire est d'environ de 20 kB.
-Map=output.map # Permet de générer le fichier de mapping
De nombreux flags existent pour optimiser le code ou avoir des informations supplémentaires, l’article suivant en mentionne :
- https://interrupt.memfault.com/blog/best-and-worst-gcc-clang-compiler-flags
- https://gcc.gnu.org/onlinedocs/gcc/
2.3. Configuration de Libopencm3
L'étape de configuration de la HAL libopencm3 est effectué de manière automatique par les règles du Makefile comme expliqué précédemment. Cependant pour comprendre comment le framework récupére les bonnes informations, il faut se pencher sur le script de configuration présent dans libopencm3, ce script génère automatiquement le fichier linker spécifique à notre microcontrôleur avec le chargement de la configuration mémoire et du bootloader (generated.stm32f446re.ld).
- libopencm3/ld/devices.data : une base de données avec les informations sur les microcontrôleurs.
- libopencm3/linker.ld.S : le script de configuration.
stm32f446?e* stm32f4 ROM=512K RAM=128K
Plus d’informations sur la configuration et le fonctionnement :
https://interrupt.memfault.com/blog/how-to-write-a-bootloader-from-scratch
2.4. Configuration de FreeRTOS
La configuration de l'OS passe également par les règles du Makefile mais aussi par un fichier spécifique à FreeRTOS (config/FreeRTOSConfig.h), il permet de désactiver ou d'activer certains des paramètres propre à l'OS (https://www.freertos.org/a00110.html).
Les éléments importants du noyau de FreeRTOS sont les suivants :
- SystemCoreClock (variable globale à initialiser avec la HAL pour donner la base de temps à l'OS).
- Préemption ou non.
- Utilisation des mutexes ou non.
- Utilisation des timers logiciels ou non.
- Priorités des interruptions (très important pour les processeurs Cortex M) : https://www.freertos.org/RTOS-Cortex-M3-M4.html
- Mapping de certaines fonctions d’interruptions (utilisées par FreeRTOS).
- Gestion des “stack-overflows” des tâches : https://www.freertos.org/Stacks-and-stack-overflow-checking.html
3. Flashage et debug
Une fois la configuration effectuée, les outils logiciels sont mis en place et le développeur peur les utiliser à travers différentes commandes. Néanmoins, il est important de comprendre les liens qui existent entre la carte d'évaluation et le framework et comment ces éléments sont mis en place.
3.1. Automatisation
Le fichier Makefile permet de configurer des règles pour automatiser les actions de flashage, linkage, de debug et de compilation. Il permet aussi de définir l’arborescence du projet pour les fichiers sources et les headers (ici on a seulement les fichiers blink.h et blink.c à la racine du projet).
Les commandes importantes sont les suivantes :
$ make # compiler le projet
$ make flash # flasher le *.elf dans le microcontrôleur
$ make clean
$ make mrproper # nettoyer en profondeur le projet
$ make gdb # ouvrir une session de debug
3.2. STLink / SWD / OpenOCD
Le flashage et le debug du microcontrôleur regroupent des outils matériels et logiciels :
- STLINK : c’est une sonde qui permet de faire le lien physique entre le pc et le microcontrôleur. Sur la nucleo-board, elle est présente sur la carte et le développeur à seulement besoin de brancher un câble micro-usb sur le connecteur CN1 (un UART est également connecté pour l’échange de données). La sonde supporte plusieurs protocoles pour flasher/debugger le microcontrôleur comme le JTAG ou le SWD par exemple.
- SWD (Serial Wire Debug) : c'est une interface qui utilise seulement 2 signaux contrairement au JTAG qui en utilise 6. Cependant, le SWD est supporté par les processeurs ARM alors que le JTAG est plus universel dans le domaine des microcontrôleurs.
- OpenOCD : c’est simplement l’interface logiciel entre les signaux physiques et l'ordinateur.
Pour plus d’informations :
- https://developer.arm.com/architectures/cpu-architecture/debug-visibility-and-trace/coresight-architecture/serial-wire-debug
- https://www.linuxembedded.fr/2018/08/openocd-from-scratch
3.3. GDB
Le debug est une étape indispensable dans le développement d’un logiciel, l’opération se fait avec l'outil GDB dans notre cas.
$ make gdb
Au lancement du debugger, le programme commence à la fonction suivante :
reset_handler()
Pour aller à la fonction main :
gdb> break main
gdb> continue
Le tableau suivant propose un récapitulatif des commandes GDB très utiles :
Commandes | Arguments | Commentaires | |
q | quit | Quitter le débogueur. | |
c | continue | Exécuter le programme jusqu'au prochain point d'arrêt. | |
b | break |
function-name line-number |
Mettre un point d'arrêt sur une fonction ou une ligne du programme. |
watch | watchpoints | variable-name | Mettre un point d'arrêt sur une variable. |
n | next | Avancer pas-à-pas sans rentrer dans les sous-fonctions. | |
s | step | avancer pas-à-pas en entrant dans les sous-fonctions. | |
p |
variable-name function-name &variable-name &function-name |
Afficher la valeur d'une variable (& pour l'adresse de la variable). | |
p/x | print/x |
variable-name function-name &variable-name &function-name |
Afficher la valeur d’une variable en hexadécimal (& pour l’adresse de la variable). |
i | info |
b (or breakpoints) watch (or watchpoints) |
Afficher les points d'arrêts. |
bt | backtrace |
Afficher la pile d'exécution. |
|
CTRL+L |
Rafraichir la fenêtre de GDB. |
||
CTRL+C |
Arrêter le programme. |
Source : http://www.yolinux.com/TUTORIALS/GDB-Commands.html
4. Exemple
Le dépôt Github du framework contient deux fichiers blink.h et blink.c qui permettent de faire clignoter la LED2 présente sur la carte d'évaluation présentée précédemment (Nucléo-F446RE pour rappel).
Le fichier blink.h contient uniquement les déclarations de la GPIO relié à la LED2 :
#pragma once
#include "libopencm3/stm32/rcc.h"
#include "libopencm3/stm32/gpio.h"
#define LED_PORT (GPIOA)
#define LED_PIN (GPIO5)
#define LED_PORT_RCC (RCC_GPIOA)
#define BLINK_DELAY (200)
Le fichier blink.c contient la tâche FreeRTOS qui changera l'état de la GPIO pour faire clignoter la LED.
#include "libopencm3/stm32/rcc.h"
#include "libopencm3/stm32/gpio.h"
#include "libopencm3/cm3/systick.h"
#include "FreeRTOS.h"
#include "task.h"
#include "blink.h"
// needed by FreeRTOS
uint32_t SystemCoreClock;
static void led_blink_task(void * pvParameters) {
// avoid warning about unused parameters
(void)pvParameters;
while(1) {
gpio_toggle(LED_PORT, LED_PIN);
vTaskDelay(pdMS_TO_TICKS(BLINK_DELAY));
}
}
int main(void) {
// give the used system clock frequency to FreeRTOS
SystemCoreClock = rcc_ahb_frequency;
// systick configuration
systick_set_frequency(configTICK_RATE_HZ, SystemCoreClock);
// led port peripheral clock enabling
rcc_periph_clock_enable(LED_PORT_RCC);
// led pin configuration (output mode, low speed, push/pull output)
gpio_mode_setup(LED_PORT, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, LED_PIN);
// blinky task creation
TaskHandle_t led_bink_task_handle = NULL;
BaseType_t ret_status;
ret_status = xTaskCreate( led_blink_task,
( const char * ) "led_blink",
configMINIMAL_STACK_SIZE + 200,
NULL,
tskIDLE_PRIORITY + 1,
&led_bink_task_handle );
// start scheduler
if(ret_status == pdPASS) {
vTaskStartScheduler();
}
// should never fall here
while(1) {};
return 0;
}
De nombreux exemples existent sur internet (ou livres) pour utiliser les caractéristiques du microcontrôleur et de FreeRTOS :
- https://rhye.org/
- Beginning STM32 (Developping with FreeRTOS, libopencm3 and GCC) de Warren Gay.
- https://github.com/libopencm3/libopencm3-examples/tree/master/examples/stm32/f4
Conclusion
L'article a permis de présenter le framework de build pour les microcontrôleurs STM32 mais aussi de décrire les étapes préliminaires avant le développement du logiciel embarqué (prise en compte de l'architecture du processeur et du microcontrôleur mais également de la carte d'évaluation). Les outils GNU/Linux tels que Makefile, GCC et GDB ont également été abordés pour prendre en main rapidement et facilement le framework. De nombreux liens internet ont été rajoutées à l'article pour inviter le lecteur à creuser les informations présentées. La structure de ce framework est versatile et personnalisable, il peut donc être modifié pour utiliser d'autres microcontrôleurs, HAL (libopencm3) ou OS (FreeRTOS).
Sources
- https://github.com/Openwide-Ingenierie/generic-microcontroller-project.git
- https://www.st.com/content/st_com/en.html
- https://www.freertos.org/index.html
- http://libopencm3.org/docs/latest/stm32f4/html/modules.html
- https://linuxembedded.fr/
- https://interrupt.memfault.com/blog/
- https://gcc.gnu.org/
- https://www.gnu.org/software/gdb/
- http://openocd.org/