🔌 Microcontrôleur - Semestre 6

Année Universitaire : 2022-2023
Semestre : 6
Crédits : 4 ECTS
Spécialité : Systèmes Embarqués


PART A - Présentation Générale du Cours

Vue d'ensemble

Ce cours approfondit la programmation de microcontrôleurs avec focus sur les STM32 (ARM Cortex-M3). Il couvre la configuration des périphériques (GPIO, timers, ADC, UART), le développement de drivers, et la gestion des interruptions. Le point culminant est le projet Voilier, un système embarqué autonome intégrant capteurs et actionneurs.

Objectifs pédagogiques :

Position dans le cursus

Ce cours s’appuie sur :

Il prépare aux applications :


PART B - Expérience Personnelle et Contexte d’Apprentissage

Organisation et ressources

Le module était structuré en deux parties complémentaires :

1. Cours et TDs :

2. Projet Voilier : Conception d’un voilier autonome radiocommandé avec :

Environnement de développement :

Déroulement du projet Voilier

Architecture du système :

Architecture STM32

Figure : Architecture d'un microcontrôleur STM32 - CPU ARM Cortex-M4 avec périphériques

Le voilier autonome intègre plusieurs sous-systèmes :

Sous-système Composants Fonction
Navigation Girouette, boussole, GPS Déterminer position et orientation
Contrôle Servo-moteurs Ajuster gouvernail et voile
Communication UART, radio Télémétrie et commandes
Alimentation Batterie, régulateur Autonomie énergétique

Capteurs implémentés :

Girouette (anémomètre) :

Boussole électronique :

Étapes de développement :

Étape 1 : Drivers de base

Développement de drivers modulaires pour chaque périphérique.

Driver GPIO :

typedef struct {
    GPIO_TypeDef * GPIO;      // Port (GPIOA, GPIOB, GPIOC...)
    char GPIO_Pin;            // Numéro broche 0-15
    char GPIO_Conf;           // Configuration
} MyGPIO_Struct_TypeDef;

// Modes de configuration
#define In_Floating  0x4
#define In_PullUp    0x8
#define In_PullDown  0x8
#define Out_Ppull    0x2      // Push-pull
#define Out_OD       0x6      // Open-drain
#define AltOut_Ppull 0xA      // Fonction alternative

// Initialisation GPIO
void MyGPIO_Init(MyGPIO_Struct_TypeDef * GPIOStructPtr) {
    // Activation horloge du port
    if (GPIOStructPtr->GPIO == GPIOA) {
        RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;
    }
    
    // Configuration de la broche
    if(GPIOStructPtr->GPIO_Pin <= 7) {
        GPIOStructPtr->GPIO->CRL &= ~(0xF << (4*GPIOStructPtr->GPIO_Pin));
        GPIOStructPtr->GPIO->CRL |= (GPIOStructPtr->GPIO_Conf << (4*GPIOStructPtr->GPIO_Pin));
    }
    else {
        GPIOStructPtr->GPIO->CRH &= ~(0xF << (4*(GPIOStructPtr->GPIO_Pin % 8)));
        GPIOStructPtr->GPIO->CRH |= (GPIOStructPtr->GPIO_Conf << (4*(GPIOStructPtr->GPIO_Pin % 8)));
    }
}

// Lecture d'une entrée
int MyGPIO_Read(GPIO_TypeDef * GPIO, char GPIO_Pin) {
    return (GPIO->IDR & (1 << GPIO_Pin)) != 0 ? 1 : 0;
}

// Mise à 1
void MyGPIO_Set(GPIO_TypeDef * GPIO, char GPIO_Pin) {
    GPIO->BSRR = (1 << GPIO_Pin);
}

// Mise à 0
void MyGPIO_Reset(GPIO_TypeDef * GPIO, char GPIO_Pin) {
    GPIO->BRR = (1 << GPIO_Pin);
}

// Basculement
void MyGPIO_Toggle(GPIO_TypeDef * GPIO, char GPIO_Pin) {
    GPIO->ODR ^= (1 << GPIO_Pin);
}

Étape 2 : Timers et PWM

Configuration des timers pour génération PWM (contrôle servo-moteurs).

// Initialisation Timer en mode PWM
void MyTimer_PWM_Init(TIM_TypeDef * Timer, int frequence) {
    // Activation horloge timer
    if (Timer == TIM2) {
        RCC->APB1ENR |= RCC_APB1ENR_TIM2EN;
    }
    
    // Configuration prescaler et période
    Timer->PSC = 72 - 1;              // Prescaler (72 MHz / 72 = 1 MHz)
    Timer->ARR = 20000 - 1;           // Période 20 ms (50 Hz pour servo)
    
    // Mode PWM sur canal 1
    Timer->CCMR1 |= TIM_CCMR1_OC1M_1 | TIM_CCMR1_OC1M_2;  // PWM mode 1
    Timer->CCMR1 |= TIM_CCMR1_OC1PE;  // Preload enable
    
    // Activation sortie
    Timer->CCER |= TIM_CCER_CC1E;
    
    // Démarrage timer
    Timer->CR1 |= TIM_CR1_CEN;
}

// Réglage rapport cyclique (duty cycle)
void MyTimer_SetDutyCycle(TIM_TypeDef * Timer, int channel, int duty) {
    if (channel == 1) {
        Timer->CCR1 = duty;  // Valeur de 1000 à 2000 µs pour servo
    }
}

Commande de servo-moteur :

Étape 3 : ADC pour girouette

Lecture analogique de la position de la girouette.

// Initialisation ADC
void MyADC_Init(ADC_TypeDef * ADC, char channel) {
    // Activation horloge ADC
    RCC->APB2ENR |= RCC_APB2ENR_ADC1EN;
    
    // Configuration ADC
    ADC->CR2 |= ADC_CR2_ADON;         // Activation ADC
    ADC->SQR3 = channel;              // Sélection canal
    ADC->SMPR2 = 0x7 << (3*channel);  // Temps échantillonnage 239,5 cycles
}

// Lecture ADC (bloquante)
int MyADC_Read(ADC_TypeDef * ADC) {
    ADC->CR2 |= ADC_CR2_ADON;         // Démarrage conversion
    while (!(ADC->SR & ADC_SR_EOC));  // Attente fin conversion
    return ADC->DR;                   // Lecture résultat
}

// Conversion ADC → angle
int Girouette_GetAngle(void) {
    int raw = MyADC_Read(ADC1);
    return (raw * 360) / 4096;  // 12 bits → 0-360°
}

Étape 4 : UART pour télémétrie

Communication série pour envoyer données et recevoir commandes.

// Initialisation UART
void MyUART_Init(USART_TypeDef * UART, int baudrate) {
    // Activation horloge
    if (UART == USART1) {
        RCC->APB2ENR |= RCC_APB2ENR_USART1EN;
    }
    
    // Configuration GPIO (TX/RX)
    // PA9 = TX (fonction alternative), PA10 = RX (input)
    
    // Configuration baudrate
    UART->BRR = 72000000 / baudrate;  // 72 MHz / 9600 bps
    
    // Activation TX et RX
    UART->CR1 |= USART_CR1_TE | USART_CR1_RE | USART_CR1_UE;
}

// Envoi d'un caractère
void MyUART_SendChar(USART_TypeDef * UART, char c) {
    while (!(UART->SR & USART_SR_TXE));  // Attente buffer vide
    UART->DR = c;
}

// Réception d'un caractère
char MyUART_ReceiveChar(USART_TypeDef * UART) {
    while (!(UART->SR & USART_SR_RXNE));  // Attente données disponibles
    return UART->DR;
}

// Envoi d'une chaîne
void MyUART_SendString(USART_TypeDef * UART, char * str) {
    while (*str) {
        MyUART_SendChar(UART, *str++);
    }
}

Étape 5 : Régulation de cap

Algorithme de contrôle pour maintenir un cap.

// Structure de contrôle
typedef struct {
    int cap_consigne;      // Cap désiré
    int cap_actuel;        // Cap mesuré (boussole)
    int angle_vent;        // Direction du vent (girouette)
    int position_gouvernail;
    int position_voile;
} VoilierControl_TypeDef;

// Régulation simple (proportionnelle)
void Voilier_Regulate(VoilierControl_TypeDef * voilier) {
    // Calcul erreur de cap
    int erreur = voilier->cap_consigne - voilier->cap_actuel;
    
    // Normalisation erreur (-180° à +180°)
    if (erreur > 180) erreur -= 360;
    if (erreur < -180) erreur += 360;
    
    // Correction proportionnelle
    int correction = erreur * 5;  // Gain proportionnel
    
    // Limites
    if (correction > 500) correction = 500;
    if (correction < -500) correction = -500;
    
    // Application au gouvernail
    voilier->position_gouvernail = 1500 + correction;
    MyTimer_SetDutyCycle(TIM2, 1, voilier->position_gouvernail);
    
    // Ajustement voile selon vent
    int angle_voile = abs(voilier->angle_vent - voilier->cap_actuel);
    if (angle_voile > 180) angle_voile = 360 - angle_voile;
    voilier->position_voile = 1000 + (angle_voile * 1000) / 180;
    MyTimer_SetDutyCycle(TIM3, 1, voilier->position_voile);
}

Étape 6 : Interruptions

Gestion d’événements asynchrones (réception UART, timers).

// Configuration interruption UART
void MyUART_EnableIT(USART_TypeDef * UART) {
    UART->CR1 |= USART_CR1_RXNEIE;  // Interruption réception
    NVIC_EnableIRQ(USART1_IRQn);
    NVIC_SetPriority(USART1_IRQn, 1);
}

// Handler interruption UART
void USART1_IRQHandler(void) {
    if (USART1->SR & USART_SR_RXNE) {
        char received = USART1->DR;
        // Traitement commande
        if (received == 'L') {
            // Virer à gauche
        } else if (received == 'R') {
            // Virer à droite
        }
    }
}

// Configuration interruption timer
void MyTimer_EnableIT(TIM_TypeDef * Timer, int periode_ms) {
    Timer->DIER |= TIM_DIER_UIE;  // Interruption update
    NVIC_EnableIRQ(TIM2_IRQn);
}

// Handler interruption timer (tâche périodique)
void TIM2_IRQHandler(void) {
    if (TIM2->SR & TIM_SR_UIF) {
        TIM2->SR &= ~TIM_SR_UIF;  // Clear flag
        
        // Tâche périodique (ex: lecture capteurs, régulation)
        Voilier_Regulate(&voilier);
    }
}

Difficultés rencontrées

Configuration des registres : Les datasheets STM32 sont volumineuses (>1000 pages). Il faut comprendre chaque bit des registres de configuration. Erreur fréquente : oublier d’activer l’horloge du périphérique (RCC).

Timing et interruptions : Conflits entre tâches : une interruption trop longue bloque les autres. Nécessité de priorités bien réglées et de handlers courts.

Calibration des capteurs : La girouette nécessite une calibration (offset, linéarité). La boussole doit être compensée en perturbations magnétiques.

Débogage matériel : Problèmes parfois difficiles à diagnostiquer : câblage, alimentation, interférences. Oscilloscope et analyseur logique indispensables.


PART C - Aspects Techniques Détaillés

1. Architecture STM32F103

Caractéristiques principales :

Composant Spécification
CPU ARM Cortex-M3, 32 bits, 72 MHz
Flash 128 KB (programme)
RAM 20 KB (données)
GPIO 51 broches I/O
Timers 4 timers 16 bits avancés
ADC 2 ADC 12 bits, 16 canaux
Communication 3 USART, 2 SPI, 2 I2C, USB, CAN

Organisation mémoire :

Zone Adresses Usage
Flash 0x08000000 - 0x0801FFFF Code programme
SRAM 0x20000000 - 0x20004FFF Variables, pile
Périphériques 0x40000000 - 0x5FFFFFFF Registres memory-mapped
Système 0xE0000000 - 0xE00FFFFF NVIC, SysTick

2. GPIO (General Purpose Input/Output)

Registres GPIO :

Chaque port (A, B, C, D) dispose de registres de configuration et de contrôle.

Registre Fonction
GPIOx_CRL Configuration broches 0-7 (4 bits par broche)
GPIOx_CRH Configuration broches 8-15
GPIOx_IDR Input Data Register (lecture)
GPIOx_ODR Output Data Register (écriture)
GPIOx_BSRR Bit Set/Reset Register (atomique)
GPIOx_BRR Bit Reset Register

Configuration d’une broche :

Chaque broche nécessite 4 bits de configuration : MODE (2 bits) + CNF (2 bits).

MODE CNF Configuration
00 01 Input floating
00 10 Input pull-down/up
01 00 Output push-pull 10 MHz
10 00 Output push-pull 2 MHz
11 00 Output push-pull 50 MHz
11 10 Alternate function push-pull

3. Timers

Modes de fonctionnement :

Les timers STM32 sont polyvalents :

Mode compteur simple :

Mode PWM :

Mode capture :

Registres principaux :

Registre Fonction
TIMx_CR1 Control Register (activation, mode comptage)
TIMx_PSC Prescaler (diviseur fréquence)
TIMx_ARR Auto-Reload (période)
TIMx_CNT Compteur actuel
TIMx_CCR1-4 Capture/Compare (PWM duty cycle)
TIMx_CCMR1-2 Configuration canaux (PWM mode)
TIMx_CCER Activation sorties

Calcul de fréquence PWM :

Fréquence PWM = Horloge / (PSC + 1) / (ARR + 1)

Exemple : 72 MHz / 72 / 20000 = 50 Hz (servo-moteur)

4. ADC (Convertisseur Analogique-Numérique)

Caractéristiques ADC STM32 :

Configuration :

// Séquence de conversion
ADC1->SQR1 = (nombre_conversions - 1) << 20;  // Longueur séquence
ADC1->SQR3 = canal;                            // Premier canal

// Temps d'échantillonnage
ADC1->SMPR2 = 0x7 << (3 * canal);  // 239,5 cycles (max précision)

// Démarrage conversion
ADC1->CR2 |= ADC_CR2_ADON;  // Activation + start

Modes d’acquisition :

Mode Description
Simple Une conversion sur commande
Continu Conversions en boucle
Scan Plusieurs canaux en séquence
Discontinu Sous-groupes de canaux

5. UART/USART

Paramètres de communication :

Paramètre Valeur typique
Baudrate 9600, 115200 bps
Bits de données 8 bits
Bit de parité Aucun
Bits de stop 1 bit

Calcul du registre BRR :

BRR = Horloge_périphérique / Baudrate

Exemple : 72 MHz / 115200 = 625

Gestion du buffer :

Pour éviter la perte de données, utiliser :

6. Interruptions (NVIC)

Nested Vectored Interrupt Controller :

Le NVIC gère jusqu’à 68 interruptions avec 16 niveaux de priorité.

Configuration :

// Activation interruption
NVIC_EnableIRQ(USART1_IRQn);

// Priorité (0 = plus haute)
NVIC_SetPriority(USART1_IRQn, 2);

// Désactivation
NVIC_DisableIRQ(USART1_IRQn);

Priorités :

Plus le numéro est faible, plus la priorité est haute. Une interruption de priorité plus haute peut préempter une interruption en cours.

Bonnes pratiques :

7. Horloge et RCC

Reset and Clock Control :

Le RCC configure les horloges et active les périphériques.

Sources d’horloge :

Configuration typique :

HSE 8 MHz → PLL ×9 → SYSCLK 72 MHz

Activation périphériques :

Chaque périphérique doit être activé via RCC avant utilisation :

RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;    // GPIO A
RCC->APB2ENR |= RCC_APB2ENR_USART1EN;  // USART1
RCC->APB1ENR |= RCC_APB1ENR_TIM2EN;    // Timer 2

PART D - Analyse Réflexive et Perspectives

Compétences acquises

Programmation embarquée : Maîtrise de la programmation bas niveau en C, manipulation directe des registres, compréhension du fonctionnement matériel. Capacité à lire et interpréter des datasheets complexes.

Développement de drivers : Conception de couches d’abstraction matérielle (HAL) réutilisables. Structure modulaire du code, séparation interface/implémentation.

Intégration système : Le projet Voilier a développé la capacité à intégrer plusieurs sous-systèmes (capteurs, actionneurs, communication) dans une application cohérente et fonctionnelle.

Débogage matériel/logiciel : Utilisation d’outils professionnels (oscilloscope, analyseur logique, debugger). Méthodologie de diagnostic des problèmes.

Points clés à retenir

1. Toujours activer l’horloge : Erreur n°1 : oublier RCC->APBxENR. Le périphérique ne fonctionne pas sans son horloge.

2. Configuration complète : GPIO, timers, ADC nécessitent une configuration précise de nombreux bits. Bien lire la datasheet.

3. Volatile pour registres : Les registres matériels doivent être déclarés volatile pour éviter l’optimisation du compilateur.

4. Interruptions = handlers courts : Traitement minimal dans ISR, report du travail dans la boucle principale via flags.

5. Débogage méthodique : Commencer simple (LED blinking), ajouter progressivement la complexité. Tester chaque module séparément avant intégration.

Retour d'expérience projet Voilier

Aspects positifs :

Défis techniques :

Leçons apprises :

Applications pratiques

Domotique : Contrôle d’éclairage, chauffage, volets roulants. Communication sans fil (Zigbee, LoRa).

Robotique : Contrôle de moteurs, lecture de capteurs (ultrason, infrarouge), navigation autonome.

IoT (Internet of Things) : Objets connectés : thermostats, trackers, stations météo. Communication WiFi, Bluetooth.

Automobile : ECU (Electronic Control Unit), capteurs ABS, airbags, injection moteur.

Médical : Dispositifs portables (glucomètres, tensiomètres), pompes à perfusion, stimulateurs cardiaques.

Limites et ouvertures

Limites du cours :

Ouvertures vers :

Évolution technologique

Tendances actuelles :

Outils modernes :

Conclusion

Le cours Microcontrôleur est fondamental pour tout ingénieur en systèmes embarqués. La maîtrise de la programmation bas niveau et de l’interfaçage matériel est essentielle dans un monde où les objets connectés sont omniprésents.

Le projet Voilier est l’expérience marquante du semestre : conception complète d’un système autonome intégrant capteurs, actionneurs et contrôle temps réel. Les compétences acquises (drivers, interruptions, périphériques) sont directement transférables à l’industrie.

Évolution des compétences : De la simple LED clignotante au voilier autonome, le parcours montre la progression : GPIO → timers → ADC → UART → intégration système complet. Chaque brique s’ajoute pour construire des systèmes de plus en plus complexes.

Recommandations :

Liens avec les autres cours :


📚 Documents de Cours

Voici les supports de cours en PDF pour approfondir la programmation des microcontrôleurs STM32 :

🔧 STM32 - Structures et Registres

Guide complet des structures C pour l'accès aux registres du STM32, configuration et utilisation des périphériques.

📥 Télécharger le PDF

📌 GPIO - Entrées/Sorties

Configuration des GPIO, modes d'entrée/sortie, pull-up/pull-down et manipulation des broches avec pointeurs.

📥 Télécharger le PDF

⚡ Interruptions

Gestion des interruptions, NVIC, priorités, handlers et bonnes pratiques de programmation temps réel.

📥 Télécharger le PDF

⏱️ Timers

Configuration et utilisation des timers pour génération de délais, comptage d'événements et mesures temporelles.

📥 Télécharger le PDF

🌊 PWM - Modulation de Largeur d'Impulsion

Génération de signaux PWM pour commande de moteurs, LEDs et variation d'intensité avec les timers.

📥 Télécharger le PDF

📊 ADC - Convertisseur Analogique-Numérique

Configuration de l'ADC, acquisition de signaux analogiques, modes de déclenchement et utilisation avec DMA.

📥 Télécharger le PDF


Cours enseigné en 2022-2023 à l’INSA Toulouse, Département Génie Électrique et Informatique.