« SE4Binome2023-3 » : différence entre les versions
Ligne 258 : | Ligne 258 : | ||
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. | 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== | ===keyboard.c=== | ||
Ce fichier quant à lui est spécifique à notre périphérique et se base sur device.c. | 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. | 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. |
Version du 15 janvier 2024 à 14:16
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.
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).
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.
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.
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. 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 KeyboardHost
:
void processKeyUp(uint8_t KeyCode){
clearLeds();
}
void processKeyDown(uint8_t KeyCode, uint8_t Modifier){
char PressedKey = 0;
if ((KeyCode >= HID_KEYBOARD_SC_A) && (KeyCode <= HID_KEYBOARD_SC_Z))
{
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);
}
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).
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).
device.c
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
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.