Objectifs pédagogiques

Les principaux objectifs de ce sujet de travaux pratiques sont de consolider les méthodes de programmation initiées précédemment (TP C4‑1 ) :

  • créer pas à pas un programme décomposé en fonctions ;
  • employer des variables locales statiques ;
  • définir des pseudo‑constantes à la place des constantes.

Les exercices sont aussi l'occasion de découvrir :

  • l'emploi pseudo‑fonctions à la place des fonctions « courtes » (c'est‑à‑dire, qui sont codables avec peu d'instructions) ;
  • la décomposition d'un programme sur plusieurs fichiers ;
  • cette possibilité n'existe pas dans l'environnement de simulation Tinkercad, mais même sans disposer du matériel de la partie opérative, il est possible de tester la bonne compilation du code avec le logiciel Arduino IDE.

Pour traiter ce sujet de TP, il est recommandé d'avoir étudié chapitres C4‑I à C4‑VI du cours. Des renvois aux principaux éléments de connaissance requis sont indiqués au fur et à mesure des questions.

Mise en situation

Spécifications matérielles générales

Tous les exercices sont à traiter dans l'environnement de simulation en ligne Tinkercad , en implémentant préalablement le montage électronique en figure ci‑dessous.

Ce montage comprend les composants suivants :

  • Une carte Arduino Uno R3 héberge le programme de commande du système et assure son alimentation.
  • Une platine d'essai (breadboard) de grande taille est nécessaire. Ses rails « + » et «  » sont respectivement reliés aux broches 5V et GND de la carte Arduino.
  • Deux afficheurs 7 segments W permettent d'afficher un nombre entier à deux chiffres (unités et dizaines), c'est‑à‑dire compris entre 0 et 99. Chaque afficheur est à cathode commune (caractéristique à choisir son menu contextuel de chaque afficheur sur Tinkercad) ; cette cathode est à relier directement au rail «  » de la platine.
  • De plus, la broche du séparateur décimal DP (decimal point) est également à relier directement au rail «  » car cette fonctionnalité ne sera pas utilisée.
  • Deux circuits intégrés décodeurs BCD vers 7 segments à technologie CMOS, référence CD4511 , respectivement associés aux deux afficheurs 7 segments, permettent de diminuer le nombre requis de broches de la carte Arduino pour piloter le système.
  • Les 4 entrées BCD W de ces circuits sont respectivement reliées (fils gris) :
    • aux broches 3 à 6 du port numérique de la carte pour l'afficheur du chiffre des unités ;
    • aux broches  8 à 11 du port numérique de la carte pour l'afficheur du chiffre des dizaines ;
    ces 8 broches étant toutes à configurer comme sorties de la carte.
  • Par ailleurs, pour chaque circuit décodeur CD4511, on prend les dispositions suivantes :
    • L'entrée d'effacement (blank) fonctionne en logique négative, c'est‑à‑dire qu'au niveau logique bas, l'effacement opère (aucun segment ne s'allume).
    • Cette entrée est à relier respectivement à la broche 7 de la carte Arduino pour l'afficheur du chiffre des unités ou à la broche 12 pour l'afficheur du chiffre des dizaines .
      Configurées en sorties, ces deux broches permettront d'effectuer le clignotement des afficheurs.
    • Chacune des 7 sorties repérées A à G du circuit CD4511 doit être respectivement reliée (cf. les fils jaunes sur le schéma) à la broche du segment de même repère de l'afficheur via un résistor de limitation de courant d'une valeur ohmique de 220 Ω ;
    • L'entrée « lampe‑témoin » LT (lamp test) doit être directement reliée au rail « + » de la platine (car cette fonctionnalité ne sera pas utilisée ici).
    • L'entrée « verrou actif » LE (latch enable) est directement reliée au rail «  » de la platine (car cette fonctionnalité ne sera pas non plus utilisée).
    • Enfin, l'alimentation en tension des circuits intégrés sera assurée par liaisons directes aux rails « + » et «  » de la platine.
  • Deux bouton‑poussoir respectivement désignés START‑STOP et SET-RESET constituent les seuls éléments de l'interface homme‑machine. Ils sont l'un et l'autre câblés en logique positive :
    • le pôle gauche est alimenté par le rail « + » de la platine ;
    • le pôle droit est relié à la broche 2 (bouton START-STOP) ou à la broche 13 (bouton SET-RESET) du port numérique de la carte Arduino et au rail « − » de la platine via une résistance de pull‑down de 2 kΩ ;
    • ces pôles sont également reliés l'un à l'autre par un condensateur anti‑rebond de 10 nF recommandé en pratique, mais facultatif en simulation, voire néfaste – donc, à mettre hors circuit si les appuis sont mal détectés sur Tinkercad – cf. chap. C2‑VIII ).

Spécifications logicielles générales

Le but final du sujet de TP est d'implémenter le programme de commande d'un afficheur de compte à rebours de secondes (en anglais, countdown timer W). L'interface homme‑machine minimale est prévue pour donner à l'utilisateur les possibilités listées ci‑dessous :

  • à tout moment, arrêter/reprendre le décompte par un simple appui sur le bouton START‑STOP ;
  • lorsque le décompte arrive à la valeur 0, signaler la fin durant 2 s par un clignotement rapide (période 0,2 s, rapport cyclique 50 %) des afficheurs ;
  • lorsque le système est à l'arrêt, réinitialiser la valeur affichée à une valeur préréglée (par défaut, 20 s) par un simple appui sur le bouton SET‑RESET ;
  • lorsque le système est à l'arrêt, basculer en mode réglage, ou vice‑versa revenir en mode normal, par un appui long (au moins 1 s) sur le bouton SET‑RESET.

De plus, il est attendu que le mode réglage opère de la façon suivante :

  • afficher par défaut la valeur préréglée clignotante (demi‑période 1 s, rapport cyclique 50 %) ;
  • en cas d'appui simple sur le bouton START‑STOP, effectuer une incrémentation unitaire de cette valeur ;
  • en cas d'appui long maintenu sur le bouton START‑STOP, repasser en affichage permanent et effectuer une incrémentation rapide de la valeur préréglée (+1 par 0,1 s) ;
  • et dans les deux cas, effectuer un rebouclage à 0 à chaque dépassement de la valeur maximale affichage sur deux digits (99).

Travail demandé

Il est recommandé de traiter les exercices dans l'ordre.

Préalablement, dans un nouveau circuit sous Tinkercad :

  • effectuer le câblage de la partie matérielle conformément à la figure ci‑dessus ;
  • vérifier le câblage par branchements directs temporaires, typiquement en reliant les sorties du microcontrôleur au potentiel de référence 5V de la carte.

Pour coder les programmes, il est vivement recommandé de ne pas travailler directement dans Tinkercad, mais d'utiliser un éditeur de code externe comme VS Code, à paramétrer pour obtenir une coloration syntaxique adaptée au langage C++ (cf. chap. C2‑X ).

On s'efforcera de respecter les règles de bonnes pratiques de codage, notamment pour le nommage des données, l'indentation et l'aération du code (cf. chap. C2‑X ).

  • Au fur et à mesure, enregistrer chaque fichier source en le nommant conformément à la numérotation des exercices, c'est‑à‑dire par exemple :
    Ct42_exNvX.ino
    où N est le numéro de l'exercice et X la version du programme.
  • Tous ces fichiers doivent être regroupés dans un répertoire d'exercices nommé par exemple TP_C4-2, lui‑même placé dans le répertoire principal de programmation PROG_C, lui‑même placé dans répertoire personnel d'étudiant.

Remarque. Comme on n'expérimente pas ici avec une véritable carte Arduino, il n'est pas indispensable de créer un répertoire de projet distinct pour chaque programme.

Enfin, pour tester le bon fonctionnement de chaque programme, procéder par copier‑coller dans la fenêtre d'édition de Tinkercad. En cas de modifications ponctuelles de mise au point, ne pas oublier d'effectuer un copier‑coller inverse dans l'éditeur de code et d'enregistrer les modifications.

Répondre aux questions supplémentaires sur feuille ou cahier.

Consignes de codage

Au début de chaque programme, selon les besoins, définir des pseudo‑constantes (cf. chap. C4‑III ) pour nommer :

  • les numéros de broche en fonction de leur utilisation (procéder de la même manière que pour traiter les sujets de TP C2‑2  et C3‑I ) ;
  • les valeurs des paramètres de durée indiquées supra dans les spécifications logicielles générales .
  1. Fonctions et pseudo‑fonctions d'initialisation
    1. Pour vérifier le bon fonctionnement des sorties de la carte Arduino, coder préalablement trois fonctions :
      • initPins, sans valeur de retour et sans argument, qui configure toutes les broches de la carte conformément aux spécifications matérielles ;
      • steadyDisplay, sans valeur de retour et sans argument, qui rend l'affichage fixe (c'est‑à‑dire non clignotant) sur les afficheurs 7 segments ;
      • displayDigit, sans valeur de retour et prenant pour arguments deux entiers codés sur 8 bits (un octet), respectivement :
        • digit, le chiffre à afficher (codé en BCD sur le quartet de poids faible de l'octet),
        • bcdInputFirstPin, le premier numéro de broche d'entrée du circuit décodeur via lequel le chiffre sera affiché,
        et qui affiche le chiffre sur l'afficheur raccordé au circuit décodeur ciblé.
      Dans la fonction setup, appeler ces trois fonctions pour afficher successivement tous les chiffres de 9 à 0 sur l'afficheur des unités puis sur celui des dizaines, avec un délai d'une demi-seconde entre deux valeurs et une seconde de délai entre chaque afficheur.
    2. Pour vérifier le bon fonctionnement des entrées de la carte Arduino, coder préalablement quatre pseudo‑fonctions (cf. chap. C4‑III ) équivalentes aux fonctions présentées au chapitre C4‑VI  dans le module de bibliothèque digitalSignals :
      • updateSignal, pour mettre à jour l'octet de mémorisation des niveaux logiques du signal de tension sur une entrée numérique de la carte ;
      • risingEdge, pour détecter un front montant codé sur l'octet de mémorisation des niveaux logiques du signal de tension sur une entrée numérique de la carte ;
      • isLow, pour détecter que le niveau logique de tension sur une entrée numérique de la carte est bas ;
      • isHigh, pour détecter que le niveau logique de tension sur une entrée numérique de la carte est haut.
      Remarque : en cas de difficultés rencontrées dans le codage des pseudo‑fonctions, on pourra utiliser directement le code des fonctions proposé dans le cours.
      Pour tester ces pseudo‑fonctions, en reprenant le programme de la question a) :
      • au niveau zéro du programme (typiquement, avant la fonction setup), déclarer le type LogicalSignal proposé dans le chap. C4‑VI , ainsi que deux variables globales de ce type nommées respectivement startStopButtonSignal et setResetButtonSignal, initialisées pour mémoriser respectivement les signaux logiques sur les deux boutons du systèmes ;
      • dans la fonction loop, appeler les pseudo‑fonctions codées pour qu'un appui sur le bouton START‑STOP inverse l'état allumé/éteint de l'afficheur des unités, et qu'un appui sur bouton SET‑RESET fasse idem pour l'afficheur des dizaines.
  2. Fonctions et pseudo‑fonctions d'affichage
    1. Pour permettre l'affichage d'un nombre à deux chiffres, à l'aide des opérateurs de division euclidienne et de reste (cf. chap. C3‑II ), coder préalablement deux pseudo‑fonctions :
      • unitDigit(number) qui retourne le chiffre des unités de son argument number ;
      • tensDigit(number) qui retourne le chiffre des dizaines de son argument number.
    2. En utilisant ces deux pseudo‑fonctions, coder une fonction displayNumber, sans valeur de retour et prenant pour argument un nombre entier nommé number, quelconque mais a priori positif. Elle doit afficher le chiffre des unités et celui des dizaines de ce nombre respectivement sur les deux afficheurs du système.
    3. Pour tester ces éléments de programme, coder un compte à rebours rudimentaire en déclarant préalablement deux variables globales de type entier :
      • presetTimerValue, initialisée à la valeur par défaut 20, qui mémorise la valeur de préréglage du compte à rebours ;
      • timerValue, initialisée à presetTimerValue, qui mémorise la valeur courante du compte à rebours.
      Le compte à rebours est déclenché par appui simple sur le bouton START‑STOP et se déroule jusqu'à atteindre la valeur 0 sans interruption possible (on peut donc provisoirement utiliser la fonction Arduino delay). Ensuite, il doit pouvoir être réinitialisé à sa valeur de préréglage par un appui simple sur le bouton SET‑RESET.
  3. Codage de la machine à état du système
  4. Le programme codé à la question précédente présente l'inconvénient d'être non réactif puisqu'il utilise la fonction delay (cf. chap. C2‑IX ). Afin que le compte à rebours puisse être interrompu ou redémarré par un appui sur le bouton START‑STOP, on se propose de le coder sous la forme d'une machine à états W et d'utiliser la fonction millis pour gérer les durées.
    1. Déclarer un type énuméré nommé State avec, pour le moment, seulement deux constantes énumérées STOPPED et RUNNING (cf. chap. C3‑IV ).
    2. Déclarer également une variable globale nommée state de type State en lui affectant la valeur initiale STOPPED.
    3. Dans la fonction loop, coder successivement deux bifurcations switch sur la variable state :
      • la première pour contrôler les changements d'état du système, c'est‑à‑dire les conditions de passage d'un état à un autre ;
      • la deuxième pour effectuer les actions associées respectivement à chacun des états ;
      conformément à la figure ci‑contre qui reprend les spécifications du système données supra.
      Dans l'état RUNNING, pour coder la décrémentation à chaque seconde de la variable timerValue, on pourra typiquement employer une variable statique nommée previousMillis, initialisée à la valeur retournée par la fonction millis lors du changement d'état de STOPPED à RUNNING.
    4. Tester le bon fonctionnement du programme en faisant des interruptions du compte à rebours par appuis répétés sur le bouton START‑STOP. En particulier, essayer d'interrompre le compte à rebours juste après ou juste avant un changement de seconde. Que constate‑t‑on alors à la reprise du compte à rebours (après un nouvel appui) ?
  5. Amélioration de la précision du compte à rebours
  6. Le programme codé à la question précédente induit une certaine imprécision car la variable de compte à rebours timerValue est décrémentée à la seconde près. Or un appui sur le bouton START‑STOP peut intervenir à tout moment au cours d'une seconde, et la fraction de seconde déjà écoulée avant l'arrêt n'est pas comptabilisée (cf. la question 3.c supra). On se propose donc corriger le défaut .
    1. Modifier la valeur initiale de la variable presetTimerValue pour tenir compte du fait que timerValues exprime dorénavant une durée en millisecondes.
    2. Modifier le code du cas RUNNING de la deuxième bifurcation switch de la machine à états du système pour gérer en millisecondes la variable timerValue. Typiquement, on calculera dans une variable millisIncrease le nombre de millisecondes écoulées depuis la dernière itération de la fonction loop et on décrémentera timerValue de cette valeur (et bien entendu, sans aller en dessous de 0).
    3. Recommencer les mêmes tests qu'à la question 3.c d'arrêt/reprise du compte à rebours. Vérifier que la fraction de seconde déjà écoulée avant arrêt est maintenant bien comptabilisée.
  7. Signalisation de la fin du compte à rebours
  8. Le programme codé à la question précédente manque d'ergonomie dans la mesure où la fin du compte à rebours ne fait l'objet d'aucun signalement. On se propose donc de combler cette lacune en suivant les spécifications données supra (clignotement rapide durant 2 s ).
    1. Dans la déclaration du type énuméré State, ajouter la constante END pour représenter l'état « fin » du compte à rebours.
    2. Modifier les bifurcations switch de gestion de la machine à états pour intégrer ce nouvel état, conformément à la figure ci‑contre.
    3. Pour comptabiliser le temps écoulé dans l'état END et ainsi exprimer la condition de sortie de cet état, on pourra utiliser la même variable globale previousMillis introduite à la question 3.b.
      NB : la condition de passage de l'état RUNNING à l'état STOPPED n'est plus la même que dans les versions précédentes du programme puisque la fin du décompte doit maintenant conduire à l'état END (et non plus à l'état STOPPED).
    4. Pour mettre en œuvre le clignotement simultané des deux afficheurs, coder une fonction nommée blinkDisplayAtHalfPeriod, sans valeur de retour et prenant un argument unique la demi‑période de clignotement exprimée en millisecondes. Pour être réactive, cette fonction doit utiliser une variable locale statique spécifique, typiquement nommée previousBlinkMillis pour mémoriser la valeur retournée par la fonction millis au clignotement précédent.
    5. Il suffit alors d'appeler cette fonction dans le cas END de la deuxième bifurcation switch de la machine à état du système. Par ailleurs, il ne faut pas oublier d'imposer l'affichage fixe dans les autres états du système par appel de la fonction steadyDisplay.
    6. Tester le bon fonctionnement du programme avec ces modifications.
  9. Ajustement de la valeur de préréglage du compte à rebours
  10. Le programme codé à la question précédente manque encore d'ergonomie : il faut pouvoir régler la valeur de réinitialisation du compte à rebours. On se propose donc de combler cette lacune en suivant les spécifications données supra .
    1. Pour détecter l'occurrence d'un appui long sur un bouton‑poussoir, coder une fonction nommé longPushOccurred retournant une valeur de type bool et prenant un argument de type LogicalSignal.
    2. Pour cela, on pourra notamment déclarer dans la fonction deux variables locales statiques :
      • millisWhenPushed pour mémoriser la valeur retournée par la fonction millis à l'instant de l'appui sur le bouton ;
      • occurred, de type bool, pour mémoriser si l'appui long vient juste d'être détecté ; en effet, la fonction ne doit retourner la valeur 1 qu'une seule fois par appui long, même si ce dernier se prolonge.
      Remarque. En simulation sous Tinkercad (mais pas avec un programme compilé sous Arduino IDE pour une vraie carte), cette fonction doit être préalablement déclarée par un prototype après la déclaration du type LogicalSignal ;
    3. Pour incrémenter de façon cyclique la valeur de la variable presetTimerValue , c'est‑à‑dire reboucler à 0 après avoir atteint la valeur 99, coder une pseudo‑fonction nommée addOneSecondModuloMax admettant un argument nommé number.
    4. Dans la déclaration du type énuméré State, ajouter la constante SETUP pour représenter l'état « réglage » du compte à rebours.
    5. À l'aide des éléments codés précédemment, modifier les bifurcations switch de gestion de la machine à états pour intégrer ce nouvel état, conformément à la figure ci‑contre.
    6. Tester le bon fonctionnement du programme avec ces modifications.
  11. Incrémentation rapide de la valeur préréglée
  12. Le programme codé à la question précédente manque encore d'ergonomie : en effet, régler la valeur préréglée de réinitialisation du compte à rebours par incrémentation unitaire est fastidieux si on veut changer la valeur de plusieurs dizaines d'unités. On se propose donc de combler cette lacune en suivant les spécifications données supra .
    1. Dans la déclaration du type énuméré State, ajouter la constante FAST_INCREASE pour représenter l'état « réglage rapide » du compte à rebours.
    2. Modifier les bifurcations switch de gestion de la machine à états pour intégrer ce nouvel état, conformément à la figure ci‑contre.
    3. Tester le bon fonctionnement du programme avec ces modifications.
  13. Version modulaire du programme
  14. En gardant les mêmes éléments de code, répartir le programme de la question 7 sur plusieurs fichiers. Vérifier que cette version modulaire est compilable avec le logiciel Arduino IDE. Quels modules pourraient être constitués en bibliothèque ?