TP 1 — Allumer une LED, lire un bouton ====================================== Objectifs --------- - créer un projet STM32CubeIDE pour la carte, avec la configuration minimale ; - comprendre le schéma d'une LED et le calcul de sa résistance ; - piloter une sortie : clignotement, puis chenillard sur quatre LED ; - lire une entrée, constater le rebond d'un bouton et l'éliminer. Durée indicative : une séance de 3 h. Matériel : voir :doc:`../prise_en_main`. Ce qu'il faut savoir -------------------- Les LED de façade ~~~~~~~~~~~~~~~~~ La carte porte quatre LED bleues en façade, chacune pilotée directement par une broche du microcontrôleur, à travers une résistance de 220 Ω : .. list-table:: :header-rows: 1 :widths: 25 25 25 25 * - LED - Broche - Repère - Résistance * - LED_1 - PD4 - D9 - R28, 220 Ω * - LED_2 - PD3 - D10 - R29, 220 Ω * - LED_3 - PD2 - D11 - R30, 220 Ω * - LED_4 - PC7 - D12 - R31, 220 Ω Elles sont **actives à l'état haut** : la broche à 1 (3,3 V) fait passer le courant dans la LED, qui s'allume ; la broche à 0 l'éteint. .. tikz:: Une LED de façade : la broche, à l'état haut, alimente la LED à travers 220 Ω. \draw (0,0) node[left, align=right]{broche PD4\\[-2pt]\scriptsize 3,3 V à l'état haut} to[short, o-] (0.5,0) to[R, l={R28, $220\,\Omega$}] (3.2,0) to[short] (4.5,0) to[led, l={LED\_1 (D9)}] (4.5,-2.4) node[ground]{}; \draw[-{Stealth}, thick, blue!60!black] (3.9,-0.6) -- (3.9,-1.8) node[midway, left, font=\scriptsize]{$I \approx \dfrac{3{,}3 - 3{,}0}{220} \approx 1{,}4$ mA}; **Le courant.** Une LED bleue a une tension de seuil d'environ 3 V. Il reste 3,3 − 3,0 = 0,3 V aux bornes de la résistance, soit un courant d'environ 0,3 / 220 ≈ 1,4 mA. C'est peu, mais une LED moderne est bien visible à ce courant ; et une broche du STM32H750 ne doit de toute façon pas fournir plus de quelques milliampères. Une broche en sortie ~~~~~~~~~~~~~~~~~~~~ Une broche configurée en **sortie** (*GPIO_Output*, mode *push-pull*) impose sa tension : 3,3 V à l'état haut, 0 V à l'état bas. La bibliothèque HAL fournit trois fonctions : .. code-block:: c HAL_GPIO_WritePin(GPIOD, GPIO_PIN_4, GPIO_PIN_SET); // 1 : allumée HAL_GPIO_WritePin(GPIOD, GPIO_PIN_4, GPIO_PIN_RESET); // 0 : éteinte HAL_GPIO_TogglePin(GPIOD, GPIO_PIN_4); // inverse l'état ``HAL_Delay(ms)`` attend le nombre de millisecondes demandé, en comptant les interruptions d'un minuteur du cœur (*SysTick*), réglé par CubeMX. Les boutons ~~~~~~~~~~~ Les boutons SW1 à SW4 de la carte sont aussi **actifs à l'état haut** : au repos, une résistance de 10 kΩ tire la broche à la masse (0) ; un appui la relie au 3,3 V (1). La résistance est sur la carte : la broche se configure **sans tirage interne** (*No pull-up and no pull-down*). .. list-table:: :header-rows: 1 :widths: 30 30 40 * - Bouton - Broche - Au repos / appuyé * - SW1 - PE10 - 0 / 1 * - SW2 - PA9 - 0 / 1 * - SW3 - PE8 - 0 / 1 * - SW4 - PE7 - 0 / 1 Étape 1 — Créer le projet ------------------------- #. Dans STM32CubeIDE : **File → New → STM32 Project**. #. Dans l'onglet **MCU/MPU Selector**, cherchez ``STM32H750VBT6`` (ou ``STM32H750VBTx``), sélectionnez-le, puis **Next**. #. Nommez le projet ``tp01_gpio``, langage **C**, type **Executable**, puis **Finish**. Acceptez l'ouverture de la perspective CubeMX. .. Capture d'écran à insérer : CubeIDE, onglet MCU/MPU Selector, le STM32H750VBTx sélectionné. Déposez _static/captures/sans_os/tp01-selecteur-mcu.png puis décommentez le bloc ci-dessous (retirez le préfixe « .. » devant chaque ligne) : .. .. image:: ../_static/captures/sans_os/tp01-selecteur-mcu.png .. :align: center .. :width: 90% CubeMX affiche le boîtier du microcontrôleur, broche par broche. Étape 2 — Configurer le minimum ------------------------------- #. **Le débogage.** Dans **System Core → SYS**, réglez **Debug** sur **Serial Wire**. Sans ce réglage, CubeMX peut réaffecter les broches de la sonde, et la carte devient difficile à reprogrammer. #. **La LED.** Cliquez sur la broche **PD4** et choisissez **GPIO_Output**. Puis clic droit sur la broche, **Enter User Label** : ``LED_1``. #. **L'horloge.** Ne changez rien : le microcontrôleur tourne sur son oscillateur interne HSI, à 64 MHz (onglet **Clock Configuration**, pour le constater). #. Enregistrez (**Ctrl-S**) : CubeMX propose de **générer le code** ; acceptez. .. Capture d'écran à insérer : CubeMX, vue du boîtier : PD4 en GPIO_Output avec le label LED_1 ; SYS → Debug sur Serial Wire. Déposez _static/captures/sans_os/tp01-pd4-led1.png puis décommentez le bloc ci-dessous (retirez le préfixe « .. » devant chaque ligne) : .. .. image:: ../_static/captures/sans_os/tp01-pd4-led1.png .. :align: center .. :width: 90% Le code généré initialise l'horloge et la broche PD4. Ouvrez ``Core/Src/main.c`` : la fonction ``main()`` appelle ``HAL_Init()``, ``SystemClock_Config()`` et ``MX_GPIO_Init()``, puis entre dans une boucle ``while (1)``. .. important:: N'écrivez **que** entre les commentaires ``/* USER CODE BEGIN … */`` et ``/* USER CODE END … */``. À chaque nouvelle génération, CubeMX réécrit tout le reste du fichier, et le code placé ailleurs disparaît. Étape 3 — Faire clignoter LED_1 ------------------------------- Dans la boucle ``while (1)``, dans le bloc ``USER CODE BEGIN 3``, faites clignoter LED_1 deux fois par seconde. Le label ``LED_1`` donné dans CubeMX a créé deux constantes dans ``main.h`` : ``LED_1_GPIO_Port`` et ``LED_1_Pin``, à utiliser plutôt que ``GPIOD`` et ``GPIO_PIN_4``. Compilez (**Project → Build Project**), puis programmez la carte et lancez le programme : **Run → Run As → STM32 C/C++ Application**, configuration par défaut. .. note:: Si STM32CubeIDE n'arrive pas à se connecter à la carte, ouvrez **Run → Run Configurations… → Debugger** et choisissez **Connect under reset** : la sonde prend alors la main au moment du redémarrage, avant que le programme en place ne démarre. .. Capture d'écran à insérer : CubeIDE, Run Configurations → Debugger, option « Connect under reset ». Déposez _static/captures/sans_os/tp01-connect-under-reset.png puis décommentez le bloc ci-dessous (retirez le préfixe « .. » devant chaque ligne) : .. .. image:: ../_static/captures/sans_os/tp01-connect-under-reset.png .. :align: center .. :width: 90% .. admonition:: Questions 1. Quelle est la période du clignotement ? Mesurez-la (montre, ou vidéo au ralenti d'un téléphone) et comparez-la au code. 2. Remplacez l'attente par ``HAL_Delay(1)``. Que voit-on ? Pourquoi ? 3. Pourquoi utiliser ``LED_1_Pin`` plutôt que ``GPIO_PIN_4`` ? .. dropdown:: Solution de l'étape 3 :icon: code :color: secondary Dans ``main.c``, entre ``/* USER CODE BEGIN 3 */`` et ``/* USER CODE END 3 */``, c'est-à-dire **dans** la boucle ``while (1)`` : .. code-block:: c HAL_GPIO_TogglePin(LED_1_GPIO_Port, LED_1_Pin); HAL_Delay(250); **Ce qui se passe.** ``HAL_GPIO_TogglePin`` lit l'état actuel de la sortie dans le registre ``ODR`` (*Output Data Register*) du port D, et écrit l'inverse dans le registre ``BSRR`` (*Bit Set/Reset Register*) : un bit « set » pour passer la broche à 1, un bit « reset » pour la passer à 0. ``BSRR`` modifie **une seule** broche sans toucher aux quinze autres du port, en une seule écriture : c'est pour cela que la HAL l'utilise. ``HAL_Delay(250)`` attend que le compteur de millisecondes ``uwTick``, augmenté par l'interruption *SysTick* 1 000 fois par seconde, ait avancé de 250. Pendant ce temps, le processeur tourne en rond. **Deux fois par seconde** : le clignotement complet (allumée puis éteinte) dure deux bascules, soit 2 × 250 = 500 ms. Une erreur fréquente est d'écrire ``HAL_Delay(500)``, qui donne un cycle d'une seconde. .. dropdown:: Réponses aux questions 1 à 3 :icon: light-bulb :color: info **1. La période.** Elle vaut **500 ms** : 250 ms allumée, 250 ms éteinte. Mesurée au chronomètre sur 20 clignotements (10 s), on trouve 10 s à quelques dixièmes près. La HAL ajoute en réalité **une milliseconde** à chaque attente (``HAL_Delay`` attend au moins le délai demandé, et arrondit au tic suivant), soit 251 ms par bascule : 0,4 % d'écart, invisible ici. **2. Avec HAL_Delay(1).** La LED semble **allumée en permanence**, un peu moins brillante. Elle clignote pourtant à 500 Hz (1 ms allumée, 1 ms éteinte), mais l'œil ne distingue pas un clignotement au-delà de 50 à 100 Hz environ : c'est la **persistance rétinienne**. Il perçoit la **moyenne** de la lumière : allumée la moitié du temps, la LED paraît moins lumineuse. C'est exactement le principe de la **MLI** (PWM) du TP 2, qui règle la luminosité en jouant sur cette proportion. **3. LED_1_Pin plutôt que GPIO_PIN_4.** ``LED_1_Pin`` et ``LED_1_GPIO_Port`` sont des ``#define`` écrits par CubeMX dans ``main.h``, à partir du label. Si la LED est un jour déplacée sur une autre broche, il suffit de changer la broche dans CubeMX : le code suit **sans modification**. Et le code se lit : ``LED_1`` dit ce qu'on commande, ``GPIO_PIN_4`` seulement où. Étape 4 — Le chenillard ----------------------- #. Dans CubeMX (double-clic sur le fichier ``tp01_gpio.ioc``), configurez **PD3**, **PD2** et **PC7** en **GPIO_Output**, labels ``LED_2``, ``LED_3`` et ``LED_4``. Régénérez le code. #. Écrivez un chenillard : une seule LED allumée à la fois, de LED_1 à LED_4, puis on recommence. Une LED toutes les 200 ms. Rangez les quatre LED dans un tableau, pour parcourir les LED avec un indice plutôt que d'écrire quatre fois le même code : .. code-block:: c typedef struct { GPIO_TypeDef *port; uint16_t pin; } led_t; static const led_t leds[4] = { { LED_1_GPIO_Port, LED_1_Pin }, { LED_2_GPIO_Port, LED_2_Pin }, { LED_3_GPIO_Port, LED_3_Pin }, { LED_4_GPIO_Port, LED_4_Pin }, }; .. admonition:: Questions 4. Dans quel ordre les LED s'allument-elles sur la façade ? L'ordre physique est-il celui des broches ? 5. Ajoutez au cycle une étape « les quatre allumées », puis une étape « toutes éteintes ». À quel défaut de câblage chacune des deux étapes permet-elle de penser ? .. dropdown:: Solution de l'étape 4 :icon: code :color: secondary Le tableau ``leds`` se place dans ``/* USER CODE BEGIN PV */`` (variables privées), avant ``main``. Une fonction qui n'allume que la LED d'indice ``i`` se place dans ``/* USER CODE BEGIN 0 */`` : .. code-block:: c /* N'allume que la LED d'indice i (0 à 3), éteint les trois autres. */ static void leds_show_one(uint32_t i) { for (uint32_t k = 0u; k < 4u; k++) { HAL_GPIO_WritePin(leds[k].port, leds[k].pin, (k == i) ? GPIO_PIN_SET : GPIO_PIN_RESET); } } Dans la boucle (``USER CODE BEGIN 3``) : .. code-block:: c static uint32_t i = 0u; /* static : garde sa valeur d'un tour à l'autre */ leds_show_one(i); i = (i + 1u) % 4u; /* 0, 1, 2, 3, 0, 1... */ HAL_Delay(200); **Pourquoi un tableau de structures.** Les quatre LED ne sont pas sur le même port (PD4, PD3, PD2 sur le port D, mais PC7 sur le port C) : on ne peut pas les commander par un simple numéro de bit. Chaque entrée du tableau garde le couple (port, broche), et la boucle traite les quatre LED de la même façon. **Pourquoi static.** Une variable locale ordinaire est recréée à chaque passage dans le bloc, et repartirait de 0 à chaque tour : le chenillard resterait sur LED_1. ``static`` la place en mémoire globale tout en la gardant visible seulement dans ce bloc. **Le modulo.** ``(i + 1) % 4`` donne le reste de la division par 4 : après 3, on revient à 0, sans ``if``. .. dropdown:: Réponses aux questions 4 et 5 :icon: light-bulb :color: info **4. L'ordre.** Le programme allume PD4, PD3, PD2 puis PC7, dans l'ordre du tableau. Sur la façade, regardez si les LED s'allument de gauche à droite : l'ordre des **repères** D9 à D12 sur le circuit, l'ordre **physique** et l'ordre des **broches** sont trois choses différentes. Le tableau sert aussi à cela : pour changer l'ordre d'affichage, il suffit de permuter ses lignes, sans toucher au reste du programme. **5. Les étapes de test.** - « **Toutes allumées** » révèle une LED qui ne s'allume **jamais** : LED montée à l'envers, soudure absente, résistance coupée, ou mauvaise broche dans CubeMX. - « **Toutes éteintes** » révèle une LED qui ne s'éteint **jamais** : broche en court-circuit avec le 3,3 V, ou erreur de label (deux labels sur la même LED, par exemple). Dans un chenillard seul, une LED défaillante passe facilement inaperçue : ces deux étapes forment un **test de câblage** élémentaire, qu'on retrouve au démarrage de beaucoup d'appareils. Étape 5 — Lire un bouton ------------------------ #. Dans CubeMX, configurez **PE10** en **GPIO_Input**, label ``SW1``, **No pull-up and no pull-down**. Régénérez le code. #. Faites suivre le bouton par LED_1 : allumée tant que SW1 est enfoncé, éteinte sinon. La lecture se fait avec : .. code-block:: c GPIO_PinState etat = HAL_GPIO_ReadPin(SW1_GPIO_Port, SW1_Pin); .. admonition:: Question 6. Pourquoi la broche est-elle configurée sans tirage interne ? Que lirait-on, bouton relâché, si la résistance de 10 kΩ n'était pas sur la carte ? .. dropdown:: Solution de l'étape 5 :icon: code :color: secondary .. code-block:: c HAL_GPIO_WritePin(LED_1_GPIO_Port, LED_1_Pin, HAL_GPIO_ReadPin(SW1_GPIO_Port, SW1_Pin)); ``HAL_GPIO_ReadPin`` lit le bit de la broche dans le registre ``IDR`` (*Input Data Register*) du port E, et renvoie ``GPIO_PIN_SET`` (1) ou ``GPIO_PIN_RESET`` (0). Le bouton et la LED sont **tous deux actifs à l'état haut** : la valeur lue peut être recopiée telle quelle sur la LED. Si le bouton était actif à l'état bas, il faudrait inverser la valeur. La boucle tourne sans attente : la broche est lue plusieurs millions de fois par seconde, et la LED suit le bouton sans délai perceptible. .. dropdown:: Réponse à la question 6 :icon: light-bulb :color: info **6. Sans tirage interne.** La carte porte déjà, pour chaque bouton, une résistance de **10 kΩ** entre la broche et la masse (*pull-down*). Activer en plus le tirage interne mettrait deux résistances en jeu : - un *pull-down* interne (environ 40 kΩ) serait en parallèle : sans gravité, mais inutile ; - un *pull-up* interne serait **contraire** : bouton relâché, la broche serait au milieu d'un pont diviseur 40 kΩ / 10 kΩ, vers 0,66 V — lu 0, mais avec une marge réduite et un courant permanent inutile. **Sans la résistance de 10 kΩ**, bouton relâché, la broche ne serait reliée à **rien** : une entrée CMOS **flottante**. Son impédance est énorme (de l'ordre du gigaohm) ; la moindre charge électrostatique, le doigt qui approche, une piste voisine qui commute suffisent à la faire basculer. On lirait 0 ou 1 **au hasard**. Toute entrée doit avoir un état défini au repos : c'est le rôle des résistances de tirage. Étape 6 — Le rebond ------------------- On veut maintenant qu'**un appui** sur SW1 fasse **avancer le chenillard d'une LED**, au lieu de le faire tourner tout seul. #. Détectez le **front montant** : le moment où la lecture passe de 0 à 1. Gardez pour cela la lecture précédente dans une variable. #. Appuyez dix fois sur SW1, lentement, et comptez de combien de LED le chenillard a avancé. Un contact mécanique ne se ferme pas proprement : pendant quelques millisecondes, il **rebondit**, et la broche passe plusieurs fois de 0 à 1 avant de se stabiliser. Le programme, qui lit la broche des millions de fois par seconde, voit alors plusieurs appuis là où il n'y en a eu qu'un. #. Éliminez le rebond **par échantillonnage** : lisez le bouton toutes les **20 ms** seulement, et ne considérez un changement d'état que lorsque **deux lectures successives concordent**. .. admonition:: Questions 7. Sans anti-rebond, de combien de LED le chenillard avançait-il pour dix appuis ? Ce nombre est-il le même d'un essai à l'autre ? 8. Pourquoi 20 ms ? Que se passe-t-il avec 1 ms ? Avec 500 ms ? 9. Avec l'anti-rebond, le programme ne fait plus rien d'autre pendant l'attente de 20 ms s'il utilise ``HAL_Delay``. Comment faire avancer un clignotement **et** surveiller le bouton dans la même boucle ? Indice : ``HAL_GetTick()`` renvoie le nombre de millisecondes écoulées depuis le démarrage. .. dropdown:: Solution de l'étape 6 :icon: code :color: secondary **Première version, sans anti-rebond** : on garde la lecture précédente, et on détecte le passage de 0 à 1. .. code-block:: c static GPIO_PinState previous = GPIO_PIN_RESET; static uint32_t i = 0u; GPIO_PinState now = HAL_GPIO_ReadPin(SW1_GPIO_Port, SW1_Pin); if ((previous == GPIO_PIN_RESET) && (now == GPIO_PIN_SET)) { i = (i + 1u) % 4u; /* front montant : un "appui" */ } previous = now; leds_show_one(i); **Version avec anti-rebond**, dans une fonction (``USER CODE BEGIN 0``) qui renvoie 1 une seule fois par appui : .. code-block:: c #define DEBOUNCE_MS 20u static uint32_t sw1_pressed(void) { static uint32_t last_sample_ms = 0u; static GPIO_PinState previous_read = GPIO_PIN_RESET; static GPIO_PinState stable = GPIO_PIN_RESET; uint32_t now = HAL_GetTick(); uint32_t edge = 0u; if ((now - last_sample_ms) >= DEBOUNCE_MS) /* une lecture / 20 ms */ { GPIO_PinState read = HAL_GPIO_ReadPin(SW1_GPIO_Port, SW1_Pin); last_sample_ms = now; /* deux lectures successives identiques : l'état est stable */ if ((read == previous_read) && (read != stable)) { stable = read; edge = (stable == GPIO_PIN_SET) ? 1u : 0u; } previous_read = read; } return edge; } et dans la boucle : .. code-block:: c static uint32_t i = 0u; if (sw1_pressed()) { i = (i + 1u) % 4u; } leds_show_one(i); **Comment ça marche.** La fonction distingue trois choses : - ``read`` : ce que la broche vaut **maintenant** — peut-être en plein rebond ; - ``previous_read`` : ce qu'elle valait 20 ms plus tôt ; - ``stable`` : l'état **reconnu** du bouton, qui ne change que si deux lectures espacées de 20 ms concordent. Un rebond dure typiquement de **1 à 10 ms**. Deux lectures identiques à 20 ms d'intervalle ne peuvent donc pas être toutes deux tombées dans le même rebond : l'état est vraiment établi. On renvoie 1 seulement quand ``stable`` passe à 1 : c'est le **front montant de l'état stable**, un appui et un seul. **Pourquoi sans HAL_Delay.** La fonction compare ``HAL_GetTick()`` à l'instant de sa dernière lecture, et rend la main aussitôt si 20 ms ne se sont pas écoulées. La boucle principale n'est jamais bloquée : c'est la réponse à la question 9. **Le calcul now − last_sample_ms.** Il reste juste même quand le compteur de millisecondes, un entier de 32 bits, revient à zéro (au bout de 49,7 jours) : la soustraction en entiers non signés se fait modulo 2³², et donne toujours l'écart réel. Comparer ``now >= last_sample_ms + DEBOUNCE_MS`` serait faux à ce moment-là. .. dropdown:: Réponses aux questions 7 à 9 :icon: light-bulb :color: info **7. Sans anti-rebond.** Pour dix appuis, le chenillard avance de **plus de dix LED**, et pas du même nombre d'un essai à l'autre. Chaque appui produit plusieurs fronts montants : à la fermeture, les lames du contact rebondissent l'une sur l'autre pendant quelques millisecondes, et la tension oscille entre 0 et 3,3 V. La boucle, qui lit la broche toutes les quelques dizaines de nanosecondes, voit chaque oscillation. Le nombre de rebonds dépend de la force et de la vitesse de l'appui : il n'est **pas reproductible**. Un oscilloscope sur la broche le montre très bien. **8. Le choix de 20 ms.** Il faut une période **plus longue que le rebond** (quelques ms) et **plus courte qu'un appui** (un appui bref dure de 50 à 100 ms) : - à **1 ms**, deux lectures successives peuvent tomber dans le même rebond : les faux appuis reviennent ; - à **500 ms**, il faut tenir le bouton une seconde (deux lectures concordantes) pour qu'il soit reconnu, et deux appuis rapides n'en font qu'un : le bouton devient **paresseux**. Entre 10 et 30 ms, le filtrage est sûr et le délai imperceptible (au plus 40 ms entre l'appui et sa prise en compte). **9. Faire deux choses à la fois.** On n'attend plus : on **mesure le temps écoulé**. Chaque activité retient l'instant de sa dernière action, et la boucle vérifie à chaque tour si son délai est écoulé : .. code-block:: c static uint32_t t_blink = 0u; if (HAL_GetTick() - t_blink >= 250u) { t_blink = HAL_GetTick(); HAL_GPIO_TogglePin(LED_4_GPIO_Port, LED_4_Pin); } if (sw1_pressed()) { i = (i + 1u) % 4u; } C'est la méthode de la **boucle non bloquante**. Elle marche, mais chaque nouvelle activité ajoute une variable de temps et un test : c'est pour gérer proprement plusieurs activités qu'existent les systèmes temps réel, comme FreeRTOS dans la partie 2. .. dropdown:: Tout le code de la correction :icon: download :color: secondary :download:`main_user_code.c <../../corrections/sans_os/tp01/main_user_code.c>` — compilé sans avertissement contre la HAL du STM32H7. Ce n'est **pas** un ``main.c`` complet : ce sont les extraits à placer dans les blocs ``USER CODE`` du ``main.c`` généré par CubeMX, chacun sous le commentaire qui indique son bloc. La boucle ``while (1)`` contient une variante par étape, encadrée par ``#if 0`` / ``#if 1`` : n'en activer **qu'une à la fois**. La configuration CubeMX attendue est rappelée en tête du fichier, et les réponses aux questions à la fin. Exercices supplémentaires ------------------------- Ces exercices n'ont pas de correction. - **Compteur binaire** : chaque appui sur SW1 ajoute 1 à un compteur de 0 à 15, affiché en binaire sur les quatre LED ; SW2 le remet à zéro. - **Vitesse réglable** : SW1 accélère le chenillard, SW2 le ralentit, entre 50 ms et 1 s par LED. - **Sens de rotation** : SW3 inverse le sens du chenillard. - **Jeu de réflexe** : une LED s'allume au hasard ; il faut appuyer sur le bouton correspondant (SW1 à SW4) le plus vite possible. Les LED affichent ensuite le temps de réaction, en dixièmes de seconde, en binaire. - **Appui long** : distinguer un appui court (moins de 500 ms) d'un appui long sur SW1, et leur donner deux actions différentes. Après le TP ----------- Votre programme a remplacé le chargeur d'amorçage de RatBot dans la flash interne. Pour rendre la carte au robot, voir :ref:`etat-ratbot`.