Communication FPGA / Port Série

Le but de la séance est de réaliser un dialogue avec la carte FPGA. Les sorties consisteront en un affichage sur les LEDs ou sur l’afficheur, les entrées en lecture d’interrupteurs ou de boutons-poussoirs.

Ce périphérique communique via un port série (USB). Sous Linux1, il est automatiquement associé à un fichier spécial /dev/ttyUSBx (avec x un nombre, ici probablement 0). Une fois ce fichier ouvert, la communication s’effectue simplement par lectures/écritures.

Pour que cela fonctionne, il faut néanmoins s’assurer que les mécanismes de mise en tampons (buffers) soient désactivés. L’appel setvbuf(fd, NULL, _IONBF, 0); de la dernière fois fonctionne ici aussi. Cependant pour des besoins pédagogiques, nous utiliserons les appels systèmes POSIX (pages 2 du manuel, par exemple man 2 read).

Les principales différences des appels systèmes avec les E/S standards sont :

  • Les fichiers sont directement décrits par leur index dans la table des descripteurs ouvert du processus. C’est donc un simple nombre (int) et non un FILE *.
  • Les fonctions ne commencent pas par f (fopen => open, fread => read, …).
  • Le descripteur est toujours premier le paramètre (c’était le dernier de fread et fwrite).
  • Il n’existe pas de fonction équivalentes à feof, read retourne 0 lorsque la fin de fichier est atteinte, -1 en cas d’erreur.
  • read et write n’utilisent qu’un nombre pour représenter la taille (équivalent du nombre * taille).

Protocole

La communication avec la carte FPGA utilise un protocole simple :

Écriture
Pour écrire un octet, il faut envoyer la séquence de trois octets suivante : W action valeur

Les actions (hexadécimal) disponibles sont les suivantes :

action  
10 envoyer un octet vers les LEDs (chaque bit représente une LED).
21 demande l’initialisation de l’afficheur (la valeur est ignorée mais doit être présente).
30 envoi d’un caractère vers l’afficheur (2 lignes de 16 caractères).
20 rafraîchissement de l’afficheur (la valeur est ignorée. Qui me dit a quoi ça sert ?)
Lecture
Pour lire l’état des différents boutons, il faut envoyer la séquence de deux octets suivante : R query, puis lire un octet.
query  
4 État des interrupteurs (bit 0, 1, 2, 3)
8 État des boutons-poussoirs Est, Nord, Ouest et Centre (respectivement bit 0, 1, 2, 3). Un bogue dans le firmware nous empêche de récupérer les positions.

Travail

On récupère l’archive fpga.tar.gz. Pour le faire en ligne de commandes référez vous au td précédent.

fpga.h
prototypes des fonctions de manipulation de la carte FPGA ainsi que les constantes utiles.
fpga.c
contiendra les fonctions de manipulation de la carte FPGA.
main.c
votre exemple.
  1. Remplir les quelques constantes manquantes dans fpga.h. Quand vaut il mieux faire des macros ou des énumérations ?
  2. Dans main.c, ouvrir/fermer le port série.
  3. Dans une boucle infinie, envoyez un octet sur les LEDs et faire une pause (usleep). Pour commencer on fera cycler un simple compteur. Avant de tester, on enregistrera une fonction qui attrapera le ‘Ctrl+C’. Il s’agit en fait du signal SIGTERM : exit_if(signal(SIGTERM, trap_sigterm) == SIG_ERR, NULL)2.
  4. On décide que le dernier interrupteur (SW3) contrôlera l’arrêt du programme. Dans la boucle lire l’état du bouton, l’afficher (%hhx hexadécimal de sHort sHort). Lorsqu’il passe a 1 arrêter la boucle. Si on relance le programme, il s’arrête de suite. On décide donc de lire avant la boucle l’état de SW3, substituez le code déjà écrit par la fonction read_status. Que devient la condition d’arrêt ?
  5. Rajouter un paramètre à read_status qui déterminera si on lit interrupteurs ou les boutons. Quel est le type de ce nouveau paramètre ? Réfléchir à la question 1. Affichez l’état des boutons-poussoirs en plus de l’état des interrupteurs. Servez vous des boutons Est et Ouest pour ralentir ou accélérer l’affichage void set_speed(int *speed, unsigned char btns) (pensez a asservir la vitesse dans l’intervalle [SLOWEST..FASTEST]).
  6. Faire la même factorisation pour la fonction write_status. Pensez à déplacer ces deux fonctions dans fpga.c et n’oubliez pas d’ajouter les prototypes dans fpga.h. Profitez en pour faire les fonctions void set_leds(int fd, unsigned char c), void write_char(int fd, char c), void init_display(int fd) et void refresh_display(int fd).
  7. Transformer notre bête compteur en chenillard. En faire une fonction. Comment maintenir l’état d’un appel sur l’autre.
  8. En plus d’afficher l’état des boutons sur l’écran, affichez les sur l’afficheur. - Faire la fonction qui envoie un caractère à l’afficheur.
    • Faire la fonction qui envoie une chaine à l’afficheur.
    • Faire une fonction qui reçoit l’état des boutons/interrupteurs et les envoie à l’afficheur. Vous pourrez vous servir de snprintf. Rajoutez ce qu’il faut pour que cela s’affiche bien. Bonus: modifiez print_bits pour faire sprint_bits, puis réécrire print_bits. Enfin affichez l’état des bits sur l’afficheur (à la place de l’hexadécimal).
  9. Faire la fonction alterner, qui affiche tantôt les LEDs paires tantôt les impaires. Le premier interrupteur (SW0) contrôlera maintenant l’animation (chenillard/alterner).
  10. On veut maintenant que le bouton-poussoir centre permette de changer d’animation (les faire cycler par exemple). On veut que seule la pression sur le bouton change l’animation, on va donc attendre que le bouton soit relâché avant de continuer. Réaliser la fonction suivante void wait_for(int fd, unsigned char bitset, unsigned char value), ou bitset représentera les bits à surveiller et value la valeur escompté pour ces bits. On pourra s’inspirer de la condition d’arrêt de la boucle.
  11. Dans les animation remplacez votre switch par un tableau de pointeur de fonction. La syntaxe est étrange, si l’on veut déclarer une variable (paramètre) fun représentant une fonction prenant un entier et ne retournant rien : void (*fun)(int);. L’affectation se passe de manière triviale : fun = le_nom_d_une_fonction;. L’appel se fait exactement comme pour une fonction : fun(42);. Pour finir, la syntaxe pour déclarer un tableau de pointeur de fonction prenant un int et ne retournant rien : void (*funs[])(int) = { une_fonction, une_autre_fonction };. Je vous l’accorde la syntaxe est passablement indigeste.
  1. Sous MacOS ce n’est pas le cas. 

  2. Par opposition a atexit, la fonction enregistrée doit avoir une signature de la forme void fun(int)