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.

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:

    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 :

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)
    

    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:

    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:

    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:

    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):

    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:

    Hello World.

    Hello World.