Blue Pill - Interruption

Table des matières

TL;DR

Faire clignoter une LED en levant une interruption.

Une interruption

Le code suivant illustre le fonctionnement d'une interruption:
     1	#include "stm32f1xx.h"
     2	#include "systick.h"
     3	
     4	#define UINT24_MAX 16777216
     5	
     6	int
     7	main (void) {
     8	    RCC->APB2ENR |= RCC_APB2ENR_IOPCEN; // Enable GPIO C 
     9	    GPIOC->CRH   |= GPIO_CRH_MODE13_1;  // GPIO C PIN 13 mode 0b10: Output mode, max speed 2 MHz
    10	
    11	    systick_delay_irq(UINT24_MAX - 1);
    12	
    13	    return 0;
    14	}
    15	
    16	void
    17	SysTick_Handler(void) {
    18	    GPIOC->ODR ^= GPIO_ODR_ODR13; // GPIO C PIN 13 output toggle state
    19	}
La disparition de la boucle while (1) n'est pas une erreur: le programme se "termine" bien à ligne 13. Le "secret" réside dans la mise en place d'une interruption par le composant SysTick. Cette interruption va provoquer l'exécution d'une fonction qui lui est associée et dont le nom est obligatoirement SysTick_Handler. Les noms des fonctions associées à une interruption d'un composant sont listés dans le fichier STM32-base-master/startup/STM32F1xx/STM32F103xB.s et sont donc spécifiques à la carte.

Les lignes 16 à 19 correspondent à la fonction appelée lorsque SysTick va lever une interruption. On se contente de basculer l'état de sortie de la PIN 13 du GPIO C. Sans configuration particulière du CPU la LED va clignoter toutes les 2 secondes environ. La fonction systick_delay_irq est assez simple:

void
systick_delay_irq(uint32_t load) {
    SysTick->LOAD = load;
    SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk | SysTick_CTRL_TICKINT_Msk | SysTick_CTRL_ENABLE_Msk;
}
On charge le registre LOAD, on active en plus le bit numéro 1 (SysTick_CTRL_TICKINT_Msk) du registre CTRL afin qu'une interruption soit levée lorsqu'on atteint zéro et le tour est joué.

Principe d'interruption

Le principe de base consiste à configurer un (ou des) composant(s) pour qu'il(s) lève(nt) une interruption qui, comme son nom l'indique, va interrompre le fonctionnement normal du programme (la boucle "while (1) { ... }"). Moins la fonction d'interruption exécute de code, plus vite le programme retrouve son état normal. Le "code" suivant fait clignoter la LED toutes les 10 secondes environ:
#include "stm32f1xx.h"

#define UINT24_MAX    16777216
#define SYSTICK_IRQS  5

int systick_irqs;

int
main (void) {
    RCC->APB2ENR |= RCC_APB2ENR_IOPCEN; // Enable GPIO C 
    GPIOC->CRH   |= GPIO_CRH_MODE13_1;  // GPIO C PIN 13 mode 0b10: Output mode, max speed 2 MHz

    SysTick->LOAD = UINT24_MAX - 1;
    SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk | SysTick_CTRL_TICKINT_Msk | SysTick_CTRL_ENABLE_Msk;

    systick_irqs = 0;
    while (1) {
        if (systick_irqs == SYSTICK_IRQS) {
            systick_irqs = 0;
            GPIOC->ODR ^= GPIO_ODR_ODR13; // GPIO C PIN 13 output toggle state
        }
    }

    return 0;
}

void
SysTick_Handler(void) {
    systick_irqs++;
}
Seule ombre au tableau: le CPU passe son temps dans la boucle à comparer la variable systick_irqs qui n'est mise à jour que lors d'une interruption. C'est là que commence la beauté de l'embarqué: il est possible de mettre en sommeil le CPU tant qu'une interruption n'est pas levée. Pour se faire, on utilise la fonction __WFI() (Wait For Interrupt) ligne 18:
     1	#include "stm32f1xx.h"
     2	
     3	#define UINT24_MAX    16777216
     4	#define SYSTICK_IRQS  5
     5	
     6	int systick_irqs;
     7	
     8	int
     9	main (void) {
    10	    RCC->APB2ENR |= RCC_APB2ENR_IOPCEN; // Enable GPIO C 
    11	    GPIOC->CRH   |= GPIO_CRH_MODE13_1;  // GPIO C PIN 13 mode 0b10: Output mode, max speed 2 MHz
    12	
    13	    SysTick->LOAD = UINT24_MAX - 1;
    14	    SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk | SysTick_CTRL_TICKINT_Msk | SysTick_CTRL_ENABLE_Msk;
    15	
    16	    systick_irqs = 0;
    17	    while (1) {
    18	        __WFI();
    19	        if (systick_irqs == SYSTICK_IRQS) {
    20	            systick_irqs = 0;
    21	            GPIOC->ODR ^= GPIO_ODR_ODR13; // GPIO C PIN 13 output toggle state
    22	        }
    23	    }
    24	
    25	    return 0;
    26	}
    27	
    28	void
    29	SysTick_Handler(void) {
    30	    systick_irqs++;
    31	}
Le résultat sur la consommation électrique est immédiat:
Min (LED off)Max (LED on)
Sans __WFI4.90 mA6.82 mA
Avec __WFI3.00 mA4.92 mA
Le code complet: https://gitlab.com/dsx/blue-pill/-/tree/master/stm32f103c8t6/00_gpio_systick_delay_irq_wfi

A retenir

Si les ressources CPU sont précieuses, il en est de même pour l'énergie: si le CPU n'a rien d'autre à faire qu'à attendre alors autant qu'il dorme.