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 ;
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.
- 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.
- 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 ;
- 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).
- Chacune des 7 sorties repérées
AàGdu 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 (boutonSET-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 ).
DP (decimal point) est également à relier directement au rail « − » car cette fonctionnalité ne sera pas utilisée. 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 programmationPROG_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 :
- Fonctions et pseudo‑fonctions d'initialisation
- 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é, - 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. - au niveau zéro du programme (typiquement, avant la fonction
setup), déclarer le typeLogicalSignalproposé dans le chap. C4‑VI , ainsi que deux variables globales de ce type nommées respectivementstartStopButtonSignaletsetResetButtonSignal, 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. - Fonctions et pseudo‑fonctions d'affichage
- 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 argumentnumber; -
tensDigit(number)qui retourne le chiffre des dizaines de son argumentnumber. - 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. - 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éfaut20, 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. - Codage de la machine à état du système
- Déclarer un type énuméré nommé
Stateavec, pour le moment, seulement deux constantes énuméréesSTOPPEDetRUNNING(cf. chap. C3‑IV ). - Dans la fonction
loop, coder successivement deux bifurcationsswitchsur la variablestate: - 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 ;
- 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) ?
- Amélioration de la précision du compte à rebours
- Modifier la valeur initiale de la variable
presetTimerValuepour tenir compte du fait quetimerValuesexprime dorénavant une durée en millisecondes. - Modifier le code du cas
RUNNINGde la deuxième bifurcationswitchde la machine à états du système pour gérer en millisecondes la variabletimerValue. Typiquement, on calculera dans une variablemillisIncreasele nombre de millisecondes écoulées depuis la dernière itération de la fonctionloopet on décrémenteratimerValuede cette valeur (et bien entendu, sans aller en dessous de0). - 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.
- Signalisation de la fin du compte à rebours
- Dans la déclaration du type énuméré
State, ajouter la constanteENDpour représenter l'état « fin » du compte à rebours. - Modifier les bifurcations
switchde gestion de la machine à états pour intégrer ce nouvel état, conformément à la figure ci‑contre. - 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éepreviousBlinkMillispour mémoriser la valeur retournée par la fonctionmillisau clignotement précédent. - Tester le bon fonctionnement du programme avec ces modifications.
- Ajustement de la valeur de préréglage du compte à rebours
- Pour détecter l'occurrence d'un appui long sur un bouton‑poussoir, coder une fonction nommé
longPushOccurredretournant une valeur de typeboolet prenant un argument de typeLogicalSignal. -
millisWhenPushedpour mémoriser la valeur retournée par la fonctionmillisà l'instant de l'appui sur le bouton ; -
occurred, de typebool, pour mémoriser si l'appui long vient juste d'être détecté ; en effet, la fonction ne doit retourner la valeur1qu'une seule fois par appui long, même si ce dernier se prolonge. - 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éeaddOneSecondModuloMaxadmettant un argument nomménumber. - Dans la déclaration du type énuméré
State, ajouter la constanteSETUPpour représenter l'état « réglage » du compte à rebours. - À l'aide des éléments codés précédemment, modifier les bifurcations
switchde gestion de la machine à états pour intégrer ce nouvel état, conformément à la figure ci‑contre. - Tester le bon fonctionnement du programme avec ces modifications.
- Incrémentation rapide de la valeur préréglée
- Dans la déclaration du type énuméré
State, ajouter la constanteFAST_INCREASEpour représenter l'état « réglage rapide » du compte à rebours. - Modifier les bifurcations
switchde gestion de la machine à états pour intégrer ce nouvel état, conformément à la figure ci‑contre. - Tester le bon fonctionnement du programme avec ces modifications.
- Version modulaire du programme
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. delay). Ensuite, il doit pouvoir être réinitialisé à sa valeur de préréglage par un appui simple sur le bouton SET‑RESET. 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. state de type State en lui affectant la valeur initiale STOPPED.
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. 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 .
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. 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). 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. LogicalSignal ;