Microcontroleur - Semestre 6
Annee Universitaire : 2022-2023
Semestre : 6
Credits : 4 ECTS
Specialite : Systemes Embarques
PART A - Presentation Generale du Cours
Vue d'ensemble
Ce cours approfondit la programmation de microcontroleurs avec focus sur les STM32 (ARM Cortex-M3). Il couvre la configuration des peripheriques (GPIO, timers, ADC, UART), le developpement de drivers, et la gestion des interruptions. Le point culminant est le projet Voilier, un systeme embarque autonome integrant capteurs et actionneurs.
Objectifs pedagogiques :
- Maitriser la programmation en C embarque pour STM32
- Configurer les peripheriques via registres (GPIO, timers, ADC, UART)
- Developper des drivers reutilisables avec HAL (Hardware Abstraction Layer)
- Gerer les interruptions et le temps reel
- Concevoir un systeme embarque complet (projet Voilier)
Position dans le cursus
Ce cours s'appuie sur :
- Langage C (S5) : bases de la programmation C
- Architecture Informatique (S5) : fonctionnement processeur et memoire
- Langage Assemblage ARM (S6) : comprehension bas niveau
Il prepare aux applications :
- Systemes embarques temps reel : contraintes temporelles
- IoT et objets connectes : capteurs, communication
- Robotique et automatisation : controle moteurs, asservissement
PART B - Experience Personnelle et Contexte d'Apprentissage
Organisation et ressources
Le module etait structure en deux parties complementaires :
1. Cours et TDs :
- Architecture STM32F103 (Cortex-M3, 72 MHz, 128 KB Flash, 20 KB RAM)
- Programmation des peripheriques via registres
- Developpement de drivers
- Gestion des interruptions et timers
2. Projet Voilier :
Conception d'un voilier autonome radiocommande avec :
- Capteurs : girouette (anemometre), boussole, GPS
- Actionneurs : servo-moteurs (gouvernail, voile)
- Communication : liaison serie, telemetrie
- Controle : regulation automatique de cap
Environnement de developpement :
- IDE : Keil uVision
- Carte : STM32F103RB (Nucleo-64 ou carte custom)
- Programmation : ST-Link (SWD)
- Debogage : breakpoints, watch, memoire
Deroulement du projet Voilier
Architecture du systeme :
Figure : Architecture d'un microcontroleur STM32 - CPU ARM Cortex-M4 avec peripheriques
Le voilier autonome integre plusieurs sous-systemes :
| Sous-systeme | Composants | Fonction |
|---|---|---|
| Navigation | Girouette, boussole, GPS | Determiner position et orientation |
| Controle | Servo-moteurs | Ajuster gouvernail et voile |
| Communication | UART, radio | Telemetrie et commandes |
| Alimentation | Batterie, regulateur | Autonomie energetique |
Capteurs implementes :
Girouette (anemometre) :
- Mesure de la direction du vent
- Interface : potentiometre rotatif → ADC
- Resolution : 12 bits (0-4095) → 0-360 deg
- Calibration necessaire
Boussole electronique :
- Mesure du cap (orientation)
- Interface : I2C ou SPI
- Donnees : azimut magnetique
Etapes de developpement :
Etape 1 : Drivers de base
Developpement de drivers modulaires pour chaque peripherique.
Driver GPIO :
typedef struct {
GPIO_TypeDef * GPIO; // Port (GPIOA, GPIOB, GPIOC...)
char GPIO_Pin; // Numero 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 entree
int MyGPIO_Read(GPIO_TypeDef * GPIO, char GPIO_Pin) {
return (GPIO->IDR & (1 << GPIO_Pin)) != 0 ? 1 : 0;
}
// Mise a 1
void MyGPIO_Set(GPIO_TypeDef * GPIO, char GPIO_Pin) {
GPIO->BSRR = (1 << GPIO_Pin);
}
// Mise a 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);
}
Etape 2 : Timers et PWM
Configuration des timers pour generation PWM (controle 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 periode
Timer->PSC = 72 - 1; // Prescaler (72 MHz / 72 = 1 MHz)
Timer->ARR = 20000 - 1; // Periode 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;
// Demarrage timer
Timer->CR1 |= TIM_CR1_CEN;
}
// Reglage rapport cyclique (duty cycle)
void MyTimer_SetDutyCycle(TIM_TypeDef * Timer, int channel, int duty) {
if (channel == 1) {
Timer->CCR1 = duty; // Valeur de 1000 a 2000 us pour servo
}
}
Commande de servo-moteur :
- Position neutre : 1500 us
- Gauche max : 1000 us
- Droite max : 2000 us
Etape 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; // Selection canal
ADC->SMPR2 = 0x7 << (3*channel); // Temps echantillonnage 239,5 cycles
}
// Lecture ADC (bloquante)
int MyADC_Read(ADC_TypeDef * ADC) {
ADC->CR2 |= ADC_CR2_ADON; // Demarrage conversion
while (!(ADC->SR & ADC_SR_EOC)); // Attente fin conversion
return ADC->DR; // Lecture resultat
}
// Conversion ADC -> angle
int Girouette_GetAngle(void) {
int raw = MyADC_Read(ADC1);
return (raw * 360) / 4096; // 12 bits -> 0-360 deg
}
Etape 4 : UART pour telemetrie
Communication serie pour envoyer donnees 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 caractere
void MyUART_SendChar(USART_TypeDef * UART, char c) {
while (!(UART->SR & USART_SR_TXE)); // Attente buffer vide
UART->DR = c;
}
// Reception d'un caractere
char MyUART_ReceiveChar(USART_TypeDef * UART) {
while (!(UART->SR & USART_SR_RXNE)); // Attente donnees disponibles
return UART->DR;
}
// Envoi d'une chaine
void MyUART_SendString(USART_TypeDef * UART, char * str) {
while (*str) {
MyUART_SendChar(UART, *str++);
}
}
Etape 5 : Regulation de cap
Algorithme de controle pour maintenir un cap.
// Structure de controle
typedef struct {
int cap_consigne; // Cap desire
int cap_actuel; // Cap mesure (boussole)
int angle_vent; // Direction du vent (girouette)
int position_gouvernail;
int position_voile;
} VoilierControl_TypeDef;
// Regulation simple (proportionnelle)
void Voilier_Regulate(VoilierControl_TypeDef * voilier) {
// Calcul erreur de cap
int erreur = voilier->cap_consigne - voilier->cap_actuel;
// Normalisation erreur (-180 deg a +180 deg)
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);
}
Etape 6 : Interruptions
Gestion d'evenements asynchrones (reception UART, timers).
// Configuration interruption UART
void MyUART_EnableIT(USART_TypeDef * UART) {
UART->CR1 |= USART_CR1_RXNEIE; // Interruption reception
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 a gauche
} else if (received == 'R') {
// Virer a 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 (tache periodique)
void TIM2_IRQHandler(void) {
if (TIM2->SR & TIM_SR_UIF) {
TIM2->SR &= ~TIM_SR_UIF; // Clear flag
// Tache periodique (ex: lecture capteurs, regulation)
Voilier_Regulate(&voilier);
}
}
Difficultes rencontrees
Configuration des registres :
Les datasheets STM32 sont volumineuses (>1000 pages). Il faut comprendre chaque bit des registres de configuration. Erreur frequente : oublier d'activer l'horloge du peripherique (RCC).
Timing et interruptions :
Conflits entre taches : une interruption trop longue bloque les autres. Necessite de priorites bien reglees et de handlers courts.
Calibration des capteurs :
La girouette necessite une calibration (offset, linearite). La boussole doit etre compensee en perturbations magnetiques.
Debogage materiel :
Problemes parfois difficiles a diagnostiquer : cablage, alimentation, interferences. Oscilloscope et analyseur logique indispensables.
PART C - Aspects Techniques Detailles
1. Architecture STM32F103
Caracteristiques principales :
| Composant | Specification |
|---|---|
| CPU | ARM Cortex-M3, 32 bits, 72 MHz |
| Flash | 128 KB (programme) |
| RAM | 20 KB (donnees) |
| GPIO | 51 broches I/O |
| Timers | 4 timers 16 bits avances |
| ADC | 2 ADC 12 bits, 16 canaux |
| Communication | 3 USART, 2 SPI, 2 I2C, USB, CAN |
Organisation memoire :
| Zone | Adresses | Usage |
|---|---|---|
| Flash | 0x08000000 - 0x0801FFFF | Code programme |
| SRAM | 0x20000000 - 0x20004FFF | Variables, pile |
| Peripheriques | 0x40000000 - 0x5FFFFFFF | Registres memory-mapped |
| Systeme | 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 controle.
| 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 (ecriture) |
| GPIOx_BSRR | Bit Set/Reset Register (atomique) |
| GPIOx_BRR | Bit Reset Register |
Configuration d'une broche :
Chaque broche necessite 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 :
- Compte de 0 a ARR (Auto-Reload Register)
- Genere interruption a debordement
- Utilise pour delais, taches periodiques
Mode PWM :
- Compare compteur (CNT) avec valeur de comparaison (CCR)
- Sortie HIGH si CNT < CCR, LOW sinon
- Rapport cyclique = CCR / ARR
Mode capture :
- Capture valeur CNT sur evenement externe
- Mesure de frequence, duree d'impulsion
Registres principaux :
| Registre | Fonction |
|---|---|
| TIMx_CR1 | Control Register (activation, mode comptage) |
| TIMx_PSC | Prescaler (diviseur frequence) |
| TIMx_ARR | Auto-Reload (periode) |
| 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 frequence PWM :
Frequence PWM = Horloge / (PSC + 1) / (ARR + 1)
Exemple : 72 MHz / 72 / 20000 = 50 Hz (servo-moteur)
4. ADC (Convertisseur Analogique-Numerique)
Caracteristiques ADC STM32 :
- Resolution : 12 bits (0-4095)
- Temps de conversion : quelques us
- Modes : simple, continu, scan (multicanaux)
- Declenchement : logiciel, timer, externe
Configuration :
// Sequence de conversion
ADC1->SQR1 = (nombre_conversions - 1) << 20; // Longueur sequence
ADC1->SQR3 = canal; // Premier canal
// Temps d'echantillonnage
ADC1->SMPR2 = 0x7 << (3 * canal); // 239,5 cycles (max precision)
// Demarrage 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 sequence |
| Discontinu | Sous-groupes de canaux |
5. UART/USART
Parametres de communication :
| Parametre | Valeur typique |
|---|---|
| Baudrate | 9600, 115200 bps |
| Bits de donnees | 8 bits |
| Bit de parite | Aucun |
| Bits de stop | 1 bit |
Calcul du registre BRR :
BRR = Horloge_peripherique / Baudrate
Exemple : 72 MHz / 115200 = 625
Gestion du buffer :
Pour eviter la perte de donnees, utiliser :
- Buffer circulaire logiciel
- Interruptions sur reception
- Controle de flux materiel (RTS/CTS)
6. Interruptions (NVIC)
Nested Vectored Interrupt Controller :
Le NVIC gere jusqu'a 68 interruptions avec 16 niveaux de priorite.
Configuration :
// Activation interruption
NVIC_EnableIRQ(USART1_IRQn);
// Priorite (0 = plus haute)
NVIC_SetPriority(USART1_IRQn, 2);
// Desactivation
NVIC_DisableIRQ(USART1_IRQn);
Priorites :
Plus le numero est faible, plus la priorite est haute. Une interruption de priorite plus haute peut preempter une interruption en cours.
Bonnes pratiques :
- Handlers courts et rapides
- Pas de printf ou delais dans ISR
- Utiliser des flags pour communication avec main
- Proteger variables partagees (volatile)
7. Horloge et RCC
Reset and Clock Control :
Le RCC configure les horloges et active les peripheriques.
Sources d'horloge :
- HSI : oscillateur interne 8 MHz (precision +/-1%)
- HSE : quartz externe 8 MHz (precision +/-50 ppm)
- PLL : multiplieur pour atteindre 72 MHz
Configuration typique :
HSE 8 MHz → PLL x9 → SYSCLK 72 MHz
Activation peripheriques :
Chaque peripherique doit etre active 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 Reflexive et Perspectives
Competences acquises
Programmation embarquee :
Maitrise de la programmation bas niveau en C, manipulation directe des registres, comprehension du fonctionnement materiel. Capacite a lire et interpreter des datasheets complexes.
Developpement de drivers :
Conception de couches d'abstraction materielle (HAL) reutilisables. Structure modulaire du code, separation interface/implementation.
Integration systeme :
Le projet Voilier a developpe la capacite a integrer plusieurs sous-systemes (capteurs, actionneurs, communication) dans une application coherente et fonctionnelle.
Debogage materiel/logiciel :
Utilisation d'outils professionnels (oscilloscope, analyseur logique, debugger). Methodologie de diagnostic des problemes.
Points cles a retenir
1. Toujours activer l'horloge :
Erreur n 1 : oublier RCC->APBxENR. Le peripherique ne fonctionne pas sans son horloge.
2. Configuration complete :
GPIO, timers, ADC necessitent une configuration precise de nombreux bits. Bien lire la datasheet.
3. Volatile pour registres :
Les registres materiels doivent etre declares volatile pour eviter l'optimisation du compilateur.
4. Interruptions = handlers courts :
Traitement minimal dans ISR, report du travail dans la boucle principale via flags.
5. Debogage methodique :
Commencer simple (LED blinking), ajouter progressivement la complexite. Tester chaque module separement avant integration.
Retour d'experience projet Voilier
Aspects positifs :
- Projet concret et motivant (systeme reel)
- Liberte de conception et d'implementation
- Travail en equipe avec repartition des taches
- Application directe de tous les concepts du cours
Defis techniques :
- Calibration des capteurs (girouette non lineaire)
- Gestion du timing (regulation 50 Hz + telemetrie)
- Interferences electromagnetiques (moteurs → capteurs)
- Optimisation consommation (autonomie batterie)
Lecons apprises :
- Importance de la modularite (drivers reutilisables)
- Tests unitaires avant integration globale
- Documentation du code (comprehension equipe)
- Version control (Git) pour travail collaboratif
Applications pratiques
Domotique :
Controle d'eclairage, chauffage, volets roulants. Communication sans fil (Zigbee, LoRa).
Robotique :
Controle de moteurs, lecture de capteurs (ultrason, infrarouge), navigation autonome.
IoT (Internet of Things) :
Objets connectes : thermostats, trackers, stations meteo. Communication WiFi, Bluetooth.
Automobile :
ECU (Electronic Control Unit), capteurs ABS, airbags, injection moteur.
Medical :
Dispositifs portables (glucometres, tensiometres), pompes a perfusion, stimulateurs cardiaques.
Limites et ouvertures
Limites du cours :
- Peu de communication sans fil (WiFi, Bluetooth)
- Pas de RTOS (Real-Time Operating System)
- Peu de traitement du signal (filtrage, FFT)
- Pas de securite (chiffrement, authentification)
Ouvertures vers :
- RTOS : FreeRTOS, gestion multitaches, synchronisation
- Communication IoT : MQTT, CoAP, LoRaWAN
- Traitement signal : filtrage numerique, FFT sur MCU
- Bas niveau avance : DMA, low power modes, bootloaders
- Machine Learning : TensorFlow Lite Micro pour MCU
Evolution technologique
Tendances actuelles :
- MCU 32 bits omnipresents : Cortex-M4, M7, RISC-V
- Connectivite integree : WiFi, BLE, LoRa sur puce
- IA embarquee : accelerateurs ML (Cortex-M55, NPU)
- Securite renforcee : TrustZone, secure boot, crypto materiel
- Ultra-basse consommation : nW pour IoT batterie 10 ans
Outils modernes :
- STM32CubeMX : generation automatique code initialisation
- PlatformIO : IDE multiplateforme (VS Code)
- Mbed OS : systeme d'exploitation pour ARM
- Zephyr RTOS : RTOS open-source pour IoT
Conclusion
Le cours Microcontroleur est fondamental pour tout ingenieur en systemes embarques. La maitrise de la programmation bas niveau et de l'interfacage materiel est essentielle dans un monde ou les objets connectes sont omnipresents.
Le projet Voilier est l'experience marquante du semestre : conception complete d'un systeme autonome integrant capteurs, actionneurs et controle temps reel. Les competences acquises (drivers, interruptions, peripheriques) sont directement transferables a l'industrie.
Evolution des competences :
De la simple LED clignotante au voilier autonome, le parcours montre la progression : GPIO → timers → ADC → UART → integration systeme complet. Chaque brique s'ajoute pour construire des systemes de plus en plus complexes.
Recommandations :
- Pratiquer regulierement (petits projets personnels)
- Lire les datasheets en profondeur (indispensable)
- Utiliser oscilloscope et analyseur logique (debogage)
- Developper des drivers reutilisables (portfolio)
- Participer a des competitions (Coupe de Robotique, hackathons)
Liens avec les autres cours :
- Langage Assemblage ARM - S6 : comprehension bas niveau
- Electronique Fonctions Numeriques - S6 : bus I2C/SPI
- Architecture Materielle - S6 : pipeline, cache
- Temps Reel - S8 : RTOS, ordonnancement
- Embedded IA for IoT - S9 : ML embarque
Documents de Cours
Voici les supports de cours en PDF pour approfondir la programmation des microcontroleurs STM32 :
STM32 - Structures et Registres
Guide complet des structures C pour l'acces aux registres du STM32, configuration et utilisation des peripheriques.
GPIO - Entrees/Sorties
Configuration des GPIO, modes d'entree/sortie, pull-up/pull-down et manipulation des broches avec pointeurs.
Interruptions
Gestion des interruptions, NVIC, priorites, handlers et bonnes pratiques de programmation temps reel.
Timers
Configuration et utilisation des timers pour generation de delais, comptage d'evenements et mesures temporelles.
PWM - Modulation de Largeur d'Impulsion
Generation de signaux PWM pour commande de moteurs, LEDs et variation d'intensite avec les timers.
ADC - Convertisseur Analogique-Numerique
Configuration de l'ADC, acquisition de signaux analogiques, modes de declenchement et utilisation avec DMA.
Cours enseigne en 2022-2023 a l'INSA Toulouse, Departement Genie Electrique et Informatique.
Microcontroller - Semester 6
Academic Year: 2022-2023
Semester: 6
Credits: 4 ECTS
Specialization: Embedded Systems
PART A - General Course Overview
Overview
This course deepens microcontroller programming with a focus on STM32 (ARM Cortex-M3). It covers peripheral configuration (GPIO, timers, ADC, UART), driver development, and interrupt management. The highlight is the Sailboat project, an autonomous embedded system integrating sensors and actuators.
Learning objectives:
- Master embedded C programming for STM32
- Configure peripherals via registers (GPIO, timers, ADC, UART)
- Develop reusable drivers with HAL (Hardware Abstraction Layer)
- Manage interrupts and real-time constraints
- Design a complete embedded system (Sailboat project)
Position in the curriculum
This course builds upon:
- C Language (S5): C programming fundamentals
- Computer Architecture (S5): processor and memory operation
- ARM Assembly Language (S6): low-level understanding
It prepares for applications in:
- Real-time embedded systems: timing constraints
- IoT and connected objects: sensors, communication
- Robotics and automation: motor control, servo systems
PART B - Personal Experience and Learning Context
Organization and resources
The module was structured in two complementary parts:
1. Lectures and Tutorials:
- STM32F103 architecture (Cortex-M3, 72 MHz, 128 KB Flash, 20 KB RAM)
- Peripheral programming via registers
- Driver development
- Interrupt and timer management
2. Sailboat Project:
Design of an autonomous radio-controlled sailboat with:
- Sensors: wind vane (anemometer), compass, GPS
- Actuators: servo motors (rudder, sail)
- Communication: serial link, telemetry
- Control: automatic heading regulation
Development environment:
- IDE: Keil uVision
- Board: STM32F103RB (Nucleo-64 or custom board)
- Programming: ST-Link (SWD)
- Debugging: breakpoints, watch, memory
Sailboat project development
System architecture:
Figure: STM32 microcontroller architecture - ARM Cortex-M4 CPU with peripherals
The autonomous sailboat integrates several subsystems:
| Subsystem | Components | Function |
|---|---|---|
| Navigation | Wind vane, compass, GPS | Determine position and orientation |
| Control | Servo motors | Adjust rudder and sail |
| Communication | UART, radio | Telemetry and commands |
| Power supply | Battery, regulator | Energy autonomy |
Implemented sensors:
Wind vane (anemometer):
- Measures wind direction
- Interface: rotary potentiometer → ADC
- Resolution: 12 bits (0-4095) → 0-360 deg
- Calibration required
Electronic compass:
- Measures heading (orientation)
- Interface: I2C or SPI
- Data: magnetic azimuth
Development steps:
Step 1: Basic drivers
Development of modular drivers for each peripheral.
GPIO Driver:
typedef struct {
GPIO_TypeDef * GPIO; // Port (GPIOA, GPIOB, GPIOC...)
char GPIO_Pin; // Pin number 0-15
char GPIO_Conf; // Configuration
} MyGPIO_Struct_TypeDef;
// Configuration modes
#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 // Alternate function
// GPIO initialization
void MyGPIO_Init(MyGPIO_Struct_TypeDef * GPIOStructPtr) {
// Enable port clock
if (GPIOStructPtr->GPIO == GPIOA) {
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;
}
// Pin configuration
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)));
}
}
// Read an input
int MyGPIO_Read(GPIO_TypeDef * GPIO, char GPIO_Pin) {
return (GPIO->IDR & (1 << GPIO_Pin)) != 0 ? 1 : 0;
}
// Set to 1
void MyGPIO_Set(GPIO_TypeDef * GPIO, char GPIO_Pin) {
GPIO->BSRR = (1 << GPIO_Pin);
}
// Set to 0
void MyGPIO_Reset(GPIO_TypeDef * GPIO, char GPIO_Pin) {
GPIO->BRR = (1 << GPIO_Pin);
}
// Toggle
void MyGPIO_Toggle(GPIO_TypeDef * GPIO, char GPIO_Pin) {
GPIO->ODR ^= (1 << GPIO_Pin);
}
Step 2: Timers and PWM
Timer configuration for PWM generation (servo motor control).
// Timer initialization in PWM mode
void MyTimer_PWM_Init(TIM_TypeDef * Timer, int frequency) {
// Enable timer clock
if (Timer == TIM2) {
RCC->APB1ENR |= RCC_APB1ENR_TIM2EN;
}
// Configure prescaler and period
Timer->PSC = 72 - 1; // Prescaler (72 MHz / 72 = 1 MHz)
Timer->ARR = 20000 - 1; // Period 20 ms (50 Hz for servo)
// PWM mode on channel 1
Timer->CCMR1 |= TIM_CCMR1_OC1M_1 | TIM_CCMR1_OC1M_2; // PWM mode 1
Timer->CCMR1 |= TIM_CCMR1_OC1PE; // Preload enable
// Enable output
Timer->CCER |= TIM_CCER_CC1E;
// Start timer
Timer->CR1 |= TIM_CR1_CEN;
}
// Set duty cycle
void MyTimer_SetDutyCycle(TIM_TypeDef * Timer, int channel, int duty) {
if (channel == 1) {
Timer->CCR1 = duty; // Value from 1000 to 2000 us for servo
}
}
Servo motor control:
- Neutral position: 1500 us
- Maximum left: 1000 us
- Maximum right: 2000 us
Step 3: ADC for wind vane
Analog reading of the wind vane position.
// ADC initialization
void MyADC_Init(ADC_TypeDef * ADC, char channel) {
// Enable ADC clock
RCC->APB2ENR |= RCC_APB2ENR_ADC1EN;
// ADC configuration
ADC->CR2 |= ADC_CR2_ADON; // Enable ADC
ADC->SQR3 = channel; // Channel selection
ADC->SMPR2 = 0x7 << (3*channel); // Sampling time 239.5 cycles
}
// ADC read (blocking)
int MyADC_Read(ADC_TypeDef * ADC) {
ADC->CR2 |= ADC_CR2_ADON; // Start conversion
while (!(ADC->SR & ADC_SR_EOC)); // Wait for end of conversion
return ADC->DR; // Read result
}
// ADC to angle conversion
int WindVane_GetAngle(void) {
int raw = MyADC_Read(ADC1);
return (raw * 360) / 4096; // 12 bits -> 0-360 deg
}
Step 4: UART for telemetry
Serial communication to send data and receive commands.
// UART initialization
void MyUART_Init(USART_TypeDef * UART, int baudrate) {
// Enable clock
if (UART == USART1) {
RCC->APB2ENR |= RCC_APB2ENR_USART1EN;
}
// GPIO configuration (TX/RX)
// PA9 = TX (alternate function), PA10 = RX (input)
// Baudrate configuration
UART->BRR = 72000000 / baudrate; // 72 MHz / 9600 bps
// Enable TX and RX
UART->CR1 |= USART_CR1_TE | USART_CR1_RE | USART_CR1_UE;
}
// Send a character
void MyUART_SendChar(USART_TypeDef * UART, char c) {
while (!(UART->SR & USART_SR_TXE)); // Wait for buffer empty
UART->DR = c;
}
// Receive a character
char MyUART_ReceiveChar(USART_TypeDef * UART) {
while (!(UART->SR & USART_SR_RXNE)); // Wait for data available
return UART->DR;
}
// Send a string
void MyUART_SendString(USART_TypeDef * UART, char * str) {
while (*str) {
MyUART_SendChar(UART, *str++);
}
}
Step 5: Heading regulation
Control algorithm to maintain a heading.
// Control structure
typedef struct {
int target_heading; // Desired heading
int current_heading; // Measured heading (compass)
int wind_angle; // Wind direction (wind vane)
int rudder_position;
int sail_position;
} SailboatControl_TypeDef;
// Simple regulation (proportional)
void Sailboat_Regulate(SailboatControl_TypeDef * sailboat) {
// Heading error calculation
int error = sailboat->target_heading - sailboat->current_heading;
// Error normalization (-180 deg to +180 deg)
if (error > 180) error -= 360;
if (error < -180) error += 360;
// Proportional correction
int correction = error * 5; // Proportional gain
// Limits
if (correction > 500) correction = 500;
if (correction < -500) correction = -500;
// Apply to rudder
sailboat->rudder_position = 1500 + correction;
MyTimer_SetDutyCycle(TIM2, 1, sailboat->rudder_position);
// Sail adjustment based on wind
int sail_angle = abs(sailboat->wind_angle - sailboat->current_heading);
if (sail_angle > 180) sail_angle = 360 - sail_angle;
sailboat->sail_position = 1000 + (sail_angle * 1000) / 180;
MyTimer_SetDutyCycle(TIM3, 1, sailboat->sail_position);
}
Step 6: Interrupts
Asynchronous event handling (UART reception, timers).
// UART interrupt configuration
void MyUART_EnableIT(USART_TypeDef * UART) {
UART->CR1 |= USART_CR1_RXNEIE; // Receive interrupt
NVIC_EnableIRQ(USART1_IRQn);
NVIC_SetPriority(USART1_IRQn, 1);
}
// UART interrupt handler
void USART1_IRQHandler(void) {
if (USART1->SR & USART_SR_RXNE) {
char received = USART1->DR;
// Command processing
if (received == 'L') {
// Turn left
} else if (received == 'R') {
// Turn right
}
}
}
// Timer interrupt configuration
void MyTimer_EnableIT(TIM_TypeDef * Timer, int period_ms) {
Timer->DIER |= TIM_DIER_UIE; // Update interrupt
NVIC_EnableIRQ(TIM2_IRQn);
}
// Timer interrupt handler (periodic task)
void TIM2_IRQHandler(void) {
if (TIM2->SR & TIM_SR_UIF) {
TIM2->SR &= ~TIM_SR_UIF; // Clear flag
// Periodic task (e.g.: sensor reading, regulation)
Sailboat_Regulate(&sailboat);
}
}
Difficulties encountered
Register configuration:
STM32 datasheets are voluminous (>1000 pages). Each bit of the configuration registers must be understood. Common mistake: forgetting to enable the peripheral clock (RCC).
Timing and interrupts:
Task conflicts: an interrupt that takes too long blocks others. Well-tuned priorities and short handlers are necessary.
Sensor calibration:
The wind vane requires calibration (offset, linearity). The compass must be compensated for magnetic disturbances.
Hardware debugging:
Problems sometimes difficult to diagnose: wiring, power supply, interference. Oscilloscope and logic analyzer are essential.
PART C - Detailed Technical Aspects
1. STM32F103 Architecture
Main specifications:
| Component | Specification |
|---|---|
| CPU | ARM Cortex-M3, 32-bit, 72 MHz |
| Flash | 128 KB (program) |
| RAM | 20 KB (data) |
| GPIO | 51 I/O pins |
| Timers | 4 advanced 16-bit timers |
| ADC | 2 x 12-bit ADC, 16 channels |
| Communication | 3 USART, 2 SPI, 2 I2C, USB, CAN |
Memory organization:
| Region | Addresses | Usage |
|---|---|---|
| Flash | 0x08000000 - 0x0801FFFF | Program code |
| SRAM | 0x20000000 - 0x20004FFF | Variables, stack |
| Peripherals | 0x40000000 - 0x5FFFFFFF | Memory-mapped registers |
| System | 0xE0000000 - 0xE00FFFFF | NVIC, SysTick |
2. GPIO (General Purpose Input/Output)
GPIO Registers:
Each port (A, B, C, D) has configuration and control registers.
| Register | Function |
|---|---|
| GPIOx_CRL | Configuration for pins 0-7 (4 bits per pin) |
| GPIOx_CRH | Configuration for pins 8-15 |
| GPIOx_IDR | Input Data Register (read) |
| GPIOx_ODR | Output Data Register (write) |
| GPIOx_BSRR | Bit Set/Reset Register (atomic) |
| GPIOx_BRR | Bit Reset Register |
Pin configuration:
Each pin requires 4 configuration bits: 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
Operating modes:
STM32 timers are versatile:
Simple counter mode:
- Counts from 0 to ARR (Auto-Reload Register)
- Generates interrupt on overflow
- Used for delays, periodic tasks
PWM mode:
- Compares counter (CNT) with compare value (CCR)
- Output HIGH if CNT < CCR, LOW otherwise
- Duty cycle = CCR / ARR
Capture mode:
- Captures CNT value on external event
- Frequency measurement, pulse duration
Main registers:
| Register | Function |
|---|---|
| TIMx_CR1 | Control Register (enable, counting mode) |
| TIMx_PSC | Prescaler (frequency divider) |
| TIMx_ARR | Auto-Reload (period) |
| TIMx_CNT | Current counter |
| TIMx_CCR1-4 | Capture/Compare (PWM duty cycle) |
| TIMx_CCMR1-2 | Channel configuration (PWM mode) |
| TIMx_CCER | Output enable |
PWM frequency calculation:
PWM Frequency = Clock / (PSC + 1) / (ARR + 1)
Example: 72 MHz / 72 / 20000 = 50 Hz (servo motor)
4. ADC (Analog-to-Digital Converter)
STM32 ADC characteristics:
- Resolution: 12 bits (0-4095)
- Conversion time: a few us
- Modes: single, continuous, scan (multi-channel)
- Trigger: software, timer, external
Configuration:
// Conversion sequence
ADC1->SQR1 = (num_conversions - 1) << 20; // Sequence length
ADC1->SQR3 = channel; // First channel
// Sampling time
ADC1->SMPR2 = 0x7 << (3 * channel); // 239.5 cycles (max precision)
// Start conversion
ADC1->CR2 |= ADC_CR2_ADON; // Enable + start
Acquisition modes:
| Mode | Description |
|---|---|
| Single | One conversion on demand |
| Continuous | Looping conversions |
| Scan | Multiple channels in sequence |
| Discontinuous | Channel subgroups |
5. UART/USART
Communication parameters:
| Parameter | Typical value |
|---|---|
| Baudrate | 9600, 115200 bps |
| Data bits | 8 bits |
| Parity bit | None |
| Stop bits | 1 bit |
BRR register calculation:
BRR = Peripheral_Clock / Baudrate
Example: 72 MHz / 115200 = 625
Buffer management:
To avoid data loss, use:
- Software circular buffer
- Receive interrupts
- Hardware flow control (RTS/CTS)
6. Interrupts (NVIC)
Nested Vectored Interrupt Controller:
The NVIC manages up to 68 interrupts with 16 priority levels.
Configuration:
// Enable interrupt
NVIC_EnableIRQ(USART1_IRQn);
// Priority (0 = highest)
NVIC_SetPriority(USART1_IRQn, 2);
// Disable
NVIC_DisableIRQ(USART1_IRQn);
Priorities:
The lower the number, the higher the priority. A higher-priority interrupt can preempt a running interrupt.
Best practices:
- Short and fast handlers
- No printf or delays in ISR
- Use flags for communication with main loop
- Protect shared variables (volatile)
7. Clock and RCC
Reset and Clock Control:
The RCC configures clocks and enables peripherals.
Clock sources:
- HSI: internal 8 MHz oscillator (accuracy +/-1%)
- HSE: external 8 MHz crystal (accuracy +/-50 ppm)
- PLL: multiplier to reach 72 MHz
Typical configuration:
HSE 8 MHz → PLL x9 → SYSCLK 72 MHz
Peripheral activation:
Each peripheral must be enabled via RCC before use:
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN; // GPIO A
RCC->APB2ENR |= RCC_APB2ENR_USART1EN; // USART1
RCC->APB1ENR |= RCC_APB1ENR_TIM2EN; // Timer 2
PART D - Reflective Analysis and Outlook
Acquired skills
Embedded programming:
Mastery of low-level C programming, direct register manipulation, understanding of hardware operation. Ability to read and interpret complex datasheets.
Driver development:
Design of reusable hardware abstraction layers (HAL). Modular code structure, interface/implementation separation.
System integration:
The Sailboat project developed the ability to integrate multiple subsystems (sensors, actuators, communication) into a coherent and functional application.
Hardware/software debugging:
Use of professional tools (oscilloscope, logic analyzer, debugger). Problem diagnosis methodology.
Key takeaways
1. Always enable the clock:
Mistake #1: forgetting RCC->APBxENR. The peripheral will not work without its clock.
2. Complete configuration:
GPIO, timers, ADC require precise configuration of many bits. Read the datasheet carefully.
3. Volatile for registers:
Hardware registers must be declared volatile to prevent compiler optimization.
4. Interrupts = short handlers:
Minimal processing in ISR, defer work to the main loop via flags.
5. Methodical debugging:
Start simple (LED blinking), progressively add complexity. Test each module separately before integration.
Sailboat project feedback
Positive aspects:
- Concrete and motivating project (real system)
- Freedom in design and implementation
- Teamwork with task distribution
- Direct application of all course concepts
Technical challenges:
- Sensor calibration (non-linear wind vane)
- Timing management (50 Hz regulation + telemetry)
- Electromagnetic interference (motors → sensors)
- Power consumption optimization (battery life)
Lessons learned:
- Importance of modularity (reusable drivers)
- Unit testing before global integration
- Code documentation (team understanding)
- Version control (Git) for collaborative work
Practical applications
Home automation:
Lighting control, heating, shutters. Wireless communication (Zigbee, LoRa).
Robotics:
Motor control, sensor reading (ultrasonic, infrared), autonomous navigation.
IoT (Internet of Things):
Connected objects: thermostats, trackers, weather stations. WiFi, Bluetooth communication.
Automotive:
ECU (Electronic Control Unit), ABS sensors, airbags, engine injection.
Medical:
Portable devices (glucometers, blood pressure monitors), infusion pumps, cardiac pacemakers.
Limitations and future directions
Course limitations:
- Little wireless communication (WiFi, Bluetooth)
- No RTOS (Real-Time Operating System)
- Little signal processing (filtering, FFT)
- No security (encryption, authentication)
Future directions:
- RTOS: FreeRTOS, multitasking, synchronization
- IoT communication: MQTT, CoAP, LoRaWAN
- Signal processing: digital filtering, FFT on MCU
- Advanced low-level: DMA, low power modes, bootloaders
- Machine Learning: TensorFlow Lite Micro for MCU
Technological evolution
Current trends:
- 32-bit MCUs everywhere: Cortex-M4, M7, RISC-V
- Integrated connectivity: WiFi, BLE, LoRa on chip
- Embedded AI: ML accelerators (Cortex-M55, NPU)
- Enhanced security: TrustZone, secure boot, hardware crypto
- Ultra-low power: nW for 10-year battery IoT
Modern tools:
- STM32CubeMX: automatic initialization code generation
- PlatformIO: cross-platform IDE (VS Code)
- Mbed OS: operating system for ARM
- Zephyr RTOS: open-source RTOS for IoT
Conclusion
The Microcontroller course is fundamental for any embedded systems engineer. Mastery of low-level programming and hardware interfacing is essential in a world where connected objects are ubiquitous.
The Sailboat project is the semester's defining experience: complete design of an autonomous system integrating sensors, actuators, and real-time control. The acquired skills (drivers, interrupts, peripherals) are directly transferable to industry.
Skills progression:
From a simple blinking LED to an autonomous sailboat, the journey shows the progression: GPIO → timers → ADC → UART → complete system integration. Each building block adds up to create increasingly complex systems.
Recommendations:
- Practice regularly (small personal projects)
- Read datasheets thoroughly (essential)
- Use oscilloscope and logic analyzer (debugging)
- Develop reusable drivers (portfolio)
- Participate in competitions (Robotics Cup, hackathons)
Links with other courses:
- ARM Assembly Language - S6: low-level understanding
- Digital Electronics Functions - S6: I2C/SPI bus
- Hardware Architecture - S6: pipeline, cache
- Real-Time Systems - S8: RTOS, scheduling
- Embedded AI for IoT - S9: embedded ML
Course Documents
Here are the PDF course materials for deeper study of STM32 microcontroller programming:
STM32 - Structures and Registers
Complete guide to C structures for STM32 register access, peripheral configuration and usage.
GPIO - Inputs/Outputs
GPIO configuration, input/output modes, pull-up/pull-down and pin manipulation with pointers.
Interrupts
Interrupt management, NVIC, priorities, handlers and real-time programming best practices.
Timers
Timer configuration and usage for delay generation, event counting and time measurements.
PWM - Pulse Width Modulation
PWM signal generation for motor control, LEDs and intensity variation using timers.
ADC - Analog-to-Digital Converter
ADC configuration, analog signal acquisition, trigger modes and usage with DMA.
Course taught in 2022-2023 at INSA Toulouse, Department of Electrical Engineering and Computer Science.