« SE4Binome2023-3 » : différence entre les versions

De projets-se.plil.fr
Aller à la navigation Aller à la recherche
Ligne 144 : Ligne 144 :
==Programmation de la carte==
==Programmation de la carte==


A l'aide de la LUFA et de leur démo <code>KeyboardHost</code>, nous allons tenter de reconnaître les touches tapés sur un clavier. Pour vérifier cela et débuggé notre code, nous avons ajouté 8 LEDs de tests destinés à afficher la valeur binaire des caractère ASCII correspondant. Après quelques problèmes de conversion entre qwerty et azerty, nous pouvons maintenant recevoir sur notre carte les caractères classiques (a à z, 0 à 9, espace, entrée, tabulation...) et nous pouvons même passer les lettres en majuscule à l'aide de la touche verrouille maj. Voici ci-dessous une parti du code de <code>KeyboardHost</code> :
A l'aide de la LUFA et de leur démo <code>KeyboardHost</code>, nous allons tenter de reconnaître les touches tapés sur un clavier. Pour vérifier cela et débuggé notre code, nous avons ajouté 8 LEDs de tests destinés à afficher la valeur binaire des caractère ASCII correspondant.


<syntaxhighlight lang="c" line>
Pour ce faire dans le fichier de démo lufa, nous avons implémenté la conversion des keycodes en caractères ascii. Il a été plus simple pour nous de convertion en utilisant le layout anglais qwerty. Il a donc fallu effectué une conversion également pour certaines touches afin de passer de qwerty à azerty. Pour finir nous considérons que de base les clés sont celles du bas lowerKey abcd. Si un événement vient mettre le drapeau upper (mode majuscule) à 1 alors nous passons sur les upperkey ABCD.
void processKeyUp(uint8_t KeyCode){
clearLeds();
}


void processKeyDown(uint8_t KeyCode, uint8_t Modifier){
A l'aide de son keycode nous avons codé la mise en place du mode majuscule grâce au capslock.
Nous pouvons maintenant recevoir sur notre carte les caractères classiques (a à z, 0 à 9, espace, entrée, tabulation...)
char PressedKey = 0;


if ((KeyCode >= HID_KEYBOARD_SC_A) && (KeyCode <= HID_KEYBOARD_SC_Z))
Après quelques complications sur la recherche des keycodes (vive la lufa !) nous pouvons également désormais activer le mode majuscule avec les touches shift droite et gauche. Nous avons également rajouté la possibilité de taper les caractères spéciaux associés aux nombres et à droite du clavier. Malheureusement, les caractères ¨, µ, £ et § n'étant pas présent dans la table ASCII, ils ne seront pas disponible sur notre clavier.
{
PressedKey = (KeyCode - HID_KEYBOARD_SC_A) + 'A';
}
else if ((KeyCode >= HID_KEYBOARD_SC_1_AND_EXCLAMATION) &
(KeyCode  < HID_KEYBOARD_SC_0_AND_CLOSING_PARENTHESIS))
{
PressedKey = (KeyCode - HID_KEYBOARD_SC_1_AND_EXCLAMATION) + '1';
}
else if (KeyCode == HID_KEYBOARD_SC_0_AND_CLOSING_PARENTHESIS)
{
PressedKey = '0';
}
else if (KeyCode == HID_KEYBOARD_SC_SPACE)
{
PressedKey = ' ';
}
else if (KeyCode == HID_KEYBOARD_SC_ENTER)
{
PressedKey = '\n';
}
else if (KeyCode == HID_KEYBOARD_SC_TAB){
PressedKey = '\t';
}
else if (KeyCode == HID_KEYBOARD_SC_DELETE){
PressedKey = 0x7f;
}
else if (KeyCode == HID_KEYBOARD_SC_SEMICOLON_AND_COLON)
{
PressedKey = 'M';
}
else if (KeyCode == HID_KEYBOARD_SC_CAPS_LOCK)
{
if(upper){
upper = 0;
}else {
upper = 1;
}
}
else if (Modifier & (1 << 1)){
PressedKey = 0xff;
}
// Qwerty to Azerty
switch (PressedKey) {
case 'A':
PressedKey = 'Q';
break;
case 'Z':
PressedKey = 'W';
break;
case 'Q':
PressedKey = 'A';
break;
case 'W':
PressedKey = 'Z';
break;
case 'M':
PressedKey = ',';
break;
default:
break;
}
// Maj
if (upper == 0){
if((KeyCode >= HID_KEYBOARD_SC_A) && (KeyCode <= HID_KEYBOARD_SC_Z)){
PressedKey = PressedKey + 0x20;
}
}
printLeds(PressedKey);
}
</syntaxhighlight>


Nous pouvons également désormais activer le mode majuscule avec les touches shift droite et gauche. Nous avons également rajouté la possibilité de taper les caractères spéciaux associés aux nombres et à droite du clavier. Malheureusement, les caractères ¨, µ, £ et § n'étant pas présent dans la table ASCII, ils ne seront pas disponible sur notre clavier. Les accents tels que é, è, à, ù et ç ont été remplacé par la lettre de base (e, a, u, c).
Grâce aux leds et à convertisseur ascii nous vérifions que la reconnaissance des touches est bonne.
 
vidéo leds


Nous allons maintenant devoir retenir les dernières touches tapées dans un buffer. En effet, en cas de freeze de la machine, il est tout de même nécessaire de se rappeler de la suite de touche tapée par l'utilisateur. Pour ce faire, nous avons créé une structure <code>buffer_t</code>, contenant un tableau de touches <code>data</code>, l'index de la dernière touche tapée <code>tail</code> et l'index de la première touche tapée <code>head</code>. A chaque appel au clavier par la carte mère, nous envoyons la touche présente à l'index <code>head</code> et nous la décalons de 1, en suivant en quelque sorte la technique FIFO. De cette manière, en ayant un tableau assez grand, on est capable de retenir un bon nombre de touches tapées par l'utilisateur et de les retenir en cas de problème.
Nous allons maintenant devoir retenir les dernières touches tapées dans un buffer. En effet, en cas de freeze de la machine, il est tout de même nécessaire de se rappeler de la suite de touche tapée par l'utilisateur. Pour ce faire, nous avons créé une structure <code>buffer_t</code>, contenant un tableau de touches <code>data</code>, l'index de la dernière touche tapée <code>tail</code> et l'index de la première touche tapée <code>head</code>. A chaque appel au clavier par la carte mère, nous envoyons la touche présente à l'index <code>head</code> et nous la décalons de 1, en suivant en quelque sorte la technique FIFO. De cette manière, en ayant un tableau assez grand, on est capable de retenir un bon nombre de touches tapées par l'utilisateur et de les retenir en cas de problème.

Version du 15 janvier 2024 à 17:55

Ordonnanceur / SE

Nous avons soudé l'ensemble des composants du shield arduino et avons testé son bon fonctionnement à l'aide de l'application Arduino. Voici ci-dessous une vidéo du clignotement des LEDs.

Nous testerons notre ordonnanceur dans un premier temps avec 3 tâches représentées par le clignotement d'une Led. Nous avons débuté par un premier programme pour vérifier le clignotement des Leds et faire fonctionner l'ISR nue. Après vérification que tout fonctionne bien, nous essayons de sauvegarder le contexte et de le reconstituer.


Après moulte tentative, nous sommes parvenus à réaliser un ordonnancuer fonctionelle qui a comme tâches le clignotement de 3 LEDs. Cependant, ce multi-tasking engendre un ralentissement des temps voulu, il faut donc géré cette partie afin de faire correspondre les délais du cahier des charges et ceux de notre réalisation.Ci-dessous, voici le code permettant de gérer l'ordonnancement :

struct task_t{
    void (*addr)(void);
    uint16_t sp;
    int state;
};

void initTask(int taskId){
    int save = SP;
    SP = task[taskId].sp;
    uint16_t address = (uint16_t)task[taskId].addr;
    asm volatile("push %0" : : "r" (address & 0x00ff) );
    asm volatile("push %0" : : "r" ((address & 0xff00)>>8) );
    SAVE_REGISTER();
    task[taskId].sp = SP;
    SP = save;
}

void scheduler (){
    currentTask ++;
    if(currentTask == NB_TASK) currentTask = 0;
}

ISR(TIMER1_COMPA_vect,ISR_NAKED){
    // Sauvegarde du contexte de la tâche interrompue
    SAVE_REGISTER();
    task[currentTask].sp = SP;
    // Appel à l'ordonnanceur
    scheduler();
    // Récupération du contexte de la tâche ré-activée
    SP = task[currentTask].sp; 
    RESTORE_REGISTER();
    
    asm volatile("reti");
}

int main(){
    setup();
    SP = task[currentTask].sp;
    task0();
}

Le main réalise d'abord le setup des LEDs, des interruptions, des tâches (leur addresse, leur pointeur de pile par exemple: 0x0600, leur état) ... puis récupère le pointeur de pile et lance manuellement la première tâche (task0). A chaque interruption de l'ISR, on sauvegarde le contexte de la tâche en cours, on choisit la prochaine tâche (task0 --> task1) et on restaure le contexte précédent associé à cette tâche. A la fin de l'interruption, la tâche voulue se lance et on attend la prochaine interruption pour continuer l'algorithme.

Cependant, vous pouvez voir ci-dessous que les tâches sont 3 fois plus longues que voulues. En effet, chaque delay est multipplié par le nombre de tâches à réaliser (ici, 3). Nous devons donc réfléchir à une méthode permettant d'endormir les tâches non sollicitées par l'ordonnanceur.

Afin d'endormir les tâches, nous écrivons une fonction wait. Désormais, chaque structure task_t inclut son état (endormi ou éveillé) et une structure sleep_t qui contient deux paramètres. Ces deux paramètres sont la raison de l'endormissement de la tâche et un compteur. Le but de notre fonction est d'attendre la fin du compteur pour changer son état et réinitialiser le compteur.

Ordonnanceur.c :

void wait_ms(uint16_t ms){
    cli();
    task[currentTask].state = SLEEP;
    sleep_t sleep;
    sleep.reason = DELAY_SLEEPING;
    sleep.data = ms;
    task[currentTask].sleep = sleep;
    TCNT1 = 0;
    sei();
    TIMER1_COMPA_vect();
}

Ordonnanceur.h :

#define SLEEP 0
#define AWAKE 1

#define DELAY_SLEEPING 0

typedef struct sleep_t{
    uint8_t reason;
    uint16_t data;
}sleep_t;

typedef struct task_t{
    void (*addr)(void);
    uint16_t sp;
    uint8_t state;
    sleep_t sleep;
}task_t;

Voici donc ci-dessous le résultat en contrôlant l'état des LEDs (ce dernier ressemble ostensiblement à la vidéo précédente, mais cette fois ci, les temps voulus sont exacts)

Carte FPGA / VHDL

Ayant voulu nous concentrer sur notre carte électronique clavier afin d'obtenir un périphérique (quasiment) parfaitement opérationnel, nous ne nous sommes pas attardés sur la carte FPGA en VHDL et ne l'avons pas entamé.

Carte électronique numérique

L'ensemble de nos fichiers sont disponible dans notre git.

Type de carte choisi

Nous avons choisi la carte fille gérant le clavier. Nous allons donc souder et programmer une carte sur laquelle sera branché un clavier en USB.

Schematic et routage de la carte

Nous avons réalisé le schematic de notre carte en nous basant sur ce projet. Nous avons pris la décision d'utiliser le même port USB femelle pour brancher le clavier et programmer la carte avec l'ordinateur (plutôt qu'un femelle pour le clavier et un mâle pour la programmation).

Schematic

Après avoir assigné les correctes footprint à chacun des éléments, nous avons commencé le routage de notre carte fille. Nous avons fait le choix de connecter chaque pin seul à un test point, ce qui nous a valu en premier lieu de sacré problème quant à l'organisation de l'espace que prenaient ces test point sur le PCB ! Après vérifications auprès de Mrs Boé et Redon, nous avons supprimé certains test points prenant trop de place et avons déplacer certains composants (rapprochage du crystal et des condensateur du microprocesseur, perfectionnement de la carte, ajout de vias pour répartir le transfert de courant entre les deux faces pour la masse...) nous avons généré les deux fichiers nécessaires à l'impression par un prestataire extérieur et avons, en attendant sa réception, commencé à travailler notre ordonnanceur.

Routage

Soudage de la carte

Nous avons reçu notre carte en 5 exemplaires le 24/10, et avons décidé de continuer l'ordonnanceur avant de commencer la soudure.

En ce grand jour du 15/11, nous avons enfin reçu notre AT90USB647 et pourrons commencer la soudure à la prochaine séance ! Au cours du premier test, notre carte n'était pas reconnu et nous avions des valeurs de tensions incohérentes en différents points. Après different soudage-resoudage de nombreux composants, nous avons compris que ceci venait en fait des boutons. En effet, nous avons choisi un composant incorrect sur notre schematic ce qui a entrainé des erreurs sur notre routage, ce qui engendre le fait que le port reset et le port HWB était constamment en court-circuit avec le ground (ceci explique cela...). Pour résoudre le problème, nous avons resoudé nos boutons avec une rotation de 90° et avons mis à jour notre schematic et le routage qui va avec. Voici donc la deuxième version des deux boutons (ici on prend l'exemple du reset) :

Nous avons commencé les premiers tests le 21/11et ces derniers furent concluant. Notre carte est reconnu par le terminal avec la commande lsusb et on a pu faire clignoter nos LEDs de test.

Carte soudée et alimentée par HE10

Programmation de la carte

A l'aide de la LUFA et de leur démo KeyboardHost, nous allons tenter de reconnaître les touches tapés sur un clavier. Pour vérifier cela et débuggé notre code, nous avons ajouté 8 LEDs de tests destinés à afficher la valeur binaire des caractère ASCII correspondant.

Pour ce faire dans le fichier de démo lufa, nous avons implémenté la conversion des keycodes en caractères ascii. Il a été plus simple pour nous de convertion en utilisant le layout anglais qwerty. Il a donc fallu effectué une conversion également pour certaines touches afin de passer de qwerty à azerty. Pour finir nous considérons que de base les clés sont celles du bas lowerKey abcd. Si un événement vient mettre le drapeau upper (mode majuscule) à 1 alors nous passons sur les upperkey ABCD.

A l'aide de son keycode nous avons codé la mise en place du mode majuscule grâce au capslock. Nous pouvons maintenant recevoir sur notre carte les caractères classiques (a à z, 0 à 9, espace, entrée, tabulation...)

Après quelques complications sur la recherche des keycodes (vive la lufa !) nous pouvons également désormais activer le mode majuscule avec les touches shift droite et gauche. Nous avons également rajouté la possibilité de taper les caractères spéciaux associés aux nombres et à droite du clavier. Malheureusement, les caractères ¨, µ, £ et § n'étant pas présent dans la table ASCII, ils ne seront pas disponible sur notre clavier.

Grâce aux leds et à convertisseur ascii nous vérifions que la reconnaissance des touches est bonne.

vidéo leds

Nous allons maintenant devoir retenir les dernières touches tapées dans un buffer. En effet, en cas de freeze de la machine, il est tout de même nécessaire de se rappeler de la suite de touche tapée par l'utilisateur. Pour ce faire, nous avons créé une structure buffer_t, contenant un tableau de touches data, l'index de la dernière touche tapée tail et l'index de la première touche tapée head. A chaque appel au clavier par la carte mère, nous envoyons la touche présente à l'index head et nous la décalons de 1, en suivant en quelque sorte la technique FIFO. De cette manière, en ayant un tableau assez grand, on est capable de retenir un bon nombre de touches tapées par l'utilisateur et de les retenir en cas de problème.

Dans la continuité du TP, une interruption est envoyé à la carte mère et le calcul du nombre de touche stocké se fait par la primitive sizeBuffer(). Pour vérifier le bon fonctionnement de cette méthode, nous envoyons une interruption faisant clignoter la LED du connecteur HE10 relié à notre carte clavier uniquemet lorsque le buffer atteint une taille égal 3.

Primitive carte mère

Afin de simplifier l'utilisation de notre carte fille nous avons codé diférrentes primitives pour la carte mère elles se retrouvent toutes dans la librairie libdevice que nous avons testé sur notre carte mère à nous. Pour le groupe s'occuppant de la carte mère il suffira donc d'inclure cette librairie et d'utiliser les fonctions à sa guise (attention par contre à adapter les broches dans le .h).

note : la librairie est compilé par son propre makefile.

device.c/.h

Ce fichier n'est pas spécifique à notre carte et peut être utilisé par toutes les autres cartes filles.

il comporte tout d'abord les fonctions de gestion de spi:

  • initSPI: initialise la transmission SPI. Attention à adapter les ports d'entrées et de sortie à la carte mère utilisée.
  • selectSlaveSPI / unselectSlaveSPI: sélectionne/désélectionne le périphérique à qui transmettre l'info.
  • transferSPI: envoie et récupère l'information sur le bus SPI.

Par la suite nous ajoutons une seconde couche plus spécifique à notre application. Elle repose sur le typage de chaque composant exemple pour notre clavier le type est 0x01:

  • initConnectorsList: crée une liste nous permettant de sauvegarder des informations sur les connecteurs (leurs brochese et leur périphérique attaché). De plus cette fonction vient sonder chaque He10 afin de connaître le périphérique attaché permettant d'être plus modulaire leur de la connexion.
  • indexDevice: renvoie l'index du périphérique recherché.
  • transferDataTo: se sert de indexDevice et des fonctions spi pour envoyer une données à un périphérique en particulier.
  • checkInterrupt: vient sonder un périphérique pour savoir si sa ligne d'interruption est à l'état haut.

Pour nous simplifier la tâche on code des fonctions utilent comme getDeviceList qui renvoie une liste du type de chaque périphérique et initDevice qui initialise le SPI et la liste des périphériques.

keyboard.c/.h

Ce fichier quant à lui est spécifique à notre périphérique et se base sur device.c.

Il permet à la carte mère de récupérer une clé à l'aide de grabKey. La récupération de plusieurs clés et aussi possible grâce à grabKeys pour se faire nous récupérons d'abord la taille du tableau comprenant les clés pour allouer de l'espace et savoir le nombre de requète spi à effectuer. Cela est fait grâce à bufferSize.