Laboratoire 3 ============= Les notions abordées dans ce laboratoire sont : * Mathématiques : - l'algèbre booléenne, - les tables de vérité, - le système binaire de représentation des nombres. * Physique : - l'électronique numérique, - le décodage, - le multiplexage. * Programmation Python : - l'utilisation de la librairie numpy, - la manipulation de matrices, - l'utilisation des "pygame.EVENT". Programme 4: Afficheur 7 segments simple ---------------------------------------- Cette première partie a pour but de vous familiariser avec la notion de table de vérité. Nous allons apprendre à manipuler des signaux électriques binaires (0 ou 1) à travers des circuits combinatoires. Le but est de représenter des nombres binaires sur un afficheur 7 segments. Nous apprendrons aussi comment traiter des signaux reçus en entrée par l'intermédiaire d'un bouton-poussoir. Les tables de vérité ~~~~~~~~~~~~~~~~~~~~~~~~~~~ Tout circuit combinatoire (simple combinaison de signaux d'entrée => pas de mémoire dans le circuit), aussi complexe qu'il soit, peut être représenté par une table de vérité. Elle donne les valeurs des signaux de sortie en fonction des signaux d'entrée. Cela permet de traiter le circuit comme une "boite noire" sans devoir se préocuper de sa réelle composition. Prenons par exemple les tables de vérité des portes logiques **ET** et **OU**. .. figure:: figures/ET_OU.png :scale: 100% :align: center :alt: Tables de vérité ET et OU. Tables de vérité ET et OU. Chacune des portes logiques prend en entrée deux signaux et renvoie un seul signal. Ces portes se comportent exactement comme les fonctions "and" et "or" que vous connaissez déjà en python. Cependant il s'agit bien ici de réels composants élétroniques. Comme vous pouvez le constater les deux colonnes de gauche représentent toutes les combinaisons d'entrées possibles. On peut aussi considérer qu'il s'agit d'une numérotation des entrées en nombres binaires (``[0 0]`` correspond à l'entrée ``0``, ``[0 1] -> 1``, ``[1 0] -> 2``, ``[1 1] -> 3``). Vous pouvez consulter `ici `_ un rappel sur les nombres binaires. Programme de base ~~~~~~~~~~~~~~~~~ Voici un programme qui vous permettra d'afficher les différents compasants élétroniques que nous allons étudier: un Arduino, un décodeur CD4511, un afficheur 7 segments et un bouton-poussoir. :: # ------------------------------------------------------------------------ # Laboratoires de programmation mathématique et physique 2 # ------------------------------------------------------------------------ # # Programme : 7 segments. # # ------------------------------------------------------------------------ import math import pygame import sys import numpy as np ### Constante(s) NOIR = (0, 0, 0) GRIS = (200, 200, 200) ROUGE = (255, 0, 0) ### Variables Globales def dessiner_arduino(sortie_arduino, sortie_CD4511, sortie_bouton): fenetre.blit(image_arduino, pos_arduino) fenetre.blit(image_CD4511, pos_CD4511) fenetre.blit(image_bouton, pos_bouton) off_ard = 194 off_cd = 15 for i in range(0, 4): if sortie_arduino[i] == 0: couleur = NOIR else: couleur = ROUGE pygame.draw.line(fenetre, couleur, (pos_arduino[0] + 280, pos_arduino[1] + off_ard), (pos_CD4511[0] + 7, pos_CD4511[1] + off_cd), 5) off_ard = off_ard + 14 off_cd = off_cd + 19 off_cd = 15 off_aff = 27 for i in range(0, 7): if sortie_CD4511[i] == 0: couleur = NOIR else: couleur = ROUGE pygame.draw.line(fenetre, couleur, (pos_afficheur[0], pos_afficheur[1] + off_aff), (pos_CD4511[0] + 102, pos_CD4511[1] + off_cd), 5) off_aff = off_aff + 19 off_cd = off_cd + 19 connexion_bouton(sortie_bouton) def dessiner_afficheur(sortie_CD4511): positions_barres = [[32, 14], [89, 20], [87, 88], [28, 150], [17, 88], [19, 20], [30, 82]] fenetre.blit(image_afficheur, pos_afficheur) i = 0 for barre in positions_barres: if sortie_CD4511[i] == 0: i = i + 1 continue x_b = pos_afficheur[0] + int(round(barre[0]*(image_afficheur.get_width()/133))) y_b = pos_afficheur[1] + int(round(barre[1]*(image_afficheur.get_height()/192))) if i == 0 or i == 3 or i == 6: fenetre.blit(barre_horizontale, (x_b, y_b)) else: fenetre.blit(barre_verticale, (x_b, y_b)) i = i + 1 return def composant_CD4511(entree): return np.array([0, 0, 0, 0, 0, 0, 0]) def sortie_memorisee(): return np.array([0, 0, 0, 0]) def gerer_click(): return 0 def connexion_bouton(sortie_bouton): return ### Paramètre(s) dimensions_fenetre = (1100, 600) # en pixels images_par_seconde = 25 pos_arduino = (65, 84) pos_CD4511 = (537, 263) pos_afficheur = (818, 251) pos_bouton = (537, 486) pos_centre_bouton = (589, 521) rayon_bouton = 18 pin_arduino = (pos_arduino[0] + 279, pos_arduino[1] + 353) pin_bouton = (pos_bouton[0] + 13, pos_bouton[1] + 13) ### Programme # Initialisation pygame.init() fenetre = pygame.display.set_mode(dimensions_fenetre) pygame.display.set_caption("Programme 7 segments") horloge = pygame.time.Clock() image_afficheur_s = pygame.image.load('images/7_seg_s.png').convert_alpha(fenetre) barre_verticale_s = pygame.image.load('images/vertical_s.png').convert_alpha(fenetre) barre_horizontale_s = pygame.image.load('images/horizontal_s.png').convert_alpha(fenetre) image_afficheur = pygame.image.load('images/7_seg.png').convert_alpha(fenetre) barre_verticale = pygame.image.load('images/vertical.png').convert_alpha(fenetre) barre_horizontale = pygame.image.load('images/horizontal.png').convert_alpha(fenetre) image_arduino = pygame.image.load('images/arduino.png').convert_alpha(fenetre) image_CD4511 = pygame.image.load('images/CD4511.png').convert_alpha(fenetre) image_CD4028 = pygame.image.load('images/CD4028.png').convert_alpha(fenetre) image_bouton = pygame.image.load('images/bouton.png').convert_alpha(fenetre) couleur_fond = GRIS # Boucle principale while True: temps_maintenant = pygame.time.get_ticks() for evenement in pygame.event.get(): if evenement.type == pygame.QUIT: pygame.quit() sys.exit() sortie_bouton = 0 fenetre.fill(couleur_fond) sortie_CD4511 = composant_CD4511(sortie_memorisee()) dessiner_afficheur(sortie_CD4511) dessiner_arduino(sortie_memorisee(), sortie_CD4511, sortie_bouton) pygame.display.flip() horloge.tick(images_par_seconde) Recopiez ce programme dans un fichier appelé ``prog-4.py``, et vérifiez qu'il fonctionne correctement. L'Arduino ~~~~~~~~~ L'Arduino est une plaquette programmable permettant de mémoriser des valeurs et d'éffectuer des opérations. Dans le cadre de cette simulation, nous allons l'utiliser pour générer des entrées binaires dans notre circuit. Créez une variable globale ``valeur_memorisee`` qui sera la valeur à afficher sur le 7 segments. Initialisez cette valeur à un nombre arbitraire entre 0 et 9. Ensuite, transformez cette variable décimale en une liste de 4 valeurs binaires, utilisez le formalisme `gros boutiste `_ (les bits avec le plus de poids sont aux petites adresses, exemple: 7 devient ``[0 1 1 1]``). **Indice:** utiliser la division entière et le reste de la division par 2. Procédure à suivre: 1. Créer et initialiser ``valeur_memorisee``. 2. Compléter la fonction ``sortie_memorisee()`` afin qu'elle retourne le nombre binaire correspondant à ``valeur_memorisee``. **Note:** utiliser les ``numpy.array`` comme dans le programme de base. 3. Vérifier votre fonction en testant différentes valeurs entre 0 et 9. Par exemple, pour 7 vous devriez obtenir ceci: .. figure:: figures/prog-7-seg-screenshot-1.png :scale: 50% :align: center :alt: Affichage sortie arduino. Affichage sortie arduino. Le décodeur CD4511 ~~~~~~~~~~~~~~~~~~ Le décodeur est un circuit combinatoire que l'on connecte directement à la sortie de l'aduino. Son rôle est de transformer les signaux représentant des nombres binaires en signaux iterprétables par l'afficheur 7 segments. Il a donc 4 entrées et 7 sorties correspondant à chaque led de l'afficheur. Ces sorties sont étiquetées de "a" à "g" et correspondent au schéma suivant : .. figure:: figures/prog-7-seg-afficheur.png :scale: 50% :align: center :alt: Afficheur 7 segments. Afficheur 7 segments. Pour afficher le nombre 7 il faut donc allumer les led "a", "b" et "c". La sortie du décodeur sera le vecteur ``[1 1 1 0 0 0 0] -> [a b c d e f g]``. Determinez la table de vérité de ce composant: combien de lignes contient elle ? Sont elles toutes utiles pour notre application ? Procédure à suivre: 1. Compléter la fonction ``composant_CD4511(entree)``. Créer une variable ``tdv`` et y placer une matrice numpy représentant la table de vérité du composant. Dans cette matrice, placer les lignes de sortie de la table, les valeurs d'entrées correspondent à l'indice de la ligne. Par exemple pour récupérer la sortie correspondant à l'entrée ``[0 1 1 1]`` on retournera ``tdv[7]``. **Note:** exemple de matrice numpy:: np.array([[1, 2], [3, 4]]) 2. Transformer l'entrée (vecteur représentant un nombre binaire) en un nombre décimal afin de l'utiliser comme indice de la matrice. 3. Retourner la bonne ligne de la table de vérité et vérifier, pour plusieurs ``valeur_memorisee``, que l'afficheur 7 segments affiche la bonne valeur. Le bouton-poussoir ~~~~~~~~~~~~~~~~~~ Le principe du bouton-poussoir est simple : si l'on appuie sur le bouton un signal 1 est envoyé à l'arduino sinon c'est un signal 0. Le but de cette partie du programme est de modifier la ``valeur_mermorisee`` en fonction du signal du bouton. Procédure à suivre : 1. Connecter le bouton à l'arduino en complétant la fonction ``connexion_bouton(sortie_bouton)``. Tracer simplement une ligne noire entre ``pin_arduino`` et ``pin_bouton``, si la ``sortie_bouton`` vaut 1 alors la ligne doit devenir rouge. 2. Dans la boucle principale vérifier si l'utilisateur est en train de cliquer. Utiliser ``pygame.mouse.get_pressed()``. 3. Vérifier si le clic se situe bien sur le bouton. Utiliser ``pos_centre_bouton`` et ``rayon_bouton``. Si c'est le cas, mettre à jour ``sortie_bouton``. 4. Lancer le programme et observer que votre signal correspond à la descrition d'un bouton-poussoir. 5. Repérer un clic, si le signal du bouton passe de 0 à 1, incrémenter une fois la ``valeur_mermorisee``. La valeur ne doit pas être continuellement incrémentée lorsqu'on laisse le bouton enfoncé, mais juste au moment du clic. 6. Vérifier votre implémentation. Le signal d'horloge ~~~~~~~~~~~~~~~~~~~ En électronique digitale, on utilise très souvent des signaux périodiques appelés signaux d'horloge afin de synchroniser différents évenements. Il s'agit d'un signal oscillant entre 0 et 1 à une fréquence fixée. Créez un signal d'horloge qui incrémente ``valeur_mermorisee`` toutes les secondes. Procédure à suivre: 1. Dans l'initialisation du programme, créer un ``pygame.USEREVENT`` en utilisant :: pygame.time.set_timer(pygame.USEREVENT, temps) où ``temps`` est le délai entre deux évenements. Pour repérer l'événement utilisé, dans votre boucle principale :: evenement.type == pygame.USEREVENT 2. Créer une variable ``sig_horloge`` qui varie entre 0 et 1 toutes les ``0.5`` secondes. 3. Quand ``sig_horloge`` passe de 0 à 1 (flanc montant de l'horloge) incrémenter ``valeur_mermorisee``. 4. Afficher un cercle au niveau de l'afficheur 7 segments (utiliser ``pos_afficheur``). Ce cercle sera rouge si ``sig_horloge`` vaut 1, noir sinon. Programme 5: Multiplexage des 7 segments ---------------------------------------- Nous allons maintenant afficher 6 valeurs différentes sur un ensemble d'afficheurs. Au premier abord, nous pourrions tenter d'utiliser 6 fois 4 sorties de l'arduino avec 6 décodeurs CD4511. Cependant, cette solution n'est pas acceptable, le nombre de sortie de l'arduino est limité à 13 et nous devrions utiliser un nombre excessif de composants. La solution: le **multiplexage**. Au lieu d'allumer tous les afficheurs en même temps, nous allons en allumer un à la fois. Il suffit ensuite de passer d'un afficheur à l'autre suffisamment rapidement pour créer l'illusion qu'ils sont tous allumés en même temps (`persitance rétinienne `_). Suite du programme ~~~~~~~~~~~~~~~~~~~~~~~~ 1. Remplacer les deux fonctions suivantes : :: def dessiner_arduino(sortie_arduino, sortie_CD4511, sortie_CD4028, sortie_bouton): fenetre.blit(image_arduino, pos_arduino) fenetre.blit(image_CD4511, pos_CD4511) fenetre.blit(image_bouton, pos_bouton) fenetre.blit(image_CD4028, pos_CD4028) for j in range(0, 2): if j == 0: off_ard = 285 off_cd = 15 pos_carte = pos_CD4511 r = range(0, 4) if j == 1: off_ard = 194 off_cd = 91 pos_carte = pos_CD4028 r = range(4, 8) for i in r: if sortie_arduino[i] == 0: couleur = NOIR else: couleur = ROUGE pygame.draw.line(fenetre, couleur, (pos_arduino[0] + 280, pos_arduino[1] + off_ard), (pos_carte[0] + 7, pos_carte[1] + off_cd), 5) off_ard = off_ard + 14 off_cd = off_cd + 19 off_cd = 15 off_aff = 5 i = 0 for i in range(0, 7): if sortie_CD4511[i] == 0: couleur = NOIR else: couleur = ROUGE pygame.draw.line(fenetre, couleur, (pos_afficheur[0] + 591, pos_afficheur[1] + off_aff), (pos_CD4511[0] + 102, pos_CD4511[1] + off_cd), 5) off_aff = off_aff + 19 off_cd = off_cd + 19 if sortie_bouton == 0: couleur = NOIR else: couleur = ROUGE pygame.draw.line(fenetre, couleur, (pos_arduino[0] + 279, pos_arduino[1] + 353), (pos_bouton[0] + 13, pos_bouton[1] + 13), 5) i = 0 off_cd = (102, 111) off_aff = 44 for i in range(0, 6): if sortie_CD4028[i] == 0: couleur = NOIR else: couleur = ROUGE pygame.draw.line(fenetre, couleur, (pos_CD4028[0] + off_cd[0], pos_CD4028[1] + off_cd[1]), (pos_afficheur[0] + off_aff, pos_CD4028[1] + off_cd[1]), 5) pygame.draw.line(fenetre, couleur, (pos_afficheur[0] + off_aff, pos_afficheur[1]), (pos_afficheur[0] + off_aff, pos_CD4028[1] + off_cd[1] - 2), 5) off_cd = (off_cd[0], off_cd[1] - 20) off_aff = off_aff + 101 def dessiner_afficheur(sortie_CD4511, sortie_CD4028): positions_barres = [[32, 14], [89, 20], [87, 88], [28, 150], [17, 88], [19, 20], [30, 82]] for j in range(0, 6): fenetre.blit(image_afficheur_s, (pos_afficheur[0] + j*101, pos_afficheur[1])) if sortie_CD4028[j] == 1: i = 0 for barre in positions_barres: if sortie_CD4511[i] == 0: i = i + 1 continue x_b = j*101 + pos_afficheur[0] + int(round(barre[0]*(image_afficheur_s.get_width()/133))) y_b = pos_afficheur[1] + int(round(barre[1]*(image_afficheur_s.get_height()/192))) if i == 0 or i == 3 or i == 6: fenetre.blit(barre_horizontale_s, (x_b, y_b)) else: fenetre.blit(barre_verticale_s, (x_b, y_b)) i = i + 1 return 2. Remplacer la section des paramètres : :: ### Paramètre(s) dimensions_fenetre = (1100, 600) # en pixels images_par_seconde = 25 pos_arduino = (0, 70) pos_CD4511 = (333, 340) pos_CD4028 = (333, 128) pos_afficheur = (500, 350) pos_bouton = (333, 524) pos_centre_bouton = (pos_bouton[0] + 51, pos_bouton[1] + 34) rayon_bouton = 18 pin_arduino = (pos_arduino[0] + 279, pos_arduino[1] + 353) pin_bouton = (pos_bouton[0] + 13, pos_bouton[1] + 13) 3. Utiliser ces deux lignes dans la boucle principale pour tester l'affichage : :: dessiner_arduino(np.zeros(8, dtype=int), np.zeros(7, dtype=int), np.zeros(6, dtype=int), 0) dessiner_afficheur(np.zeros(7, dtype=int), np.zeros(6, dtype=int)) Sélection d'un afficheur ~~~~~~~~~~~~~~~~~~~~~~~~ Commençons par sélectionner un seul des 7 segments pour y afficher la ``valeur_memorisee``. Procédure à suivre: 1. Modifier la fonction ``sortie_memorisee()`` afin qu'elle retourne aussi le numéro de l'afficheur à allumer (``num_afficheur``). Ce nombre doit être en binaire comme il s'agit d'une sortie digitale. Il sera placé sur les 4 bits de poids faible de la sortie : par exemple, si l'on veut afficher la valeur 7 (``[0 1 1 1]``) sur le troisième afficheur (``[0 0 1 1]``), on aura comme sortie ``[0 1 1 1 0 0 1 1]``. 2. Remplacer les arguments ``sortie_arduino``, ``sortie_CD4511`` et ``sortie_bouton`` dans la fonction ``dessiner_arduino()`` de la boucle principale par les valeurs adéquates. Testez votre implémentation, vous devriez obtenir ceci pour l'exemple ci-dessus: .. figure:: figures/prog-7-seg-screenshot-2.png :scale: 50% :align: center :alt: Affichage sortie arduino programme 2. Affichage sortie arduino programme 2. 3. Créer une fonction ``composant_CD4028``, celle-ci prend en argument un vecteur de 4 signaux et retourne un vecteur de 7 signaux indiquant l'afficheur à allumer. Par exemple, pour allumer l'afficheur numéro 3, retourner ``[0 0 0 1 0 0 0]``. **Indice:** une fonction particulière de numpy peut être utilisée pour représenter la table de vérité de ce composant. Remplacer la valeur ``sortie_CD4028`` par votre sortie de fonction et tester votre programme, vous devriez obtenir ceci: .. figure:: figures/prog-7-seg-screenshot-3.png :scale: 50% :align: center :alt: Affichage sortie CD4028. Affichage sortie CD4028. 4. Créer un nouveau signal d'horloge avec une période de 40 ms. Sur le flanc montant de l'horloge, incrémenter la valeur de ``num_afficheur``. Ceci devrait créer un balayage rapide des afficheurs. **Note:** utiliser ``pygame.USEREVENT + 1`` pour créer un nouvel ``USEREVENT`` Que constatez-vous ? L'affichage est-il lisible et continu ? Latence des afficheurs ~~~~~~~~~~~~~~~~~~~~~~~~ Afin de régler ce problème d'affichage, nous allons inclure dans notre code la latence des afficheurs. Un vrai afficheur 7-segment ne peut s'éteindre instantanement, cela prend quelque millisecondes, ce qui crée une latence. Procédure à suivre: 1. Créer une variable globale ``latence_mat`` une matrice de taille ``(6 7)`` dans laquelle chaque ligne comprend les 7 signaux d'un afficheur. Vous pouvez maintenant retenir l'entrée de chaque afficheur. 2. Observez la fonction ``dessiner_afficheur``, vous pouvez constater que cette fonction éxecute sa boucle principale uniquement si la ``sortie_CD4028`` vaut 1. Une fois dans la boucle principale, chaque LED est allumée ou non en fonction de la ``sortie_CD4511``. Vérifier que vous comprenez bien cette fonction en effectuant des tests sur l'affichage (exemple: allumer trois afficheurs avec la même valeur). 3. Modifier cette fonction pour faire en sorte que tous les 7 segments affichent leur valeur de latence. Si l'afficheur est sélectionné par la ``sortie_CD4028``, mettre à jour sa valeur dans ``latence_mat`` en utilisant la ``sortie_CD4511`` courante. Ensuite, allumer tous les afficheurs avec les valeurs précédentes de ``sortie_CD4511`` comprise dans ``latence_mat``. 4. Tester votre implémentation, augmenter la période de l'horloge de balayage des afficheurs afin d'observer la mise à jour des valeurs de latence. Quand la ``valeur_memorisee`` est modifiée, les valeurs afichées devraient changer graduellement, comme dans l'exemple ci-dessous: .. figure:: figures/prog-7-seg-screenshot-4.png :scale: 50% :align: center :alt: Affichage avec latence. Affichage avec latence. Heures, minutes, secondes ~~~~~~~~~~~~~~~~~~~~~~~~~ Nous allons maintenant utiliser ces 6 afficheurs pour créer une horloge heures, minutes, secondes. Procédure à suivre: 1. Créer un vecteur et y placer l'ensemble des valeurs à mémoriser : tous les digits de l'horloge. **Note:** pour obtenir l'heure, par exemple, utiliser la librairie datetime:: import datetime as dt dt.datetime.now().hour 2. Balayer l'ensemble des valeurs mémorisées afin d'afficher l'horloge complète. Synchroniser le changement de digit avec le changement d'afficheur (placer les sur le même flanc montant). Si la sychronisation est correcte vous devriez avoir une valeur fixe dans chaque afficheur. 3. Toutes les secondes (utiliser la première horloge), mettre à jour vos valeurs mémorisées avec l'heure courante. 4. Tester votre implémentation, vous devriez obtenir une horloge fonctionnelle (ici pour 13h 56min 11sec): .. figure:: figures/prog-7-seg-screenshot-5.png :scale: 50% :align: center :alt: Affichage horloge. Affichage horloge. **Note:** 40 ms fonctionne bien comme fréquence de balayage. Hello World ~~~~~~~~~~~~~ Finalement, affichons "Hello World" sur les afficheurs. Le message "Hello World" est cependant trop long pour être entièrement affiché. Il va donc falloir le faire défiler en continu de gauche à droite. Procédure à suivre: 1. Modifier la table de vérité de la fonction ``composant_CD4511``, remplacer les nombres mémorisées par les lettres de l'expression "Hello World". Chaque lettre doit prendre une ligne de la table, inutile de stocker deux fois la même lettre. Combien de lettres pouvez-vous stocker en gardant 4 bits en entrée ? Est-ce suffisant ? 2. Chaque lettre correspond maintenant à une entrée de la table, vous pouvez donc créer un vecteur d'entiers représentant le message. Ce vecteur devient l'ensemble des valeurs mémorisées. 3. Afficher le début du message et toutes les secondes décaler le message vers la gauche pour réveler la suite. Boucler pour que le message s'affiche en continu. 4. Faites en sorte que cliquer sur le bouton décale le message d'une lettre vers la gauche. Cela permettra à l'utilisateur d'accélérer le display. 5. Vous devriez obtenir ceci: .. figure:: figures/prog-7-seg.gif :scale: 100% :align: center :alt: Hello World. Hello World.