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 :

Architecture STM32

Figure : Architecture d'un microcontroleur STM32 - CPU ARM Cortex-M4 avec peripheriques

Le voilier autonome integre plusieurs sous-systemes :

Sous-systemeComposantsFonction
NavigationGirouette, boussole, GPSDeterminer position et orientation
ControleServo-moteursAjuster gouvernail et voile
CommunicationUART, radioTelemetrie et commandes
AlimentationBatterie, regulateurAutonomie 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 :

ComposantSpecification
CPUARM Cortex-M3, 32 bits, 72 MHz
Flash128 KB (programme)
RAM20 KB (donnees)
GPIO51 broches I/O
Timers4 timers 16 bits avances
ADC2 ADC 12 bits, 16 canaux
Communication3 USART, 2 SPI, 2 I2C, USB, CAN

Organisation memoire :

ZoneAdressesUsage
Flash0x08000000 - 0x0801FFFFCode programme
SRAM0x20000000 - 0x20004FFFVariables, pile
Peripheriques0x40000000 - 0x5FFFFFFFRegistres memory-mapped
Systeme0xE0000000 - 0xE00FFFFFNVIC, SysTick

2. GPIO (General Purpose Input/Output)

Registres GPIO :

Chaque port (A, B, C, D) dispose de registres de configuration et de controle.

RegistreFonction
GPIOx_CRLConfiguration broches 0-7 (4 bits par broche)
GPIOx_CRHConfiguration broches 8-15
GPIOx_IDRInput Data Register (lecture)
GPIOx_ODROutput Data Register (ecriture)
GPIOx_BSRRBit Set/Reset Register (atomique)
GPIOx_BRRBit Reset Register

Configuration d'une broche :

Chaque broche necessite 4 bits de configuration : MODE (2 bits) + CNF (2 bits).

MODECNFConfiguration
0001Input floating
0010Input pull-down/up
0100Output push-pull 10 MHz
1000Output push-pull 2 MHz
1100Output push-pull 50 MHz
1110Alternate 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 :

RegistreFonction
TIMx_CR1Control Register (activation, mode comptage)
TIMx_PSCPrescaler (diviseur frequence)
TIMx_ARRAuto-Reload (periode)
TIMx_CNTCompteur actuel
TIMx_CCR1-4Capture/Compare (PWM duty cycle)
TIMx_CCMR1-2Configuration canaux (PWM mode)
TIMx_CCERActivation 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 :

ModeDescription
SimpleUne conversion sur commande
ContinuConversions en boucle
ScanPlusieurs canaux en sequence
DiscontinuSous-groupes de canaux

5. UART/USART

Parametres de communication :

ParametreValeur typique
Baudrate9600, 115200 bps
Bits de donnees8 bits
Bit de pariteAucun
Bits de stop1 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 :


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.

Telecharger le PDF

GPIO - Entrees/Sorties

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

Telecharger le PDF

Interruptions

Gestion des interruptions, NVIC, priorites, handlers et bonnes pratiques de programmation temps reel.

Telecharger le PDF

Timers

Configuration et utilisation des timers pour generation de delais, comptage d'evenements et mesures temporelles.

Telecharger le PDF

PWM - Modulation de Largeur d'Impulsion

Generation de signaux PWM pour commande de moteurs, LEDs et variation d'intensite avec les timers.

Telecharger le PDF

ADC - Convertisseur Analogique-Numerique

Configuration de l'ADC, acquisition de signaux analogiques, modes de declenchement et utilisation avec DMA.

Telecharger le PDF


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:

STM32 Architecture

Figure: STM32 microcontroller architecture - ARM Cortex-M4 CPU with peripherals

The autonomous sailboat integrates several subsystems:

SubsystemComponentsFunction
NavigationWind vane, compass, GPSDetermine position and orientation
ControlServo motorsAdjust rudder and sail
CommunicationUART, radioTelemetry and commands
Power supplyBattery, regulatorEnergy 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:

ComponentSpecification
CPUARM Cortex-M3, 32-bit, 72 MHz
Flash128 KB (program)
RAM20 KB (data)
GPIO51 I/O pins
Timers4 advanced 16-bit timers
ADC2 x 12-bit ADC, 16 channels
Communication3 USART, 2 SPI, 2 I2C, USB, CAN

Memory organization:

RegionAddressesUsage
Flash0x08000000 - 0x0801FFFFProgram code
SRAM0x20000000 - 0x20004FFFVariables, stack
Peripherals0x40000000 - 0x5FFFFFFFMemory-mapped registers
System0xE0000000 - 0xE00FFFFFNVIC, SysTick

2. GPIO (General Purpose Input/Output)

GPIO Registers:

Each port (A, B, C, D) has configuration and control registers.

RegisterFunction
GPIOx_CRLConfiguration for pins 0-7 (4 bits per pin)
GPIOx_CRHConfiguration for pins 8-15
GPIOx_IDRInput Data Register (read)
GPIOx_ODROutput Data Register (write)
GPIOx_BSRRBit Set/Reset Register (atomic)
GPIOx_BRRBit Reset Register

Pin configuration:

Each pin requires 4 configuration bits: MODE (2 bits) + CNF (2 bits).

MODECNFConfiguration
0001Input floating
0010Input pull-down/up
0100Output push-pull 10 MHz
1000Output push-pull 2 MHz
1100Output push-pull 50 MHz
1110Alternate 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:

RegisterFunction
TIMx_CR1Control Register (enable, counting mode)
TIMx_PSCPrescaler (frequency divider)
TIMx_ARRAuto-Reload (period)
TIMx_CNTCurrent counter
TIMx_CCR1-4Capture/Compare (PWM duty cycle)
TIMx_CCMR1-2Channel configuration (PWM mode)
TIMx_CCEROutput 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:

ModeDescription
SingleOne conversion on demand
ContinuousLooping conversions
ScanMultiple channels in sequence
DiscontinuousChannel subgroups

5. UART/USART

Communication parameters:

ParameterTypical value
Baudrate9600, 115200 bps
Data bits8 bits
Parity bitNone
Stop bits1 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:


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.

Download PDF

GPIO - Inputs/Outputs

GPIO configuration, input/output modes, pull-up/pull-down and pin manipulation with pointers.

Download PDF

Interrupts

Interrupt management, NVIC, priorities, handlers and real-time programming best practices.

Download PDF

Timers

Timer configuration and usage for delay generation, event counting and time measurements.

Download PDF

PWM - Pulse Width Modulation

PWM signal generation for motor control, LEDs and intensity variation using timers.

Download PDF

ADC - Analog-to-Digital Converter

ADC configuration, analog signal acquisition, trigger modes and usage with DMA.

Download PDF


Course taught in 2022-2023 at INSA Toulouse, Department of Electrical Engineering and Computer Science.