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é.
#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 __WFI | 4.90 mA | 6.82 mA |
Avec __WFI | 3.00 mA | 4.92 mA |