for (int i = 0; i < delai; i++);Passée l'envie de sortir le goudron et les plumes (ou le verre pilé, chacun ses goûts) on peut accorder à ce "code" une certaine simplicité. Mais bien que cette boucle ne semble rien faire, il n'en est pas de même pour le CPU: il passe son temps à comparer, incrémenter et sauter à une adresse. Pas l'idéal quand on se soucie de la consommation électrique de la carte. De plus, il est dommage de confier une tâche aussi simple à un composant aussi complexe et puissant qu'un CPU. Pour finir, il est possible que le compilateur supprime purement et simplement ce "code" (si on utilise un niveau maximal d'optimisation).
#define DELAY_1_MS (SystemCoreClock / 1000) #define DELAY_10_MS (SystemCoreClock / 100) #define DELAY_100_MS (SystemCoreClock / 10)La valeur maximale de SysTick est codée sur 24 bits soit 16 777 216 - 1, soit environ 2 secondes à 8 MHz. Il faudra garder à l'esprit cette limitation et ne pas faire:
#define DELAY_1_SEC SystemCoreClockquand on commencera à modifier la fréquence du CPU et que SystemCoreClock vaudra 72 000 000.
1 #include "stm32f1xx.h" 2 3 #define DELAY_1_MS (SystemCoreClock / 1000) 4 #define DELAY_LED 250 5 6 int 7 main (void) { 8 SystemCoreClockUpdate(); // Update globale variable SystemCoreClock 9 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 // https://developer.arm.com/documentation/dui0552/a/cortex-m3-peripherals/system-timer--systick/systick-control-and-status-register 14 SysTick->LOAD = (DELAY_LED * DELAY_1_MS) - 1; // Countdown from (250 * (SystemCoreClock / 1000)) - 1 to zero (== 1/4 second) 15 SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk | SysTick_CTRL_ENABLE_Msk; // Set processor clock as clock source + Enable SysTick 16 17 while (1) { 18 while ((SysTick->CTRL & SysTick_CTRL_COUNTFLAG_Msk) == 0); // 0 reached 19 GPIOC->ODR ^= GPIO_ODR_ODR13; // GPIO C PIN 13 output toggle state 20 } 21 22 return 0; 23 }La définition du composant SysTick et de ses registres se trouvent dans le fichier STM32-base-STM32Cube-master/CMSIS/ARM/inc/core_cm3.h. C'est dans le registre LOAD que l'on place la valeur maximale puis tout se passe dans le registre CTRL:
$ cat systick.c #include "stm32f1xx.h" #define SYSTICK_DELAY_1_MS (SystemCoreClock / 1000) void systick_delay_load(uint32_t load) { SysTick->LOAD = load; SysTick->CTRL |= SysTick_CTRL_CLKSOURCE_Msk | SysTick_CTRL_ENABLE_Msk; while ((SysTick->CTRL & SysTick_CTRL_COUNTFLAG_Msk) == 0); SysTick->CTRL = 0; } void systick_delay_ms(uint32_t millis) { SysTick->LOAD = SYSTICK_DELAY_1_MS - 1; SysTick->CTRL |= SysTick_CTRL_CLKSOURCE_Msk | SysTick_CTRL_ENABLE_Msk; while (millis--) { while ((SysTick->CTRL & SysTick_CTRL_COUNTFLAG_Msk) == 0); } SysTick->CTRL = 0; }Parce que l'appel à une de ces fonctions, le passage du paramètre, la configuration de SysTick, la (ou les) boucle(s), la comparaison ... consomment du CPU donc des ticks donc du temps, j'estime que la milli-seconde est l'ordre de grandeur à privilégier pour ce composant: on compte grosso-merdo une milli-seconde et quelques dizaines de micro-secondes. Il est de la responsabilité de l'appelant de vérifier que le paramètre est "valide", une gestion d'erreur rajouterai une complexité inutile au regard de la simplicité du code.
Pourquoi 2 fonctions ? La première, plus "légère", laisse à l'appelant la responsabilité de la valeur de load. On a vu que la taille de cette valeur implique un délai maximum de 2 secondes (9 fois moins si on configure le CPU à pleine vitesse). La seconde, plus "intuitive" mais plus "lourde" (calcul de load à chaque appel, double boucle) peut traiter, quelle que soit la vitesse du CPU, un très grand nombre de milli-seconde.
En ajoutant quelques "define", il est possible d'utiliser l'une ou l'autre ou les 2 fonctions:
$ cat systick.c #if USE_SYSTICK_DELAY_LOAD void systick_delay_load(uint32_t load) { ... } #endif /* USE_SYSTICK_DELAY_LOAD */ #if USE_SYSTICK_DELAY_MS #define SYSTICK_DELAY_1_MS (SystemCoreClock / 1000) void systick_delay_ms(uint32_t millis) { ... } #endif /* USE_SYSTICK_DELAY_MS */ $ cat systick.h #pragma once #if !USE_SYSTICK_DELAY_LOAD && !USE_SYSTICK_DELAY_MS #error "USE_SYSTICK_DELAY not defined" #endif #if USE_SYSTICK_DELAY_LOAD void systick_delay_load(uint32_t load); #endif #if USE_SYSTICK_DELAY_MS void systick_delay_ms(uint32_t millis); #endifPour faire clignoter la LED toutes les 250 milli-secondes le "code" suivant peut sembler "lourd" mais c'est parce qu'il illustre l'usage d'une (ou des deux) fonctions:
#include "stm32f1xx.h" #include "systick.h" #if USE_SYSTICK_DELAY_LOAD #define DELAY_1_MS (SystemCoreClock / 1000) #endif #define DELAY_LED 250 int main(void) { #if USE_SYSTICK_DELAY_LOAD uint32_t load; #endif SystemCoreClockUpdate(); // Update globale variable SystemCoreClock #if USE_SYSTICK_DELAY_LOAD load = DELAY_LED * DELAY_1_MS; // OK @8 MHz #endif 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 while (1) { #if USE_SYSTICK_DELAY_LOAD systick_delay_load(load); #elif USE_SYSTICK_DELAY_MS systick_delay_ms(DELAY_LED); #endif GPIOC->ODR ^= GPIO_ODR_ODR13; // GPIO C PIN 13 output toggle state } return 0; }Si les délais d'allumage et d'extinction de la LED devaient être différents:
#include "stm32f1xx.h" #include "systick.h" #define DELAY_1_MS (SystemCoreClock / 1000) #define DELAY_LED 250 int main(void) { uint32_t load; SystemCoreClockUpdate(); // Update globale variable SystemCoreClock load = DELAY_LED * DELAY_1_MS; // OK @8 MHz 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 // LED is on while (1) { systick_delay_load(load); // 250 ms GPIOC->ODR ^= GPIO_ODR_ODR13; // GPIO C PIN 13 output toggle state systick_delay_ms(DELAY_LED * 4); // 1000 ms GPIOC->ODR ^= GPIO_ODR_ODR13; // GPIO C PIN 13 output toggle state } return 0; }Ici la LED reste allumée 250 ms et s'éteint durant 1 000 ms et, sans aucun soucis d'optimisation, on utilise les deux fonctions. En "production" les contraintes du projet décideront de la (bonne) fonction à utiliser.
Il est à noter que ce "code" est bloquant: le CPU ne fera rien d'autre que d'attendre, ce qui est rarement une bonne chose dans le domaine de l'embarqué. C'est pourquoi le composant SysTick peut aussi lever une interruption, bien plus efficace.
Le code complet: https://gitlab.com/dsx/blue-pill/-/tree/master/stm32f103c8t6/00_gpio_systick_delay