Programmation de microcontrôleurs STM32: Utilisation de l'IDE
Cette page vise à présenter une première utilisation du logiciel STM32CubeIDE pour la programmation de microcontrôleurs STM32. En la suivant pas-à-pas, un développeur logiciel doit être capable:
- de créer un projet,
- de savoir où ajouter son programme (simple) en langage C,
- de connaître quelles informations sont mises à disposition par l’outil pour le débogage et comprendre à quoi elles servent.
Créer un projet
Pour créer un projet sur STM32CubeIDE, différentes étapes sont nécessaires. Celles-ci sont présentées ci-dessous.

File -> New -> STM32 Project.



Ajouter du code

Après la configuration du projet, différents fichiers sont créés.
Nous allons essentiellement nous concentrer sur le fichier main.c, qui contient la fonction principale main.
Ouvrez-le et retrouvez la fonction suivante:
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_USART2_UART_Init();
/* USER CODE BEGIN 2 */
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
Lors de la création du fichier, l’IDE pré-complète et organise la fonction main.
Ainsi, en plus de fonctions d’initialisation du système, il ajoute également des commentaires vous indiquant où placer le code utilisateur (USER CODE).
Pour développer une application, la partie essentielle de la fonction main est la boucle while(1).
Étant infinie, elle permet de garantir que l’exécution restera bloquée à l’intérieur.
Ainsi, une fois l’initialisation terminée, c’est à l’intérieur de cette boucle que l’on viendra ajouter les opérations que l’on souhaitera effectuer aussi longtemps que le microcontrôleur fonctionnera.
Par exemple, pour créer une variable count qui s’incrémentera infiniment, ajoutez le code suivant:
int main(void) {
/* USER CODE BEGIN 2 */
uint32_t count = 0;
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1) {
/* USER CODE END WHILE */
count = count + 1;
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
La partie USER CODE 2 étant avant la boucle, alors celle-ci ne s’exécutera qu’une seule fois.
En revanche, la partie USER CODE 3 étant dans la boucle, alors elle sera exécutée aussi longtemps que le système restera en marche.
Dans notre cas, on souhaite créer la variable count une seule fois au départ: on la place donc dans USER CODE 2.
Par la suite, on souhaite l’incrémenter tout le long du fonctionnement donc on place le code correspondant dans USER CODE 3.
De la même manière, il sera possible d’exécuter indéfiniment des tâches plus complexes en les plaçant dans cette boucle infinie. C’est ce qui est fait lorsque l’on souhaite qu’un système réalise une mission bien précise et répétitive dans le temps.
De manière générale, on organisera notre code de la manière suivante:
- L’initialisation du système s’effectuera dans les parties
USER CODE InitouUSER CODE SysInit. Ainsi, si les configurations ne seront pas écrasées par l’initialisation du système effectuée par défaut par les fonctionsHAL_Init()ouSystemClock_Config(). - L’initialisation des périphériques s’effectuera dans la partie
USER CODE 2, le code exécuté une seule fois avant la boucle infinie. Là encore, cela permettra de mettre les périphériques dans des états connus, en étant sûr que les fonctions par défautMX_GPIO_Init()etMX_USART2_UART_Init()ne viennent pas les modifier. - La déclaration de variables locales se fera au départ de la fonction
main, dans la partieUSER CODE 1avant leur utilisation. - Les opérations initiales (exécutées qu’une seule fois) seront également effectuées dans la partie
USER CODE 2. - Enfin, les opérations récurrentes (qui doivent être ré-exécutées indéfiniment) seront placées dans la parties
USER CODE 3, c’est-à-dire dans la bouclewhile(1).
while (1) etc.
Pour la suite de ces expérimentations, il vous est cependant demandé de suivre ces indications et de valider les résultats attendus avant de tenter vos propres solutions.Debogage
Une fois le code modifié, il est nécessaire de s’assurer qu’il réalise la fonctionnalité voulue. Il faut donc passer par une phase de débogage (ou debug), où le code est directement exécuté. STM32CubeIDE met pour cela à disposition une interface dédiée.
Lancement
Pour lancer le mode de débogage, cliquez sur l’icône Debug (icône en forme d’insecte). Une fenêtre va s’ouvrir pour configurer la session de débogage. Laissez les paramètres par défaut et cliquez sur OK.
Fenêtres
En mode débogage, plusieurs fenêtres permettent d’avoir différentes informations sur le système. Voici ci-dessous un rapide récapitulatif de ces fenêtres, accessibles par des onglets sur la droite du logiciel.




Window -> Show View -> Disassembly.
Window -> Show View -> Memory Browser.
Window -> Show View -> Build Analyzer.
Exécution
![]()
Pour réaliser le débogage d’un code, il est nécessaire de l’exécuter. Différentes commandes peuvent être utilisées pour cela. Voici les principales icônes et leurs fonctions:
- Relancer le mode Debug.
- Lancer l’exécution indéfiniment.
- Terminer l’exécution en cours et en relancer une nouvelle depuis le début.
- Poursuivre l’exécution qui a été interrompue.
- Interrompre l’exécution en cours.
- Terminer l’exécution en cours.
- Exécuter la prochaine ligne de code. En cas d’appel de fonction, l’exécution s’arrête au début de la fonction.
- Exécuter la prochaine ligne de code. En cas d’appel de fonction, celle-ci est entièrement exécutée.
En utilisant ces différentes commandes, il est donc possible d’avancer dans l’exécution et de voir si une application fonctionne correctement. Cependant, elles peuvent s’avérer limitées lorsqu’il s’agit d’observer le comportement d’une ligne de code en particulier. Pour cela, il est possible d’introduire des points d’arrêts (breakpoints).

Un point d’arrêt est une directive indiquant au système d’interrompre l’exécution dès qu’il arrive à une certaine ligne du code. En débogage, il est possible d’introduire des points d’arrêts en cliquant dans la zone bleue sur la gauche du code, comme sur la figure ci-dessus. Ici, deux points d’arrêts sont placés lignes 92 et 100: à chaque fois que l’exécution arrivera à l’une de ces lignes, elle sera interrompue et devra être relancée manuellement.
On notera que des points d’arrêts peuvent également être placés directement dans le programme désassemblé. Cela peut dans certains cas permettre une granularité plus fine, en analysant l’évolution du programme instruction par instruction.
Exercices
Utilisation d’une variable
On propose d’étudier l’évolution d’une variable. Pour cela, reprenez le code suivant:
int main(void) {
/* USER CODE BEGIN 2 */
uint32_t count = 0;
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1) {
/* USER CODE END WHILE */
count = count + 1;
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
Analysez l’exécution du programme ci-dessus. Essayez de retrouver:
- le code désassemblé correspondant à la création de la variable,
- le code désassemblé correspondant à l’incrémentation de la variable,
- le rôle de chacune des instructions de ces blocs de code désassemblé,
- l’impact de ces blocs de code sur les registres internes du processeur.
count comme une variable globale située juste avant le main.
Exécutez le programme jusqu’à ce que la variable ait été incrémentée.
Dans la fenêtre Build Analyzer, retrouvez où est placée la variable.
Pourquoi est-elle placée dans cette mémoire ?main.
Pourquoi est-elle placée dans cette mémoire ?Utilisation d’une fonction
On propose d’étudier l’utilisation d’une fonction. Pour cela, reprenez le code suivant:
uint32_t add32 (uint32_t a, uint32_t b) {
return a + b;
}
int main(void) {
/* USER CODE BEGIN 2 */
uint32_t count = 0;
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1) {
/* USER CODE END WHILE */
count = add32(count, 1);
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
add32.
Pourquoi est-elle placée dans cette mémoire ?