Amusons nous avec un bouton et des LEDs sur une carte NodeMCU basée sur un ESP32-C3 (microcontrôleur RISC-V) avec ESP-IDF (FreeRTOS)

* This post is also available in English.

Contenu


* Introduction
* Matériel : Le ircuit
** Composants
** Breadboard (planche à pain)
** Choix des ports GPIO et de leur broches sur la carte
** partie LED
** Résistances
** Partie bouton
* Le logiciel
** Initialisation
** Boucle principale
** Routine de Service d’intérruption (ou ISR, Interrupt Service Routine)
** Supprimer l’effet de rebond (Debouncing)
*** Chronomètre ESP

Introduction

Mise à jour: J’ai écris, à la suite de cet article, un autre enseignant l’utilisation d’un potentiomètre et d’un écran OLED..

Suite à la mise à jour d’ArchLinux de python 3.9 vers 3.10, les outils ont du être réinstallés en effectuant :

cd ~/esp/esp-idf
git pull
git submodule update --init --recursive
./install.sh esp32c3

Si vous n’avez jamais utilisé ESP-IDF, vous pouvez lire l’article précédent, d’introduction à ESP-IDF sur les ESP32-C3 basées sur du RISC-V, comment l’installer et lancer l’environment pour compiler et flasher le code. J’ai également écrit un article à propos de l’utilisation d’ESP32-C3 avec le système embarqué POSIX, Apache NuttX , mais il sera inutile ici.

Cet article concerne, à propos le développement, sur ESP32 (plus spécifiquement sur une carte NodeMCU basée sur un ESP32-C3 à moins de 3.5 €, mais il devrait fonctionner à peu près à l’identique sur les autres cartes basées sur de l’ESP) :
* Comment faire clignoter une LED externe en utilisant les ports dits GPIO, en expliquant comment déterminer la tension (en volts, V) et l’intensité (en ampères, A) nécessaires au fonctionnement de la LED, ainsiq que le calcul de la résistance nécessaire, par différents moyens.
* Des explications sur les bandes de couleur des résistances et le calcul des résistances montées en parallèles. Je donne également un lien vers un logiciel libre et à source ouverte que j’ai écrit, pour aider à calculer les résistances nécessaires (en fonction du type de LED, et de l’intensité désirée).
* Comment connectet un commutateur externe aux probches GPIO, et la résistance nécessaire. Comment recevoir et gérer de la bonne façon son état. Notamment en supprimant l’effet de rebond, (debouncing) liée à la pression physique d’un humain sur le bouton, ainsi que l’utilisationd el’interruption logicielle (c’est une tâche plus facile qu’il n’y paraît).
* Comment faire clignoter la LED RVB incluse et l’arrếter/démarraer en utilisant un interrupteur, de façon asyncrone.

Matériel : Le Circuit


Après 20 ans sans avoir pratiqué l’électronique et quelques recherches sur la façon de créer des cicrcuits, pour une LED et un bouton, j’ai trouvé différents tutoriaux incomplets pour certaines parties. J’ai finallement trouvé un article donnant des eplixations pour cette carte, mais en utilisant Arduino, et avec pas mal d’erreur importantes. Une résistance trop forte, et au mauvais endroit. Après avoir réuni les informations de plusieurs sources et après pas mal de tests, j’ai décidé d’écrire un tutoriel complet pour les débutants comme moi. Cela pourrait m’aider à comprendre de nouveau et retrouver facilement toutes les bases lorsque nécessaire, et j’espère qu’il sera utiles pour d’autres également.

Composants

Un outil simple pour tester et apprendre les circuits électroniques est une planche à pain (en anglais breadboard), elle permet de tester sans avoir à souder quoi que ce soit. Elle peut donc également être utilisée par des enfants, comme une tension de 3,3 V alimenté par les 2,4 A maximum d’un USB 2 ou 3 n’est pas dangereuse du tout.

* Nous avons également besoin d’une LED de 5 mm, que l’on peut trouver dans des vieux cercuits électroniques si besoin (plutôt que de les jeter) ou pour vraiment pas cher, nous utilisons ici une LED rouge qui est parfaite pour représenter un état allumé (ON) ou éteind (OFF) et, qui, avec une tension de 1,8 V peut être gérée par une carte fournissant une tension d’alimentation de 3,3 V.
* Nous avons également besoin d’un bouton de commutation. Un capuchon est plus confortable mais n’est pas indispensable pour le faire fonctionner.
* Nous avons enfin besoin d’une résistance, si vous en avez de 75 ohms (75 Ω), ou une de 100 Ω et une de 330 Ω, c’est parfais, sinon une de 100 ohm seule replira relativement bien cette tâche. De 100 à 330 Ω il y aura toujours de la lumière, mais ça ne sera probablement pas très brillante.

Vous pouvez également utiliser une résistance de 10 KΩ pour le bouton, appellée pull-up (ou pull-down) pour avoir un signal plus précis, mais une résistance externe n’est pas nécessaire avec ce type de carte qui contient déjà des résistances pull-up/pull-down internes, c’est à la fois plus efficace et il est possible de les dés/activer en logiciel avec la fonction esp_err_t gpio_set_pull_mode(gpio_num_t gpio_num, gpio_pull_mode_t pull). Nous n’aurons pas besoin d’utilier le mode Open drain (drainage ouvert), utilisé avec les périphériques SPI, où les pull-up/pull-down sont utilises pour changer les signos actifs/inactifs.

Breadboard (planche à pain)

half width Breadboard
La planche à pain (en anglais Breadboard) est une carte vraiment pratique, qui permet de tester les circuits, sans faire de soudure. N’importe quel type de composant (sauf les composants à montage en surface), peut être facilement inséré ou retiré, et les contacts fonctionnent directement. Les trous sont espacés selon les standards de l’électronique de 2.54mm, soit 1 pouce des anciennes unités impériales britanniques, comme l’électronique moderne à principalement été développée aux États-Unis d’Amérique, et c’est le seul pays au monde qui utilise encore ce système complexe. Le Système International (SI), créé pendant la révolution française, est bien plus facile pour tout calcul en physique.


Breadboard de demi-taille avec les liens électriques internes représentés
En marron, les liens électriques placés à l’intérieur de la breadboard.

La carte fait de plastique comporte des liens électriques en son milieu. Ils sont divisés en trois parties ; Une partie centrale, large, et deux parties latérales.
* Les deux parties latérales sont simplement des longues lignes utilisées en principe pour la masse (GND) et l’alimentation (V ou Vcc). La ligne bleue représente la masse et la rouge le courant, et sont tracés afin d’éviter de mélanger les deux et de créer ainsi des courts-circuits. Le fait d’avoir une dorsale VCC+GND sur chaque côté est parfaite pour l’utilisation avec des cartes de type microcontrôleurs qui comportent souvent à la fois des broches d’alimentation en 5 V et en 3,3 V ou bien pour avoir un accès plus facile aux broches des 2 côts de la carte. Les cartes les plus efficaces ne comportent que des sorties 3,3 V.
* Dans la partie du milieu, les liens électriques sont perpendiculaires à ceux ddes côtés, et sont séparés au centre. ils sont principalement utilisés pour placer les composants électroniques. Des symboles, tels que des conteurs de trous, tous les 5 trous, et des lettres aux extrêmités, sont inscrits pour plus facilement monter un circuit à partir d’un patron.

Choix des ports GPIO et de leur broches sur la carte

Voici le brochage (en anglais « pinout ») de la carte, nous avons besoin de choisir les ports utilisés pour le circuit, certaines broches comportent des GPIO (de l’anglais General Purpose Input/Ouput, entrée-sortie à but généraliste), dont ceertaines ne supportent que des signaux numériques (0 ou 1), parfois des signaux analogiques, avec soit des convertisseurs analogique-numérique (ADC), numérique-analogique (DAC) ou les deux à la fois. Certaines borches permettent de fournir du courant aux composants électroniques (les ports 3,3 V en rouge sur l’illustration) Attention à ne pas les connecter directement aux ports GPIO, vous pourriez éndomager définitivement votre carte..


Brochage du NodeMCU ESP32-C3-32S-kit
Brochage du NodeMCU ESP32-C3-32S-kit

Voici la carte du brochage pour une carte NodeMCU ESP32-C3-32S-kit. Merci à J-C François pour ce dessin.

Vous pouvez générallement les trouver dans la documentation de votre carte. Il n’est cependant pas toujours facile de trouver ce genre de schéma, mais ils sont géénralement fournis par le producteur de la carte. La liste des GPIO des cartes ESP32-C3 sont dans la documentation, mais chaque fabriquant de carte peut décider de les lier à différentes broches.

Nous choisissons ici la GPIO 1 (GPIO_NUM_1 dans l’API) pour le bouton, et la broche GPIO 2 (GPIO_NUM_2) pour la LED. Pour cette carte en particulier, il s’agit réspectivement de la seconde et de la troisième broche, sur le côté gauche, en partant du haut.

ADC0_CH1, GPIO1 et GPIO2
Dans le code source, comme nous le verrons plus tard :

#define EXTERN_BUT GPIO_NUM_1 // BUTTON on GPIO1
#define EXTERN_LED GPIO_NUM_2 // LED on GPIO2

Partie LED

L'anode (+) et la cathode (-) sur un symoble diodeNous allons utiliser ici une LED rouge. LED signifie en anglais « light-emitting diode » on le traduit en français par DEL (diode electro-luminescente), il s’agît donc d’un type spécial de diode émettant de la lumière. Une diode ne fonctionne que dans une seule direction, nécessite une tension minimum déterminée pour fonctionner, et ne doit pas être alimenté au deçu d’une certaine tension, afin d’éviter de l’endommager. Si elle est alimentée dans la mauvaise direction (et pas trop fort), rien ne se produit. Cette propriété est très utiles dans un grands nombre de cas, mais ça n’est pas le sujet ici.


Symboles Diode et LED
Symboles diode (à gauche) et LED (avec des flèches)s.


La pate Anode est plus longue que celle de la cathode
La pate de l’anode est plus longue que celle de la cathode, Des LEDs posés sur une surface plane, roulent généralement d’elles même jusqu’à se mettre sur la surface plate.


le tour du bas de la LED est plat du côté de la cathode
Le contour du bas de la LED est plat du côté de la cathode.

Pour la partie LED, nous avons besoin d’une résistance (la majorité des tutoriels que j’ai trouvé indiquaient par erreur 330 Ω). les LEDs fonctionnent autour de différentes plages de tension. Aux alentours de 10 à 50 mA, utiliser 20 à 30 mA est une zone sécurisée en général, mais attention, elles utilisent parfois moins de mA. En général, avec une tension inférieure à 20 mA, la LED ne sera pas très brillante. La tension dépend de la couleur de la LED. Lors des tests, essayer de rester au milieu de ces tensions afin de ne pas brûler votre LED. Le mieux reste de suivre les spécifications du constructeur de votre LED. Il est parfois difficile de trouver ces informations et les valeurs changent d’un fabriquant à un autre. Mais en raison des composants chimiqus utilisés (cela pourrait changer en fonction de la rareté des matériaux), les valeurs limites sont situées aux alentours de :
* IR (infrarouge) 1,2 à 1,6 V
* rouge 1,8 à 2,1 V
* Orange/jaune 1,9 à 2,2 V
* Vert 1,8 à 3,1 V
* Blanc/UV (ultra-violet) 3 à 3,4 V
* Bleu 3 à 3,7 V
* RVB, chaque broche correspondant à la tension propre à sa couleur, se référer à la tension de la couleur dans au dessus (ou encore mieux, aux spécifications du fabriquant).

Vous pouvez tester la tension en avant (« Forward Voltage ») d’une LED à l’aide d’un multimètre numérique. En avant, parce que c’est la direction dans laquelle la LED s’illumine.


Multimètre numérique en position de test de diode
Multimètre numérique en position de test de diode.

Sélectionnez le mode diode du multimètre (comme sur l’image ci-dessus), et touchez l’anode (pate longue, du côté arrondi de la base de la LED) avec le connecteur rouge, et la cathode (pate courte, du côté de la base plate de la LED) avec le connecteur noir.

Nous utilisons une LED rouge, qui nécessite environ 1,8 V. L’ESP32-C3 dispose de sorties 3,3 V, il y a donc une différence de :

3,3 - 1,8 = 1,5 V

Nous avons donc besoin d’une résistance pour éviter une surtension de la LED.

Résistances

D’après la loi d’Ohm, U = RI, où U est la tention (envolts ou V), R est la résistance (en Ohms ou Ω) et I l’intensité (en ampères ou A). Donc :

R = U/I
 1,5/0,02 = 75 Ω


Nous choissons la résistance la plus proche, ayant une résistance égale ou supérieure à ce résultat.

Les résistances sont peintes avec des anneaux de couleur indiquant leur résistance. Il y a en général 4 ou 5 anneaux (ou bandes). Ils doivent être lus de gauche à droite et représentent : résistance (2 bandes), multiplicateur (une bande) et pourcentage de tolérence (1 ou 2 bandes). Voici un bon calculateur de restistance à 5 bandes.

Une bon moyen mnemotéchnique de retenir l'ordre des couleurs est en français "Ne Mangez Rien Ou Je Vous Brule Votre Grosse Barbe". for value rings they start by 0, then 1, etc..., for multiplier by 1, then 10, etc....

Black (noir) 0 0 x1
Brown (marron) 1 1 x10 ±1%
Red (rouge) 2 2 x100 ±2%
Orange (orange) 3 3 x1000=x1K
Yellow (jaune) 4 4 x10K
Green (vert) 5 5 x100K ±0.5%
Blue (bleu) 6 6 x1000K=x1M ±0.25%
Violet (violet) 7 7 x10M ±0.1%
Gray (gris) 8 8 x100M ±0.05%
White (blanc) 9 9 x1000M=1G x1
Gold (or) ±5%
Silver (argent) x0.01 ±10%

Attention à la lumière, lorsque vous regardez les bandes, des bandes peuvent êtres confondues sous une lumière inapropriée, et peuvent avoir des conséquences désastreuses pour votre matériel, en particulier si il s'agît de la couleur du multiplicateur.


Résistance prises au flash à gauche et à lumière ambiante à droite
Photographie de résistances prisent avec un flash à gauche et une lumière ambiante ombrée à droite. Comme on peut l'observer ici sur la résistance bleue à gauche, le second anneau est peint en orange, mais il semble marron avec l'ombre de la lumière naturelle à droite.

Si vous avez des doutes sur leur valeurs, vous pouvez toujours utilisé un multimètre numérique sur leur position résistance, dénotée par le caractère grec Omega (Ω) utilisé pour les Ohms.


Multimètre nuimérique en position de test de résistance
Multimètre nuimérique en position de test de résistance

Certains suggèrent 330 ohms avec 5 V, c'est vraiment trop, mais même pour 3,3 V, il en résulte

1,5/330 = 0,01 A = 1 mA

1 mA est 1/20e de la lumière idéale, cela illuminera tout de même la LED, mais avec une intensité très faible.

J'ai une résistance 47 ohms, mais il en résulterait une valeur un petit peu trop élevée :

1,5 / 47 = 31.91 mA

Dans cette vidéo, la différence est visible sur la lumière ambiante, comme la caméra fait le point lumineux sur la LED. Ici, 330 ohms (alimentant donc à LED avec 1 mA) et 100 ohms (l'alimentant en 15 mA) les résistances sont connectées en parallèle, une meilleur solution (voir plus bas), puis 330 ohms uniquement. On peut voir que la lumière ambiante change seulement parce que le capteur du téléphone n'est pas en mode HDR. Cette différence de lumière démontre que la LED n'est pas assez alimentée.

J'ai développé un outil simple de calculat de résistance à l'aide de TIC80 en langage Lua. Vous pouvez l'utiliser en ligne dans votre navigateur tel quel (version utilisant WASM) dans la page en lien ou bien la télécharger, sur la même page pour l'utiliser sur votre ordinateur ou téléphone, c'est à source ouverte, sous licence GPLv3 :



Screenshot de resisor_calculator

Mise à jour du 28 décembre 2021 : J'ai découvert après avoir écrit ce billet, que la fameuse suite logicielle libre et à source ouverte de conception électronique, KiCad contient un outil appelé PCB Calculator permettant de calculer un grand nombre de choses liées à l'électronique, tels que les résistances et comporte une table de couleur des résistances. La version 6.0.0 d Kicad a été publiée le 25 décembre et contient plusieurs années de travail et d'importantes éméliroations.

Une autre loi d'Ohm dit que Lorsque les résistances sont liées en parallèle appelée « loi des résistances en parallèle », le calcul est :

1/R = 1/R₁ + 1/R₂ + ... + 1/R₉ + ...

Donc

R = 1 / (1/R₁ + 1/R₂ + ... + 1/R₉ + ...)

Et nous avons avec les résistances de 330 Ω et 100 Ω en parallèle :

R = 1 / (1/100 + 1/330) = 1 / 76.744 Ω
I = 1.5/76.744 = 0.01954 A = 20 mA

C'est vraiement très proche de la perfection.

Cette formule fait tout en une seule fois :

I = U * 1 / (1/R₁ + 1/R₂ + ... + 1/R₉ + ...)

Donc, ici :

1.5 / (1/100 + 1/330)

J'ai également crée un simple outil de calcul en ligne de commande pour les résistances en parallèle, disponible ici https://framagit.org/popolon/electronhelp en plus de la calculatrice à résistence en tic80. Je dois toujours implémenter, le calcul du montage en parallèle dans la version Tic-80, et un outil de caulcul de résistance unique dans la version en ligne de commande.

Partie bouton

résistance Pull-up
Pour le bouton, le principle est juste un commutateur, il coupe le courant par défaut et crée un court-circuit lorsque le bouton est pressé. Ce commutateur est, sur le circuit, entre l'une des broches GPIO et une broche de la masse (GND). En raison du bruit sur le signal, une résistance pull-up/pull-down est nécessaire, afin de stabiliser le signal. Une résistance Pull-up/pull-down ajoute juste un courant très faible au court circuit, Une résistance de 10 KΩ connectée à la broche 3,3 V est utilisée à cette fin. Elle est appellée pull-up (tire vers le haut) si la résistance est connectée entre le bouton et la broche GPIO (3.3 V également), ou pull-down (tire vers le bas), si elle est connectée entre le bouton et la masse.

De nos jours, la majorité des cartes à microcontrôleurs, telles que les carte basées sur un ESP32-C3 comporte des résistances pull-up/pull-down internes. Elles peuvent être activée par logiciel.

Les pates de connexion du bouton de commutation peuvent être contre-intuitives à première vue, mais les pates su mêmes côté ne sont pas connectées entre elles, chauqe pate est connectée avec celle du côté opposé. Pour être plus clair, sur le schéma représentant la résistance pull-up, les deux pates à gauche sont toujours connectées ensemble et les deux pates à droites sont connectées ensemble, Le bouton établi la connexion entre les 2 pates de gauche et les 2 pates de droite.

Le logiciel

Nous voulons gérer :
* Une boucle principale qui fait clignoter la LED RVB de la carte.
* Une LED externe rouge qui s'illumine lorsque le clignottement s'arrête.
* Un bouton commutateur, qui peut à n'importe quelle moment, de façon asyncrone, commuté entre ces deux états. Ce bouton ne peut être vérouillé en bas, nous changeons donc d'état à chaque fois qu'il est enfoncé. Le relacher n'a aucun effet.

Pour gérer tout cela de façon asyncrone, nous avons besoin d'interruptions.
* Une interruption chronométrée afin d'attendre entre chaque pas du clignottement de la LED sur la carte.
* Une interruption lorsque le commutateur on/off est pressé, pour changer d'état.
* À ce niveau de développement, nous avons également besoin d'un interupteur par chronomètre asyncrone, utilisé pour ce que l'on appelle anti-rebond (debouncing en anglais). Lorsque le bouton est pressé, il rebondit, physiquement sur le contact et le contact passe plusieurs fois entre l'état on/off. Le même effet se produit au niveau plus bas qu'est le niveau électrique, mais le courant analogique, est géré par les composants électroniques, nous pouvons donc voir à notre niveau le courant avec un état logique numériuqe 1 (allumé/on) ou 0 (éteind/off).

Il y a un exemple inclus avec une file d'attente dans ESP-IDF, mais son but n'étiat pas très clair pour moi. Après la gestion du commutateur, Je l'ai regardé de nouveau, il permet de tester la file d'attente et les interruptions, en connectant simplement 2 broches à 2 autres broches, 2 broches envoient le signal, deux autres le reçoivent, il en suit une sortie sur le terminal série afin de comprendre ce qu'il se passe, c'est aussi très utile. Quoi qu'il en soit, après avoir pas mal recherché comment utiliser le bouton commutateur, j'ai trouvé ce sujet m'a beaucoup aidé pour la partie logicielle mais n'était pas complète, en particulier en ce qui concerne le debouncing. J'ai donc écrit ici une versionqui fonction et la façon dont elle fonctionne. Toutes les fpnctions à propos des GPIO dans ESP-IDF sont décrites ici.

J'ai effectué une copie de cet exemple esp-idf/examples/get-started/blink comme projet de départ. Voir l'article rpécédent à propos d'ESP-IDF à ce sujet, et comment le compiler.

Copiez simplement l'exemple de clignottement, et remplacez blink/main/blink.c par ma version, que vous pouvez télécharger ici.

Vous pouvez, comme première aide, le compiler et le flasher pour commencer, vous n'aurez alors plus qu'à remplacer le fichier blink.c, puis à le compiler et le flasher de nouveau.

Pour initialiser l'environnement pour de l'ESP32-C3 :

. $HOME/esp/esp-idf/export.sh
idf.py set-target esp32c3

Puis, pour le compiler et flasher (Vous pouvez également vous limité à la compilation et au flashage en fonction de vos besoins) :

idf.py build flash

Initialisation

Dans cet exemple, définir state = 1 (on) fait clignoter la LED interne et la LED rouge à l'opposé, lorsqu'il est à 0 (off).
* Choisissez les ports GPIO utilisés, remettez à zéro ces GPIO, réglez celle des LED comme sortie (output), et le bouton comme entrée (input)
* Initialisez les interruptions et chronomètres pour le bouton, nous définissons à POSEDGE (positive edge, bord positif), cela correspond au moment ou le courant monte de 0 à 1, lorsque la liaison de contact devient active dans le bouton.

Dans le projet blink (clignottement) par défaut, la LED interne bleue est définie comme BLINKING_LED, nous allons à la plce commencer de sa GPIO interne, et faire de même pour les autres couleurs de la LED RVB interne (respectivement R=3. V=4. B=5). J'utilsie ici la broche du connecteur GPIO1 pour le bouton et GPIO2 pour la LED externe (ligne 19).

#define RED_GPIO   GPIO_NUM_3
#define GREEN_GPIO GPIO_NUM_4
#define BLUE_GPIO  GPIO_NUM_5

#define EXTERN_BUT GPIO_NUM_1 // BUTTON on GPIO1
#define EXTERN_LED GPIO_NUM_2 // LED on GPIO2

Nous définissons une variable appelée state avec état de départ à 1. 1 = on, 0 = off (ligne 31) :

// set initial state to blinking led on
static int state = 1;

Nous avons besoin d'initialiser toutes les broches que nous allons utiliser (ligne 61):

  gpio_reset_pin(RED_GPIO); // reset internal RGB LED GPIO
  gpio_reset_pin(GREEN_GPIO);
  gpio_reset_pin(BLUE_GPIO);
  gpio_reset_pin(EXTERN_LED); // reset external GPIO
  gpio_reset_pin(EXTERN_BUT);

On crée alors le chronomètre, en envoyant à la fonction appropriée la structure préalablement définie (ligne 67).

  // create the timer
  esp_timer_create(&debounce_timer_args, &debounce_timer);

Nous définissons toutes les caractéristiques de la GPIO du bouton. Peut être défini en dehors du code, et envoyé à la fonction gpio_config (ligne 70).

  if (ISR_MODE == 1) {  // Interrupt mode
    gpio_config_t btn_conf;
    btn_conf.intr_type = GPIO_INTR_POSEDGE;  // Interrupt at Posedge only
    btn_conf.mode = GPIO_MODE_INPUT;           // Use INPUT mode
    btn_conf.pin_bit_mask = EXTERN_BUT_MASK;   // MASK of the button pin
    btn_conf.pull_up_en = GPIO_PULLUP_DISABLE;    //Disable pullup
    btn_conf.pull_down_en = GPIO_PULLDOWN_ENABLE; //Enable pulldown
    gpio_config(&btn_conf); // send config
  } else {               // Naive mode
    gpio_set_direction(EXTERN_BUT, GPIO_MODE_INPUT);
  }
  printf("button configured\n");

On règle à présent l'interruption associée au bouton, à la GPIO EXTERN_BUT au gestionnaire de fonction isr_button_pressed() (ligne 83).

  if (ISR_MODE == 1) {
    gpio_install_isr_service(ESP_INTR_FLAG_DEFAULT); // install GPIO interrupt
    gpio_isr_handler_add(EXTERN_BUT, isr_button_pressed, (void*) EXTERN_BUT); //Add handler of interrupt
    printf("Interrupt configured\n");
  } // end of ISR initialistaion

On règle alors la direction en sortie (OUTPUT) pour la LED et change la LED rouge externe à 1 - state = 0, ou off, on ne définit pas les 3 LEDs internes à off, comme elle vont l'être juste après, au début de la boucle (ligne 89)

  /* Set the LED GPIO as a push/pull output */
  gpio_set_direction(BLUE_GPIO, GPIO_MODE_OUTPUT);
  gpio_set_direction(RED_GPIO, GPIO_MODE_OUTPUT);
  gpio_set_direction(GREEN_GPIO, GPIO_MODE_OUTPUT);
  gpio_set_direction(EXTERN_LED, GPIO_MODE_OUTPUT);
  printf("LED output configured\n");

  gpio_set_level(EXTERN_LED, 1 - state); // 1-1=0 1-0=1

Boucle principale

Dans cette boucle infinie (while(1)), On allume simplement, une couleur de la LED, attendons un peu en utilisant l'interruption, afin d'éviter de consommer de l'énergie pour rien, puis on éteind la LED. Quelques informations à propos de l'état courant sont envoyées à la console série, telles que l'état du système (signifiant si la LED interne (1) ou externe rouge (0) est allumée), à l'arrivée en début de boucle.

Nous choisissons ici de créeer une boucle et d'envoyer la valeur de l'état (state) à la LED, puis d'attendre via un chronomètre (timer), et enfin de changer la valeur de la LED à 0 afin de l'éteindre. Il aurait été mieux d'arrêter la boucle dès que le système est arrêté et d'attendre que l'état du système soit de nouveau à on avant de boucler de nouveau.

La boucle commence par écrire l'état actuel et étteindre la LED RVB (ligne 98):

while(1) {
  printf("starting cycle by turning off the LEDs\n");
  gpio_set_level(RED_GPIO, 0);
  gpio_set_level(GREEN_GPIO, 0);
  gpio_set_level(BLUE_GPIO, 0);

Puis, si nous somme dans le mode naïf (ligne 104), sans interuption, récupérons l'état du GPIO associé au button à l'aide de la fonction gpio_get_level(), et dans tous les cas, l'affiche (ligne 107). Cela permet de voir si l'on a bien connecté le bouton à la bonne broche de GPIO. On attend alors 1 seconde (1000 millisecondes, ligne 108):

  if ( ISR_MODE == 0 ) { // naive mode
    state = gpio_get_level(EXTERN_BUT);
  }
  printf("state=%d\n",state); // display current state on console
  vTaskDelay(1000 / portTICK_PERIOD_MS); // wait with lights off

On allume alors la LED bleue interne, attend 200 millisecondes (0,2 s) puis l'eteind (ligne 110):

  gpio_set_level(BLUE_GPIO, state); // light on blue if state up
  vTaskDelay(200 / portTICK_PERIOD_MS);
  gpio_set_level(BLUE_GPIO, 0);     // light off blue

On fait alors la même chose avec la LED interne verte (ligne 114), puis la rouge (ligne 118), puis la rouge et la verte en même temps, donnant une lumière de couleur jaune (line 122). Nous attendons pour la dernière fois du cycle de la boucle pendant 200 ms (ligne 124) le cycle recommence alors en éteignant toutes les LEDs internes (ligne 100, voir au dessus) :

  gpio_set_level(GREEN_GPIO, state);// light on green
  vTaskDelay(200 / portTICK_PERIOD_MS);
  gpio_set_level(GREEN_GPIO, 0);    // light off green

  gpio_set_level(RED_GPIO, state);  // light on red
  vTaskDelay(200 / portTICK_PERIOD_MS);
  gpio_set_level(RED_GPIO, 0);      // light off red

  gpio_set_level(RED_GPIO, state);  // light on red
  gpio_set_level(GREEN_GPIO, state);// and green => yellow!!
  vTaskDelay(200 / portTICK_PERIOD_MS); // end loop
}

Routine de Service d'intérruption (ou ISR, Interrupt Service Routine)

La fonction gpio_isr_register() permet de mettre en place la fonction d'interruption.

mais également, gpio_install_isr_service() et gpio_isr_handler_add().

Interrupt allocation. Un des aspects intéressants à connaître est que vous pouvez conserver les interruptions en IRAM (Instruction RAM, mémoire vive d'instruction) et utiliser les données en DRAM (Data RAM, mémoire vive de données), permettant ainsi moins de latance lors des interruptions, et les gardées ainsi indépendantes des lecture/écriture en flash. À propos de la mémoire dans les ESP32-C3), IRAM et DRAM peuvent être lu/écrit en parallèle.

Exemple sans interruption.

Supprimer l'effet de rebond (Debouncing)


Cet article illustre le problème et 2 méthodes d'anti-rebond avec des exemples sur FPGA, et c'est vraiment bien plus simple à implémenter en FPGA que sur un processeur à but général ou un microcontrôleur.

l'app principale utilise déjà le chronomètre principal vTaskDelay, nous ne pouvons donc pas l'utiliser comme j'ai d'abord fait, dans le cas contraire, il changerait l'adresse de retour de la fonction, et s'arrêterait dans la boucle principale. On pourrait crée un nouvelle xApp, mais la méthode la plus élégante, consiste à utiliser les chronomètre ESP (ESP Timer), cela résoud tous nos problèmes de manière facile.

Chronomètre ESP

Nous allons suivre la seconde méthode et utiliser un chronomètre haute résolition (High Resolution Timer) pour cela. ESP-IDF inclus un exemple dans examples/system/esp_timer/. Nous n'avons pas besoin de nous préoccuper de la très haute résolution de l'horloge, des ms sont suffisant ici, mais, cela à l'avantage de gérer le chronomètre par interruption et d'éviter facilement les conflits d'appels, comme nous en avons absolument besoin en raison des rebonds.

Nous avons besoin de crée la structure à envoyer à la fonction esp_timer_create(), qui initialize l'interruption, et donc, l'entête de la fonction de rappel doit être prédéfinie (ligne 34):

// button manager with debounce timer definition
static esp_timer_handle_t debounce_timer;
static void test_button(void* arg);
const esp_timer_create_args_t debounce_timer_args = {
  .callback = &test_button,
  /* argument specified here will be passed to timer callback function */
  .arg = NULL,
  .name = "test_button"
};

La fonction chronomètre, est appelée par une interruption à la fin du temps d'exécution de ce chronomètre. Il vérifie alors si le bouton est encore pressé, et change alors l'état (1-0=1, 1-0=1). La LED externe est réglée à l'opposé de l'état courant. Si la LED de la carte clignote, alors la LED externe rouge est éteinte (line 44).

static void test_button(void* arg) {
  if (gpio_get_level(EXTERN_BUT) == 1)
  {
    state = 1 - state;
    gpio_set_level(EXTERN_LED, 1 - state);
  }
}

La fonction de rappel isr_button_pressed() est appélée lorsque le bouton est pressé. (au signal montant, POSEDGE). En raison du rebond, elle peut être appelée plusieurs fois pour une pression humaine analogique unique. Nous avons donc besoin de vérifier en double que nous somme vraiement dans l'état préssé (tension haute) à la fin d'un délai court, et sinon,de sortir. On vérifie alors que le chronomètre n'avait pas été démarré auparavant. Si il ne l'était pas (!) le cas, alors on lance le chronomètre qui démarre la fonction test_button() après 1 milliseconde (1000 microsecondes). Cela permet d'éviter le rebond, mais de toujours permettre des pression répétées rapides (pour la saisie d'un code morse rapide, où les jeux d'actions furieux) (ligne 52):

static void isr_button_pressed(void* args) {
  if (gpio_get_level(EXTERN_BUT) == 0) 
    return; // we only want to manage pushed button
  if (!esp_timer_is_active(debounce_timer)) { // start only if the timer isn't active
    ESP_ERROR_CHECK(esp_timer_start_once(debounce_timer, 1000)); // 1ms = 1Kµs
  }
}

p.s.: J'ai trouvé un article à propos de une autre méthode anti-rebond et également spécifique aux possibilités de RTOS.