L’un des tout premiers exemples de MCU ou de SoC implique généralement le fameux exemple «Blinky», où une LED est allumée et éteinte avec un délai fixe. C’est en fait beaucoup plus compliqué que l’exemple «Pushy» que nous avons examiné dans le premier épisode de cette série. La raison en est qu’il y a en fait toute une histoire derrière un simple appel à delay() ou son équivalent.

La raison en est qu’il existe de nombreuses façons d’implémenter une fonction de retard sur un microcontrôleur (MCU), chacune ayant ses propres avantages et inconvénients. Sur un MCU STM32, nous pouvons choisir entre essentiellement un retard actif (while loop), une mise en œuvre en utilisant le minuteur SysTick et en utilisant l’un des minuteurs périphériques. Dans les deux derniers cas, nous devons également utiliser des interruptions.

Dans cet article, nous examinerons les trois approches, ainsi que leurs avantages et leurs inconvénients.

Récapitulation des bases

Dans les architectures Arm MCU, généralement le même cœur de processeur Cortex-M est utilisé dans la même famille de MCU par un fabricant tel que ST Microelectronics («ST»). Ce noyau est connecté via une variété de bus AMBA (Advanced Microcontroller Bus Architecture), AHB étant le bus rapide. AHB connecte le cœur à tous les périphériques nécessitant une faible latence et une faible vitesse, tels que la RAM, la ROM et les contrôleurs d’affichage.

Architecture système STM32F0xx (RM 2.1).

Pendant ce temps, l’APB plus lent connecte des périphériques qui fonctionnent bien avec moins de bande passante et des vitesses plus faibles, notamment les banques d’E / S à usage général (GPIO), I2C, les minuteries, les USART et les périphériques SPI. Dans l’exemple Pushy, le cœur du processeur interroge constamment le registre de données entrantes du périphérique GPIO (GPIO_IDR) et écrit dans le registre de données sortantes (GPIO_ODR) en fonction de ces valeurs d’entrée.

Entrez Blinky

Dans le cas de l’exemple Blinky, nous supprimons l’interrogation active du GPIO_IDR qui a été utilisé pour lire l’entrée du bouton, en nous appuyant à la place sur la fonction de retard qui est ajoutée. Chaque fois que nous revenons de cette fonction de retard de blocage, nous basculons le bit GPIO_ODR, ce qui fait que la LED qui est connectée sur cette broche soit allumée ou non.

L’exemple de code implémenté à l’aide de mon framework Nodate devient ainsi:

#comprendre <gpio.h>
#comprendre <timer.h>
int principale () {
// Initialisez.
Minuterie;
//const uint8_t led_pin = 3; // Nucleo-f042k6: Port B, broche 3.
//const GPIO_ports led_port = GPIO_PORT_B;
//const uint8_t led_pin = 13; // STM32F4-Discovery: Port D, broche 13 (orange)
//const GPIO_ports led_port = GPIO_PORT_D;
//const uint8_t led_pin = 7; // Nucleo-F746ZG: Port B, broche 7 (bleu)
//const GPIO_ports led_port = GPIO_PORT_B;
const uint8_t led_pin = 13; // Blue Pill: Port C, broche 13.
const GPIO_ports led_port = GPIO_PORT_C;
// Définissez le mode broche sur la broche LED.
GPIO :: set_output(led_port, led_pin, GPIO_PULL_UP);
GPIO :: écriture(led_port, led_pin, GPIO_LEVEL_LOW);
tandis que (1) {
GPIO :: écriture(led_port, led_pin, GPIO_LEVEL_HIGH);
minuteur.retard(1000);
GPIO :: écriture(led_port, led_pin, GPIO_LEVEL_LOW);
minuteur.retard(1000);
}
revenir 0;
}

Cet exemple définit quelques préréglages pour différentes cartes, avec ici la version ‘Blue Pill’ (STM32F103C8) utilisée. Nous ne reviendrons pas ici sur le module GPIO, car les fonctions GPIO utilisées dans cet exemple ont déjà été expliquées dans le premier article de la série. N’hésitez pas à y jeter un coup d’œil si vous avez besoin d’un rappel.

Nous nous concentrerons plutôt sur le module Timer, la façon dont il delay() la fonction est mise en œuvre, ainsi que les deux approches alternatives.

Le délai actif low-tech

Une fonction de retard entièrement utilisable, fonctionnelle et raisonnablement précise peut être mise en œuvre dans un environnement nu en utilisant rien de plus qu’un humble while() boucle. Cela exploite la nature discrète des cycles de processeur en combinaison avec la connaissance de l’horloge système actuelle. Essentiellement, cela signifie convertir le retard souhaité en temps processeur et compter à rebours (ou à augmenter) jusqu’à cet intervalle.

Supposons que le MCU (SysClock) est cadencé à 48 MHz. Si nous voulons retarder avec une résolution de microseconde, nous devons multiplier la valeur de l’intervalle μs par 48 pour obtenir le nombre cible de cycles d’horloge que nous souhaitons attendre. Naturellement, chaque itération du while() loop prend plus d’un cycle d’horloge, nous devons donc diviser le nombre de cycles d’horloge par la durée d’une itération de boucle. Disons que cela prend 4 cycles d’horloge pour une itération de boucle, nous obtenons:

int count = (usecs * 48) / 4;
while (int i = 0; i < count; ++i) {
    count--;
}

Évidemment, c’est un moyen assez simple d’implémenter une fonction de retard, une fois que l’on a calculé les paramètres appropriés. Comme pour beaucoup de choses dans la vie, quand quelque chose est aussi simple, cela doit venir avec toute une liste de pièges. Dans le cas de cette fonction de retard, on n’est pas déçu à cet égard.

Tout d’abord, son manque de précision. Même si nous n’avons pas utilisé de valeurs entières pour calculer l’intervalle de cycle d’horloge approprié, il y a toujours la faiblesse inévitable en ce que cette fonction s’exécute entièrement sur le noyau (unique) du processeur. Au moment où une seule interruption se produit (par exemple à partir d’un minuteur, d’un périphérique USART, I2C ou GPIO), le décompte du nombre de cycles nécessaire pour traiter cette interruption et revenir à l’exécution de la tâche principale sera annulé.

Le fait qu’il s’agisse d’un délai actif, qui occupe entièrement (en d’autres termes bloque) le processeur signifie en outre qu’il ne fonctionne pas pour un environnement multitâche. En fait, c’est le type de fonction de retard que vous ne voulez vraiment utiliser que comme fonction de mise au rebut rapide et sale pendant les tests ou le débogage.

Compter les SysTicks

Le noyau Cortex-M possède également quelques périphériques standard qui sont intégrés directement dans ce noyau. Celles-ci sont couvertes dans le manuel de programmation (PM) pour chaque famille MCU, par exemple pour STM32F0. Ces soi-disant périphériques Core incluent le SysTick Timer (STK ou SysTick), le contrôleur d’interruption vectorielle imbriqué (NVIC) et le bloc de contrôle système (SCB). Parmi ceux-ci, NVIC est, comme son nom l’indique, essentiel pour l’enregistrement et la gestion des interruptions.

La minuterie SysTick est une minuterie assez simple, qui peut essentiellement compter à rebours de la valeur définie à zéro. Bien que cela ne semble pas étonnant, il utilise l’horloge du processeur, cela signifie qu’il ne sera pas affecté par les interruptions et autres événements qui interrompraient le temporisateur de retard actif que nous avons examiné plus tôt.

Comme nous l’avons vu dans l’exemple de code Blinky, nous créons d’abord une instance de la classe Timer. Cela configure quelques éléments dans le constructeur de la classe Timer:

Minuterie :: Minuterie() {
// Définissez le registre de rechargement pour générer une interruption toutes les millisecondes.
SysTick->CHARGE = (uint32_t) ((SystemCoreClock / 1000) – 1);
// Réinitialisez la valeur du compteur SysTick.
SysTick->VAL = 0UL;
// Définissez la source SysTick et l’IRQ.
SysTick->CTRL = (SysTick_CTRL_CLKSOURCE_Msk | SysTick_CTRL_TICKINT_Msk);
}

Plus important encore, nous définissons la valeur à partir du compte à rebours. Cela utilise la valeur globale SystemCoreClock avec l’horloge système actuelle en Hertz, en la divisant pour créer la valeur équivalente pendant 1 milliseconde. Ceci est écrit dans STK_RVR (appelé LOAD dans CMSIS).

Nous choisissons également la source d’horloge à utiliser avec le périphérique SysTick, qui est ici «Horloge du processeur». Enfin, nous activons la génération d’interruptions chaque fois que le décompte atteint zéro. Avec tout cela configuré, le delay() la fonction peut être utilisée:

statique volatil uint32_t DelayCounter;
néant SysTick_Handler() {
DelayCounter ++;
}
néant Minuterie :: retard(uint32_t SP) {
// Activer le minuteur SysTick
SysTick->CTRL | = SysTick_CTRL_ENABLE_Msk;
// Attendez un nombre spécifié de millisecondes
DelayCounter = 0;
tandis que (DelayCounter
// Désactiver le minuteur SysTick
SysTick->CTRL & = ~ SysTick_CTRL_ENABLE_Msk;
}

Comme mentionné, chaque fois que le minuteur SysTick atteint zéro, il génère une interruption. Nous avons réimplémenté le gestionnaire d’interruption SysTick_Handler(), de sorte qu’à chaque appel, nous pouvons incrémenter une variable de compteur globale. La fonction de délai elle-même démarre le minuteur SysTick et attend que la variable de compteur globale atteigne la valeur cible qui lui a été transmise en tant que paramètre. Une fois cette opération terminée, le minuteur SysTick est de nouveau désactivé et la fonction revient.

Les avantages par rapport à une boucle de retard active devraient être assez évidents: en utilisant un périphérique matériel dans le cœur Cortex-M, nous sommes assurés d’une bonne précision. Avec un changement tel que l’utilisation de l’intervalle cible comme valeur STK_RVR par exemple, nous pourrions réduire davantage toute attente active. SysTick peut également être utilisé pour une minuterie centrale du système, en gardant une trace de la disponibilité et des intervalles. Ici, cependant, on s’égarerait rapidement dans le domaine des systèmes d’exploitation embarqués à part entière.

Bien qu’un peu plus compliqué qu’une simple boucle while (), il n’y a aucune bonne raison de choisir un délai de blocage au lieu de la méthode SysTick bien supérieure.

Périphériques de minuterie

Bien que le minuteur SysTick soit pratique en ce qu’il est universellement présent dans les MCU STM32 et extrêmement simple à utiliser, cette simplicité présente également certains inconvénients. Le fait qu’il n’y ait qu’un seul périphérique SysTick n’est pas le moindre. Heureusement, la plupart des MCU STM32 sont livrés avec une sélection de périphériques de minuterie supplémentaires qui peuvent également être utilisés pour ajouter des fonctions de retard, en fonction de vos besoins.

le Livre de recettes de minuterie à usage général pour microcontrôleurs STM32 (AN4776) document de ST donne un exemple de la façon d’implémenter une boucle de retard de base dans la section 1.3.2, en utilisant le périphérique TIM6:

#définir ANY_DELAY_RQUIRED 0x0FFF
/ * Implémentation de boucle de retard de précision matérielle à l’aide du minuteur TIM6
périphérique. Toute autre minuterie STM32 peut être utilisée pour remplir cette fonction, mais
Le temporisateur TIM6 a été choisi car il a le niveau d’intégration le moins élevé. Autre minuterie
les périphériques peuvent être réservés pour des tâches plus compliquées * /
/ * Effacer l’indicateur d’événement de mise à jour * /
TIM6-> SR = 0
/ * Définissez le délai requis * /
/ * La valeur de réinitialisation du presclaer de la minuterie est 0. Si un délai plus long est nécessaire,
le registre de présaceur peut être configuré pour * /
/ *TIM6-> PSC = 0 * /
TIM6-> ARR = ANY_DELAY_RQUIRED
/ * Démarrer le compteur de la minuterie * /
TIM6-> CR1 | = TIM_CR1_CEN
/ * Boucle jusqu’à ce que l’indicateur d’événement de mise à jour soit défini * /
tandis que (! (TIM6-> SR & TIM_SR_UIF));
/ * Le délai requis est écoulé * /
/ * Le code utilisateur peut être exécuté * /

Naturellement, ce qui est amusant avec les périphériques de minuterie STM32, c’est qu’il y en a tellement de choix. Chacun d’entre eux se situe dans une certaine gamme de complexité, qui va de «assez basique» à «tout et l’évier de la cuisine». Les minuteries avancées sont celles que vous souhaitez utiliser pour la modulation de largeur d’impulsion (PWM) et les tâches plus complexes, en laissant une avec quelques minuteries de base, dont le nombre exact dépend du MCU.

Bien que nous puissions compter sur la présence permanente de la minuterie SysTick, d’autres périphériques de minuterie sont moins prévisibles et nécessitent plus d’efforts pour les configurer. Cela les rend plus adaptés aux tâches spécialisées, plutôt que d’implémenter une boucle de retard. Ce qui ne veut pas dire qu’on ne pourrait pas le faire, mais les avantages devraient être évidents.

Expiration du délai

Cela dit, j’espère que cela donnera une image plus claire des boucles de retard sur les MCU STM32. Ce ne sont guère toutes les options non plus, certaines personnes entreprenantes utilisant même le matériel de débogage intégré (par exemple, Data Watchpoint Trigger, DWT) pour la fonctionnalité de minuterie. Pourtant, la portabilité et la facilité d’utilisation devraient également être des considérations.

À la fin de cet article, je voudrais répéter à nouveau que malgré la représentation courante de l’exemple de «Blinky», il y a en fait beaucoup de choses à faire pour que cela fonctionne.