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 :
int
) et non un FILE *
.f
(fopen
=> open
, fread
=> read
, …).fread
et fwrite
).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).La communication avec la carte FPGA utilise un protocole simple :
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 ?) |
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. |
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
. Quand vaut il mieux faire des macros ou des énumérations ?main.c
, ouvrir/fermer le port série.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.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 ?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
]).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)
.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).alterner
, qui affiche tantôt les LEDs paires tantôt les impaires. Le premier interrupteur (SW0
) contrôlera maintenant l’animation (chenillard
/alterner
).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.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.