J'interface un afficheur 7 segments avec le 8051

Comme je m'ennuie et que les sujets sur quoi écrire m'échappent, je me suis dit "pourquoi ne pas parler des mini-projets qu'on nous demande de faire en EAI ?". Idée de génie ! Du coup, j'ai décidé de vous parler du projet sur lequel je bosse en ce moment.

Comme l'indique le titre, le projet consiste à interfacer un afficheur à 7 segments avec le microcontrôleur 8051. Il nous a aussi été demandé de brancher un petit clavier numérique et de faire en sorte qu'à chaque fois qu'on appuie sur un bouton, sa valeur s'affiche sur l'afficheur à 7 segments.

Il y a cependant quelques règles à respecter :

  • La première est qu'il faudra tout écrire en assembleur. Vous croyiez que le langage C est un langage de bas niveau ? Bienvenue dans le monde de la programmation embarquée ! (mais sérieusement, évitez d'écrire en assembleur à moins qu'il n'y ait pas d'autre choix ou que les performances le nécessitent)

  • Deuxièmement, nous ne devons pas utiliser de décodeur BCD. Un tel IC aurait été utile de par sa nature à faciliter l'interfaçage avec l'afficheur : nous n'aurions eu qu'à lui passer le chiffre souhaité (sous forme binaire) et il se débrouillera pour allumer les diodes adéquates dans l'afficheur à 7 segments (a, b, etc.). Mais vu que son utilisation est interdite, il faudra tout faire à la main, en software.

Avant de nous vautrer dans le code, parlons d'abord des logiciels requis. Pour ma part, j'utilise Proteus ISIS pour la simulation et MCU8051 comme IDE. Ce dernier comporte aussi un simulateur mais vu que je m'étais déjà familiarisé avec ISIS, j'ai eu la flemme d'apprendre à utiliser un autre simulateur.

J'avais auparavant travaillé avec le PIC16F84, et ce n'est qu'en passant au 8051 que je me suis rendu compte à quel point j'étais gâté côté documentation. N'ayant pas pu trouver de datasheet, j'ai dû fouiller un peu partout et faire des recherches pénibles chaque fois que je m'étais planté. Et croyez-moi, c'était très fréquent. Si j'avais prêté plus d'attention aux explications du prof, ç'aurait été plus facile, mais que voulez-vous que j'y fasse ? C'est une séance matinale et il nous est interdit de nous cafféiner en pleine classe. Et ça, c'est en supposant que j'y avais assisté.

Je tiens à noter que les restrictions qui nous ont été imposées sont à but éducatif. Comme vous l'avez probablement déjà constaté, ce "projet" n'est qu'un exercice classique, ce n'est pas une machine qui va révolutionner le monde tel qu'on le connaît. Mais le but n'est justement pas là, le but est d'apprendre à écrire en assembleur, et non pas d'avoir un montage qui fonctionne le plus rapidement possible, car sinon on nous aurait donné carte blanche pour utiliser les technologies que l'on veut. Avec un Arduino par exemple, ç'aurait été beaucoup plus facile à réaliser car ce machin a une bibliothèque toute faite pour tout ce qui peut vous passer par la tête.

Interfaçage avec l'afficheur à 7 segments

Pour ce faire j'ai utilisé le module nommé "7SEG-DIGITAL". J'ai donc connecté ses 7 pins au port P0 du 8051 en prenant soin de placer des résistances de 100 Ohms entre les connexions. Après quelques tests, j'ai pu conclure quel succession de bits envoyer pour afficher telle ou telle valeur :

La tâche de loin la plus difficile était celle de réaliser cette merveille sous mspaint. Plus difficile que le code même !

J'ai ensuite enregistré ces valeurs dans la RAM à partir de l'adresse 30h. L'idée est d'écrire une routine qui, pour un chiffre N, enverra à l'afficheur l'octet stocké à l'adresse 30h + N. Ainsi, pour afficher le chiffre 7, il faudra envoyer la valeur stockée dans l'adresse 30h + 7h, soit 37h : #00000111b. J'ai jugé que cet algorithme se traduirait en peu code, mais il se peut qu'il y ait des algorithmes plus efficaces.

Tout ce qu'il me reste à faire maintenant est de trouver un moyen pour passer la valeur souhaitée à la fonction. J'ignore si c'est la façon idiomatique de faire les choses, mais j'ai décidé de passer des arguments à la fonction par le biais du registre à utilisation générale R7. Ce n'est pas vraiment une convention, mais plutôt un choix arbitraire. Voilà donc la totalité de la fonction display :

display:
 push a
 push 0
 mov a, #30h
 add a, r7
 mov r0, a
 mov p0, @r0
 pop 0
 pop a
 ret

La première chose que la fonction fait est de sauvegarder le contenu de A et de R0 sur la pile (stack), afin de les restaurer plus tard à coups de POP. Puisque la fonction utilise ces registres-là, elle risque d'altérer les valeurs qui y étaient préalablement enregistrées, causant ainsi des bugs si le code essaie de les utiliser après que la fonction soit appelée.

Ensuite, on place dans l'accumulateur la somme de valeur 30h et du contenu du registre R7, soit le chiffre que l'on veut afficher. On enregistre ensuite ce résultat - qui n'est autre que l'adresse de la représentation du chiffre à afficher - dans R0 puis on envoie au port P0 la valeur pointée par R0 (adressage indirect). Après avoir restauré les données de l'accumulateur et de R0, on met fin à la fonction avec l'instruction RETurn.

On aurait pu se passer du registre R0 et d'utiliser @a pour pointer sur l'adresse voulue, mais dans ce cas, le code refuse tout bonnement de se compiler pour une raison qui m'est inconnue. Apparemment on ne peut pas utiliser l'accumulateur pour faire de l'adressage indirect à moins qu'on le fasse avec @a+dptr ou encore @a+pc, mais je ne vois malheureusement pas comment procéder dans ces cas là, j'ai donc opté pour la solution la plus facile.

Interfaçage avec le keypad

Ouais, c't'un clavier.

Là c'est plus compliqué. La première approche à laquelle j'ai pensé consiste à utiliser 6 boutons, et par conséquent six pins : les cinq premiers boutons vaudront 0 - 4 respectivement si le sixième bouton est appuyé, 5 - 9 s'il ne l'est pas. Le sixième bouton aurait dans ce cas une fonction similaire à celle du bouton SHIFT du clavier : le clavier shifte entre les lettres majuscules et minuscules, tandis que le sixème bouton, dans notre cas, shifterait entre [0 .. 4] et [5 .. 9]. Le code ne serait pas trop compliqué, mais le keypad serait plus pénible à utiliser.

Une autre alternative serait d'utiliser 10 boutons, chacun d'entre eux connecté sur un pin. Vu que chaque port a une longueur de 8 bits, cette méthode nécessiterait l'utilisation de deux ports, ce qui n'est pas très pratique.

Après avoir effectué quelques recherches, il s'est avéré qu'il y avait une méthode classique pour pallier à ce problème. L'avantage est qu'elle ne nécessite que 7 pins pour une totalité de douze boutons, mais l'algorithme a l'air assez compliqué à implémenter.

Voici donc le corps de la fonction responsable de lire le keypad :

read_keypad:
 mov p1, #11111111b
 clr p1.0
 jnb p1.3, ret_1
 jnb p1.4, ret_4
 jnb p1.5, ret_7

 setb p1.0
 clr p1.1
 jnb p1.3, ret_2
 jnb p1.4, ret_5
 jnb p1.5, ret_8
 jnb p1.6, ret_0

 setb p1.1
 clr p1.2
 jnb p1.3, ret_3
 jnb p1.4, ret_6
 jnb p1.5, ret_9 

 ret

ret_1:
 mov r7, #1d
 ret
ret_2:
 mov r7, #2d
 ret
ret_3:
 mov r7, #3d
 ret
ret_4:
 mov r7, #4d
 ret
ret_5:
 mov r7, #5d
 ret
ret_6:
 mov r7, #6d
 ret
ret_7:
 mov r7, #7d
 ret
ret_8:
 mov r7, #8d
 ret
ret_9:
 mov r7, #9d
 ret
ret_0:
 mov r7, #0d
 ret

Ouch.. Pas très élégant comme solution, ça je vous l'accorde. Mais elle fait l'affaire. Tout comme pour la fonction display, le résultat sera stocké dans le registre R7. Ca nous permettra de les appeler de façon successive ce qui rend la boucle principale plus courte.

main:
 call read_keypad
 call display
 call delay
 sjmp main

Vous avez probablement remarqué la présence d'une routine "delay" dans le code ci-haut. C'est une routine de temporisation logicielle, elle ne fait que décrémenter certains registres pour occuper le processeur :

;Fonction de délai
;Un délai assez court pour que le clavier soit responsif
;Mais assez long pour que le CPU ne souffre pas trop
delay:
 push 0
 push 1
 push 2
 mov r0, #127
 mov r1, #127
 mov r2, #1
boucle:
 djnz r0, boucle
 djnz r1, boucle
 djnz r2, boucle
 pop 2
 pop 1
 pop 0
 ret

Code final

Le code final peut être trouvé sur mon compte github : https://github.com/Scroph/8051/tree/master/keypad. J'espère que vous aurez appris un truc ou deux, on se reverra dans un autre billet o/

Commentaires

Posts les plus consultés de ce blog

Porting a Golang and Rust CLI tool to D

Decrypting .eslock files

Résoudre le problème de téléversement avec Arduino Nano sous Windows 7