Outils pour utilisateurs

Outils du site


cesitp1

TP 1 Informatique Embarquée CESI

Informations avant de commencer!

bvdp.inetdoc.net_files_iut_tp_pic_warning.jpeg Pour faire le TP, vous devrez bien lire le sujet, ne pas sauter de parties, ne pas inventer de prototypes de fonctions mais bien respecter ce qui est demandé, car les fonctions que vous écrirez devront pouvoir être substituées à d'autres fonctions ayant les mêmes prototypes. Les marges de manœuvre dont vous disposez sont au niveau de l'implémentation de ces fonctions, mais les prototypes font partie du cahier des charges et ne sont pas négociables! Le TP se déroulera sur plusieurs séances, il est de votre responsabilité de conserver les fichiers que vous aurez produits; pour cela, sauver l'URL de votre projet sur Wokwi et également LE CONTENU DU CODE pour éviter toute perte. Vous devrez également conserver les chronogrammes intéressants pour pouvoir les revoir à posteriori et les comparer.

Prise en main du programme d'exemple

Vous allez dans un premier temps utiliser un programme existant pour en comprendre le fonctionnement puis vous devrez écrire votre propre programme avec des contraintes différentes. Ouvrir dans une autre fenêtre: https://wokwi.com/arduino/libraries/SevSeg/SevSeg_Counter

Puis, en haut à gauche, cliquer sur “Save a copy”. Comme pour te TD, vous devriez vous loguer et conserver cette URL ainsi que sauvegarder votre programme à la fin de la séance et vous l'envoyer par mail (car vous travaillez sur un compte local sur le PC).

Analyse de la schématique et des composants

Lire la documentation du composant afficheur 7 segments multiplexé à 4 chiffres: https://docs.wokwi.com/parts/wokwi-7segment

En lisant le fichier diagram.json, déterminer le type d'afficheur 7 segments utilisé.

Relever sur papier les connexions électriques entre l'Arduino (avec la numérotation Arduino) et l'afficheur

En plus de la numérotation des broches Arduino, vous devrez également relever la correspondance avec les broches du microcontrôleur ATMEGA328P. Les connecteurs IOL et IOH (avec la numérotation Arduino de 0 à 13) à droite sont respectivement pilotés par les registres PORTD et PORTB sur le schéma suivant:

Regrouper les broches des différents signaux et dire par quoi elles sont pilotées. Par exemple, les 7 segments sont pilotés par …

Schéma de la carte Arduino UNO R3: https://bvdp.inetdoc.net/files/cesi/Arduino_Uno_Rev3-schematic.pdf

Utilisation d'une librairie haut niveau pour piloter l'afficheur

Dans le programme fourni, la valeur à afficher est transmise via sevseg.setNumber(val, position_point_décimal); puis sevseg.refreshDisplay() doit être appelée régulièrement pour réaliser une étape du balayage

Analyser le code fourni et comprendre l'utilisation du timer par scrutation qui a été abordée au dernier TD.

Modifier la schématique pour reproduire le schéma suivant:

Au besoin, le fichier de schématique est disponible sur: https://bvdp.inetdoc.net/files/cesi/tp1/diagram1.json

Adapter le code pour piloter l'affichage avec la valeur 10bits lue sur le Convertisseur Analogique Numérique via l'entrée A0. Ajouter également l'affichage sur la console de Debug de la valeur lue en décimal suivie d'un retour à la ligne. Vérifier en simulation le bon fonctionnement du système pour différentes positions du potentiomètre.

Analyse des signaux de commande de l'afficheur

Ajouter à la schématique le composant “Logic Analyzer (8 channels)” et connecter

  • les entrées D3..0 de l'analyseur logique aux broches 5..2 de l'Arduino
  • les entrées D7..4 de l'analyseur logique aux broches 13..10 de l'Arduino

Au besoin, le fichier de schématique est disponible sur: https://bvdp.inetdoc.net/files/cesi/tp1/diagram2.json

Lancer une simulation puis l’arrêter. Un fichier portant l'extension vcd est téléchargé, le sauver dans le dossier Téléchargements (si l'option est proposée, cocher toujours effectuer cette action).

Nous allons automatiser l'affichage des chronogrammes contenus dans ce fichier pour cela, ouvrir un terminal en pressant ALT+F2 puis xterm et touche Entrée. Copiez coller dans ce terminal le texte suivant (il faudra ensuite laisser ce terminal ouvert pendant toute la durée où vous souhaitez pouvoir afficher les chronogrammes):

echo commence
mkdir ~/wokwi
cd ~/wokwi
rm -f wokwi2gtkwave.py && wget --no-check-certificate  https://raw.githubusercontent.com/bvandepo/wokwi2gtkwave/main/wokwi2gtkwave.py
python3 ./wokwi2gtkwave.py 
echo fini

Relancer une simulation puis l’arrêter. Vous devriez voir une fenêtre gtkwave s'ouvrir et afficher le chronogramme, sinon appeler l'enseignant. Sur le chronogramme, vous pourrez zoomer à l'aide de la molette de la souris en maintenant la touche CTRL pressée. Les fichiers chronogrammes sont automatiquement déplacés dans un sous dossier ~/wokwi/vcdforgtkwave que vous pourrez renommer à posteriori si vous voulez en conserver certaines versions et éviter qu'elles ne soient écrasées.

Interprétez les chronogrammes:

  • En déduire comment les LED sont câblées dans cet afficheur?
  • Zoomer sur des instants de changements de segments et regarder combien de digits sont actifs à cet instant.
  • Combien de LEDs peuvent être allumées simultanément au maximum?
  • A quelle fréquence l'affichage de la totalité des chiffres est effectué?

Programme avec votre propre librairie

Le programme d'exemple étant maintenant compris (normalement…), vous allez devoir écrire vous même les fonctions permettant de piloter l'afficheur.

Numérotation des digits et segments

Nous utiliserons une numérotation un peu différente de celle de la documentation de l'afficheur 7 segments fournie en: https://docs.wokwi.com/parts/wokwi-7segment

numérotation TP rôle affichage sur le chronogramme
digit 0 chiffre le plus à gauche (miliers) D0
digit 1 chiffre (centaines) D1
digit 2 chiffre (dizaines) D2
digit 3 chiffre le plus à droite (unités) D3
segment 0 à 3 segment A à D non visibles
segment 4 segment E D4
segment 5 segment F D5
segment 6 segment G D6
segment 7 point décimal DP D7

Changements par rapport à l'exercice précédent

La librairie utilisée précédemment est très générique, ce qui signifie qu'elle peut être utilisée dans plusieurs configurations différentes en s'adaptant facilement (par exemple à différents type et taille d'afficheurs, à différente familles de microcontrôleurs). La librairie que vous allez écrire sera moins générique mais sera spécifique et optimisée pour un microcontrôleur et un afficheur particulier, les deux étant connectés d'une manière fixe. Ceci nous permettra d'illustrer la commande à bas niveau en pilotant les broches du microcontrôleur directement via ses registres.

Dans cet exercice, pour changer, nous allons utiliser un autre type de cablage de l'afficheur. Pour changer le type d'afficheur du schéma, remplacer dans le fichier diagram.json dans les attributs du composant “wokwi-7segment”

"attrs": { "digits": "4", "common": "anode" }

par

"attrs": { "digits": "4", "common": "cathode" }

Avec cet afficheur, vous devrez balayer les différents digits (actifs à l'état bas) et pour chaque digit piloter les segments (actifs à l'état haut) qui doivent s'allumer. VOUS DEVREZ ABSOLUMENT veiller à ce que:

  1. il n'y ait jamais deux digits actifs en même temps, pour cela vous devrez en désactiver un AVANT d'activer le suivant.
  2. les changements de valeur pilotant les segments soient réalisés lorsque AUCUN des digits n'est actif.

Programme de départ

Copier coller ce programme à la place de l'ancien. Il contient la définition des variables globales dont vous aurez besoin:

aff7seg.ino
byte numDigits = 4;
byte digitPins[] = {2, 3, 4, 5};
byte numSegments = 8;
byte segmentPins[] = {6, 7, 8, 9, 10, 11, 12, 13};
byte tabDeco7seg[]={0x3f,0x06,0x5b,0x4F,0x66,0x6d,0x7d,0x07,0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71};
 
void setup() {
  Serial.begin(115200);
  Serial.println("debut");
}
 
void loop() {
}

Initialisations

Dans un premier temps, nous utiliserons les fonctions Arduino digitalWrite(…) et pinMode(…) pour configurer/piloter les broches (en utilisant les tableaux définis en variables globales pour identifier les broches).

Compléter le programme pour configurer toutes les broches utiles dans la bonne direction et à une valeur inactive. Il est très fortement conseillé d'utiliser des boucles car le traitement à produire pour les différentes broches est quasiment le même.

Vérification de la commandabilité

Modifier loop() temporairement juste pour allumer les segments F et le point décimal de l'afficheur des dizaines.

Faire de même cette fois-ci pour allumer les segments A et E de l'afficheur des milliers.

Une fois que vous avez vérifié en simulation que vous êtes bien capables de piloter les segments, commenter ce code dans loop().

Fonction de pilotage d'un digit

Implémenter la fonction void setDigit(unsigned char nDigitActif,unsigned char nSegsActif)

  • nDigitActif prenant une valeur comprise en 0 et 3 pour indiquer le digit à activer
  • nSegsActif prenant une valeur sur 8 bits pour indiquer (à l'état haut) les segments (et le point) à allumer.

Cette fonction devra successivement:

  • désactiver tous les digits avant de changer les segments
  • piloter les segments en utilisant les valeurs des bits de nSegsActif
  • activer le bon digit

Tester votre fonction en utilisant ce sous programme loop():

aff7seg5.ino
void loop() {
setDigit(0,0x10);
setDigit(1,0x20);
setDigit(2,0x40);
setDigit(3,0x80);
}

Vérifier que l'affichage est correct. Faire de même pour cet autre programme:

Tester votre fonction en utilisant ce sous programme loop():

aff7seg7.ino
void loop() {
setDigit(0,1);
setDigit(1,2);
setDigit(2,3);
setDigit(3,4);
}

Décodage 7 segments

Nous avons vu en TD l'utilisation d'une table de transcodage (Look-Ut Table) pour réaliser le décodage 7 segments. Cette table est déclarée dans votre programme dans le tableau tabDeco7seg.

Adapter loop() pour afficher les chiffres présents dans les cases du tableau byte digits[4]; en réalisant le décodage. Vous aurez pris soin d'initialiser digit[] pour que chaque case contienne un chiffre différent à afficher sur le digit correspondant. Vous veillerez aussi à ce qu'une valeur supérieure à 15 dans une case de digit[] ne provoque pas une fuite mémoire lors de la recherche de la valeur correspondante dans la table tabDeco7seg. Pour l'instant vous ne vous occuperez pas du point décimal. Vous ne devez pas modifier la fonction void setDigit(unsigned char nDigitActif,unsigned char nSegsActif)!

Gestion du point décimal

Toutes les valeurs stockées dans le tableau tabDeco7seg ont le bit 7 à la valeur 0. Donc votre programme à l'exercice précédent doit avoir éteint tous les points décimaux.

Nous allons maintenant utiliser la variable globale byte dots; pour indiquer quels points décimaux afficher. Pour cela le bit i de dots à 1 indique qu'il faut allumer le point décimal de l'afficheur i.

Adapter le programme précédent pour activer correctement les points décimaux. Pour cela, utiliser les opérateurs logiques et de décalage vus en TD. Vous ne devez toujours pas modifier la fonction void setDigit(unsigned char nDigitActif,unsigned char nSegsActif)!

Conversion binaire 16 bits vers BCD 4 digits

Vous devez maintenant implémenter une fonction void setNumber(unsigned int val) qui va remplir les 4 cases du tableau digits à partir d'une valeur numérique sur 16 bits non signée passée via le paramètre val. Pour cela, vous pourrez utiliser notamment l'opérateur modulo (% en C).

Tester votre programme pour afficher la valeur “789.5”. Pour cela appelez setNumber(7895); et dots=4;

Gestion du temps

Observez pour l'exercice précédent sur les chronogrammes la fréquence à laquelle les digits sont balayés et relevez la durée pendant laquelle chacun d'eux est allumé à chaque affichage. Un rapport cyclique différent se traduit par une intensité lumineuse perçue différente. Pour éviter cela, nous allons mettre en place 2 tâches pseudo parallèles:

  • Une première servant à calculer une valeur à afficher (incrémentée à chaque appel) et à la convertir en BCD.
  • Une seconde servant à réaliser l'affichage multiplexé (balayage) des différents digits. UN SEUL DIGIT DOIT ÊTRE PILOTÉ A CHAQUE APPEL.

La structure du sous programme loop() est fournie et vous devez implémenter les 2 taches dans les fonctions correspondantes.

aff7seg11.ino
///////////////////////////
void tache1(){
 
}
///////////////////////////
void tache2(){
 
}
///////////////////////////
void loop() {
  unsigned int periodiciteTache1=100; //100ms entre chaque incrémentation de la valeur à afficher
  unsigned int periodiciteTache2=4;  //4ms pour l'affichage de chaque digit
  static unsigned long timerTache1 = millis();
  static unsigned long timerTache2 = millis();
  if (millis() - timerTache1 >= periodiciteTache1) {
    timerTache1 += periodiciteTache1;
    tache1();
  }
  if (millis() - timerTache2 >= periodiciteTache2) {
    timerTache2 += periodiciteTache2;
    tache2();
  }
}
///////////////////////////
 
}

Vérifier à l'aide des chronogrammes que chaque digit est bien piloté pendant approximativement 4ms et donc que l'affichage est rafraîchi à une cadence de 62.5Hz. Vérifier que la valeur affichée sur l'afficheur est bien cohérente avec le temps simulé (affiché en haut à droite de la schématique pendant la simulation).

Appeler un enseignant pour valider votre travail.

Gestion du temps par Interruption Timer

Vous avez peut être relevé des déviations par rapport à la valeur idéal de 4ms pour l'affichage sur les digits. Nous pouvons aggraver ce phénomène en rendant la tâche 1 bloquante pendant plus longtemps. Pour cela, ajouter à la fin de la tâche 1 un appel à delay(25); et relever les chronogrammes. Dans ce cas, on observe que la tâche 2 ne pourra pas être exécutée toutes les 4ms, le processeur restant bloqué dans l'exécution de la tâche 1 pendant plus de 25ms. Nous allons maintenant utiliser un mécanisme appelé Interruption pour que la tâche 2 soit appelée lorsque nécessaire, en interrompant la tâche 1 si besoin.

Avec les interruptions, il n'est plus nécessaire de venir scruter régulièrement la valeur du timer pour exécuter la tâche si besoin. Au lieu de cela, une configuration de l'interruption est effectuée au début du programme afin que la tâche 2 s'exécute à une cadence prédéterminée.

Retirer l'appel de la tâche 2 dans loop(), et appeler la fonction void setupTimer2() une fois dans setup(). Ainsi le sous programme ISR(TIMER2_COMPA_vect) est appelé automatiquement toutes les 4ms.

aff7seg13.ino
//Timer 2 : https://www.aranacorp.com/fr/utilisation-des-timers-de-larduino/
// timer2 (8 bits) qui est utilisé par la fonction Tone() et la génération de la PWM sur les broches 3 et 11.
void setupTimer2(){
  cli();                                   // disable all interrupts
  TCCR2A = (1<<WGM21)|(0<<WGM20);          // Mode CTC
  TIMSK2 = (1<<OCIE2A);                    // Local interruption OCIE2A
  TCCR2B = (0<<WGM22)|(1<<CS22)|(1<<CS21); // prediviser /256 
  OCR2A = 250;                             //250*256*1/16000000 = 4ms
  sei();                                   // enable all interrupts
}
 
//appelée toutes les 4ms:
ISR(TIMER2_COMPA_vect){                    // timer compare interrupt service routine  
    tache2();
}

Observer à nouveau les chronogrammes et vérifier que les durées d'alternance des digits sont maintenant respectées.

NotaBene: Nous utilisons ici le mécanisme d'interruption sans prendre les précautions normalement exigées. Idéalement, il faudrait mettre en place un verrou sur les données et périphériques utilisés en commun par les différentes tâches pour éviter par exemple que l'interruption timer affiche des valeurs en cours de réglage par la tâche 1. Nous illustrons plus tard ces principes.

Appeler un enseignant pour valider votre travail.

Pilotage des broches à bas niveau

Nous avons utilisé jusqu'à maintenant la fonction digitalWrite(…) pour piloter individuellement les broches de l'afficheur. Nous allons maintenant mettre en place un pilotage bas niveau permettant d'accéder simultanément à plusieurs bits en écrivant une valeur 8 bits dans un registre associé au port de sortie.

Nous allons commencer par évaluer le temps nécessaire à la commande de l'afficheur 7 segments par votre fonction void setDigit(void setDigit(…). Pour cela, nous allons utiliser une broche (A3, pilotée par le bit 3 du port C) qui va être mise à 1 pendant l'exécution de la fonction et à 0 le reste du temps. Configurer la broche A3 en sortie et à l'état bas dans setup(). Au début de la fonction void setDigit(…), modifier le registre PORTC pour mettre son bit 3 à la valeur 1, puis le remettre à la valeur 0 à la fin de la fonction (utiliser les opérateurs & et | ainsi que des masques).

Défaire la connexion du point décimal à l'entrée D7 de l'analyseur logique et y connecter A3 de l'Arduino pour pouvoir observer ce signal et relever la durée pendant laquelle le signal est à l'état haut pour quelques digits.

Implémentation bas niveau

Nous allons conserver cette implémentation de votre fonction void setDigit(…) en la renommant void setDigitHAL(…) et vous allez maintenant devoir ré-implémenter une fonction void setDigit(…) réalisant le même travail mais sans utiliser la fonction digitalWrite(…) de la HAL Arduino.

Veillez à ne pas écrire inutilement plusieurs valeurs à la suite dans un registre de PORT, car chaque valeur écrite pilote effectivement la broche. Éventuellement passer par une variable temporaire adéquate, dont la valeur sera finalement affectée au registre de port.

Pour réaliser la nouvelle implémentation de void setDigit(…), vous devrez manipuler les différents registres PORTB, PORTC et PORTD pour:

  1. activer la broche A3 pour début de mesure de la durée d'exécution: TIC
  2. régler toutes les cathodes inactives
  3. régler les segments (poids faible), en maintenant toutes les cathodes inactives
  4. régler les segments (poids fort)
  5. régler la cathode active
  6. désactiver la broche A3 pour fin de mesure de la durée d'exécution:TOC

Pour cela vous devrez utiliser des opérateurs logiques, de décalage, des valeurs constantes ainsi que les paramètres nDigitActif et nSegsActif. Avant de coder, vous devriez dessiner chaque registre modifié pour chaque action et les valeurs modifiées pour chacun des bits.

Une fois la fonction codée et le fonctionnement étant correct, relever sa durée d'exécution sur le chronogramme. La durée relevée par votre professeur est de 2.5 us, ce qui est bien plus rapide que l'implémentation utilisant la HAL Arduino.

Organisation des fichiers

En vous inspirant de la librairie utilisée dans le TD1 cesitd1, créer 2 fichiers:

  • lib7seg.cpp dans lequel vous déplacerez les constantes et l'implémentation des fonctions
  • lib7seg.h dans lequel vous copierez les prototypes des fonctions

Vous déplacerez les initialisations liées à l'afficheur 7 segments de la fonction setup() à une fonction setup7seg() qui sera elle même appelée par setup() et rangée dans la librairie.

Vous devrez également ajouter à votre librairie une fonction void setDots(unsigned char val) permettant de régler la valeur de la variable dots et donc de configurer quels points décimaux doivent être allumés.

Appeler un enseignant pour valider votre travail.

Application

Nous proposons maintenant d'utiliser la librairie que vous venez de faire pour réaliser une application de monitoring. Connecter 4 potentiomètres aux entrées A0, A1, A2 et A4 de l'Arduino pour reproduire le schéma suivant:

Au besoin, le fichier de schématique est disponible sur: https://bvdp.inetdoc.net/files/cesi/tp1/diagram3.json

Ecrire un programme permettant de réaliser l'acquisition des mesures sur ces 4 entrées en permanence dans une tache, chaque 100ms. Les valeurs lues devront être stockées dans un tableau unsigned int datain[4].

Dans une seconde tache, exécutée chaque deux secondes, le numéro de l'entrée dont la valeur est affichée sur l'afficheur 7 segments sera changée, de sorte que l'on balaye les 4 entrées en 8 secondes. Cette tache remplie le rôle de choix d'aiguillage. Elle devra également indiquer sur l'afficheur quelle est la valeur en cours d'affichage, cette tache devra régler la valeur de commande des points décimaux (avec la fonction setDots(…)) pour allumer le point décimal i lors de l'affichage de la valeur datain[i].

Une dernière tache, exécutée à la cadence maximale doit utiliser la valeur de l'entrée sélectionnée par la seconde tache pour qu'elle soit affichée par la routine d'interruption timer de votre librairie.

Affichage des mesures

Nous allons maintenant tracer les valeurs lues sur les 4 entrées analogiques dans un plotter comme indiqué sur https://docs.wokwi.com/guides/serial-monitor#configuring-the-serial-monitor . Pour cela, dans diagram.json, après la ligne:

"editor": "wokwi",

ajouter:

"serialMonitor": {
"display": "plotter",
"newline": "lf"
},

et dans la tache 1, après la collecte des mesures sur les entrées analogiques, ajouter:

fintache1.ino
for (int i=0;i<4;i++){
  Serial.print(datain[i]);
  Serial.print(" ");
 }
Serial.println();

En lançant la simulation et en jouant sur les potentiomètres, vous devriez voir un tracé similaire à:

Appeler un enseignant pour valider votre travail.

Rendre la librairie de pilotage de l'afficheur 7 segments "Orientée Objet"

Visionner la vidéo de cours présentant les concepts de la Programmation Orientée Objet: https://youtu.be/5Y9P4MIkTIs

Polycopié du cours POO: https://bvdp.inetdoc.net/files/iut/cours_POO_intro_complet_2021_numbered.pdf

Si vous êtes arrivé jusqu'ici, demander à l'enseignant quoi faire ensuite.

cesitp1.txt · Dernière modification: 2021/12/02 23:07 de bvandepo

Outils de la page