Système à base des microcontrôleurs
Ce document présente les bonnes pratiques de programmation embarquée pour les systèmes à base de microcontrôleurs. Il s'adresse aux étudiants en informatique de 3ème année, ainsi qu'à toute personne souhaitant maîtriser les concepts essentiels liés à la gestion des types, des variables, des opérateurs, des interruptions, de l'optimisation du code, et de l'utilisation des machines à états finis (FS
D'après le document Système à base des microcontrôleurs
Cet article a été rédigé automatiquement à partir du document source, puis vérifié avant publication.

Document source
Programmation, Microcontrôleurs, Informatique · PDF · 18 pages · 2014
Afficher l'aperçu du document
Ce document présente les bonnes pratiques de programmation embarquée pour les systèmes à base de microcontrôleurs. Il s'adresse aux étudiants en informatique de 3ème année, ainsi qu'à toute personne souhaitant maîtriser les concepts essentiels liés à la gestion des types, des variables, des opérateurs, des interruptions, de l'optimisation du code, et de l'utilisation des machines à états finis (FSM) dans un contexte embarqué.
Types utilisés
En programmation embarquée, il est important d'utiliser des types de données indépendants du compilateur pour garantir la portabilité et la précision des données. Les types standards comme int ou double peuvent varier selon l'environnement de compilation (taille en bits). Il est donc recommandé d'utiliser des types à taille fixe, définis dans stdint.h :
int8_t,uint8_t: entiers signés et non signés sur 8 bitsint16_t,uint16_t: entiers sur 16 bitsint32_t,uint32_t: entiers sur 32 bitsint64_t,uint64_t: entiers sur 64 bits
Ces types correspondent aux typedefs suivants :
typedef signed char int8_t;
typedef unsigned char uint8_t;
typedef signed int int16_t;
typedef unsigned int uint16_t;
typedef signed long int int32_t;
typedef unsigned long int uint32_t;
typedef signed long long int int64_t;
typedef unsigned long long int uint64_t;
Variables
Variables globales et locales
Les variables locales sont déclarées à l'intérieur des fonctions qui les utilisent, tandis que les variables globales sont définies au début du programme. Le linker attribue une adresse fixe aux variables globales pendant toute la durée d'exécution.
Les variables locales sont par défaut automatiques : elles sont créées à l'entrée de la fonction et détruites à la sortie. Elles sont stockées dans la pile, une zone mémoire dédiée notamment au stockage des données de retour des sous-programmes. L'utilisation de variables locales améliore la lisibilité et la sécurité des données dans un programme C.
Variables statiques
Une variable globale est persistante par nature. En la déclarant avec le mot-clé static, on la « privatise » au fichier où elle est déclarée, ce qui empêche son utilisation depuis un autre fichier.
Variables externes
Les variables externes permettent de partager des variables entre plusieurs fichiers source :
/* File file1.c */
int b1 = 0;
void next(void) {
char a1;
a1 = 'a';
b1 = 77;
}
/* File file2.c */
extern int a1;
void next1(void) {
float b1;
b1 = 19.2;
a1 = 13;
}
Qualificateurs de variables
Le langage C propose des qualificateurs qui influent sur le comportement des variables :
- const : indique que la valeur de la variable ne doit jamais changer.
- volatile : indique que la variable peut être modifiée par des sources externes (périphériques matériels, processus, etc.) et que le compilateur ne doit pas optimiser son accès. La variable est relue à chaque accès depuis son emplacement mémoire d'origine.
Une variable peut avoir plusieurs qualificateurs combinés.
Opérateurs
Opérateurs logiques de comparaison
Les opérateurs logiques && (ET logique) et || (OU logique) diffèrent des opérateurs binaires & et | par leur comportement d'évaluation :
- Pour
&&, si l'opérande gauche est faux (zéro), l'opérande droit n'est pas évalué car le résultat sera forcément faux. - Pour
||, si l'opérande gauche est vrai (différent de zéro), l'opérande droit n'est pas évalué car le résultat sera forcément vrai.
Manipulation des bits
La manipulation des bits est essentielle en programmation embarquée pour contrôler les registres matériels (8, 16 ou 32 bits). Voici quelques opérations courantes :
Mettre à 1 le bit 4 de a :
unsigned a = 0x000F; /* 0000 0000 0000 1111 */
unsigned b = 0x0010; /* 0000 0000 0001 0000 */
unsigned c = a | b; /* 0000 0000 0001 1111 soit 0x001F */
Mettre à zéro le bit 3 de a :
unsigned a = 0x000F; /* 0000 0000 0000 1111 */
unsigned b = 0xFFF7; /* 1111 1111 1111 0111 */
unsigned c = a & b; /* 0000 0000 0000 0111 soit 0x0007 */
Tester si le bit 2 de a est à 1 :
unsigned a = 0x000F; /* 0000 0000 0000 1111 */
if (a & (1 << 2)) {
/* bit 2 est à 1 */
}
Tester si le bit 3 de a est à 1 et si le bit 15 est à 0 :
unsigned a = 0x000F; /* 0000 0000 0000 1111 */
if ((a & (1 << 3)) != 0 && (a & (1 << 15)) == 0) {
/* bit 3 est à 1 et bit 15 est à 0 */
}
Points d’entrées
Le point d'entrée principal d'une application est la fonction main(). Dans un système embarqué sans système d'exploitation, le point d'entrée est défini lors de la phase d'édition de liens par l'initialisation du vecteur d'interruption.
D'autres points d'entrée correspondent aux routines d'interruption, définies dans une table de vecteurs sous forme de pointeurs vers les callbacks des interruptions.
Lors des interruptions, il est conseillé de minimiser le traitement pour éviter la superposition d'interruptions, sauf dans les cas à contraintes temps réel strictes, par exemple :
- Traitements d'urgence : bouton d'alerte, alarme
- Traitements fortement temporisés : synthèse vocale, affichage vidéo
Optimisation du code
Le compilateur propose différents niveaux d'optimisation :
- -O0 (niveau 0) : allocation des variables dans les registres, rotation des boucles, élimination du code inutilisé, simplification des expressions et instructions.
- -O1 (niveau 1) : toutes les optimisations de -O0, plus suppression des affectations inutilisées et élimination des expressions communes locales.
- -O2 (niveau 2) (niveau par défaut) : toutes les optimisations de -O1, plus optimisations des boucles.
Utilisation des machines à états finis (FSM)
Les FSM (Finite State Machines) sont largement utilisées pour gérer les états et transitions dans les systèmes embarqués. Par exemple, pour gérer l'état d'une LED :
- LED_ON
- LED_OFF
Les transitions peuvent être codées sous forme de masques binaires pour activer plusieurs transitions simultanément :
typedef enum {
LED_ON,
LED_OFF
} ledState;
typedef enum {
NO_TRANS = 0,
LED_ON_OFF = 0x01,
LED_OFF_ON = 0x02
} ledTransition;
Exemple de codage d'une FSM pour une LED
switch(led->state) {
case LED_ON:
if (led->trans & LED_ON_OFF) {
led->state = LED_OFF;
led->trans = NO_TRANS;
GPIO_ResetBits(led->port, led->pin);
}
if (led->trans & LED_OFF_ON) {
led->state = LED_ON;
led->trans = NO_TRANS;
GPIO_SetBits(led->port, led->pin);
}
break;
case LED_OFF:
// Traitement similaire pour l'état LED_OFF
break;
}Gestion du temps dans la FSM
La fonction suivante gère le timeout et les transitions temporelles :
void LED_MsRoutine(ledStruct *led) {
if (led->timeout) {
led->timeout--;
if (led->timeout == led->duty)
led->trans = LED_OFF_ON;
} else {
led->timeout = led->period;
led->trans = LED_ON_OFF;
}
}
Exemple d'utilisation dans un programme principal
void Decrement_Delay() {
LED_MsRoutine(&leds[0]);
}
int main(void) {
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOD, ENABLE);
SysTick_Config(SystemCoreClock / 1000);
LED_Init(&leds[0], GPIOD, GPIO_Pin_12, 500, 350);
while (1) {
LED_Routine(&leds[0]);
}
}
Utilisation des librairies
Chaque librairie embarquée comporte deux fichiers :
- Fichier *.c : contient les traitements, la déclaration des variables et des fonctions.
- Fichier *.h : contient les déclarations des types et les prototypes des fonctions.
Les librairies manipulent généralement un objet de type structure. Par exemple, pour une LED :
typedef struct {
ledState state;
ledTransition trans;
GPIO_TypeDef* port;
uint16_t pin;
uint16_t period;
uint16_t duty;
__IO uint16_t timeout;
} ledStruct;
void LED_Init(ledStruct *led, GPIO_TypeDef* port, uint16_t pin, uint16_t period, uint16_t duty);
void LED_Routine(ledStruct *led);
void LED_MsRoutine(ledStruct *led);
Glossaire des termes clés
- int8_t, uint8_t, int16_t, uint16_t, etc. : Types entiers à taille fixe, signés ou non signés.
- Variable locale : Variable déclarée dans une fonction, créée à l'entrée et détruite à la sortie de cette fonction.
- Variable globale : Variable déclarée en dehors des fonctions, accessible dans tout le programme.
- Variable statique : Variable globale ou locale dont la portée est limitée au fichier ou à la fonction, mais dont la durée de vie est persistante.
- Variable externe (extern) : Variable déclarée dans un fichier et utilisée dans un autre.
- Qualificateur const : Indique que la variable ne peut pas être modifiée après initialisation.
- Qualificateur volatile : Indique que la variable peut changer de valeur de manière asynchrone, empêchant certaines optimisations du compilateur.
- Opérateurs logiques && et || : Opérateurs de court-circuit pour les opérations ET et OU.
- Manipulation des bits : Opérations sur les bits individuels d'une variable, utilisées pour contrôler des registres matériels.
- Point d'entrée : Fonction ou adresse où commence l'exécution du programme (ex : main(), vecteur d'interruption).
- Interruption : Mécanisme permettant d'interrompre le programme principal pour exécuter une routine spécifique.
- Optimisation du code : Techniques utilisées par le compilateur pour améliorer la performance ou la taille du code généré.
- FSM (Finite State Machine) : Machine à états finis, modèle utilisé pour gérer des états et transitions dans un système.
- Transition en mode masque : Utilisation de valeurs binaires (0x01, 0x02, 0x04, ...) pour représenter plusieurs transitions possibles simultanément.
- Structure (struct) : Type de données regroupant plusieurs variables sous un même nom.
Points clés à retenir
- Utiliser des types à taille fixe pour garantir la portabilité et la précision des données.
- Favoriser les variables locales pour améliorer la lisibilité et la sécurité des données.
- Utiliser les qualificateurs
constetvolatileselon le contexte pour un comportement correct du programme. - Comprendre la différence entre opérateurs logiques et binaires, notamment le court-circuit.
- Maîtriser la manipulation des bits pour contrôler les registres matériels.
- Minimiser le traitement dans les routines d'interruption pour éviter les conflits.
- Choisir le niveau d'optimisation du compilateur adapté aux besoins du projet.
- Utiliser les FSM pour gérer efficacement les états et transitions dans les systèmes embarqués.
- Organiser le code en librairies avec fichiers *.c et *.h pour une meilleure modularité.
Commentaires
Aucun commentaire pour le moment. Posez la première question.