« SE4Binome2023-3 » : différence entre les versions
Ligne 164 : | Ligne 164 : | ||
Ensuite à l'aide d'un switch nous décodons l'instructions reçu lors d'une interruption SPI. | Ensuite à l'aide d'un switch nous décodons l'instructions reçu lors d'une interruption SPI. | ||
< | <syntaxhighlight lang="c" line> | ||
ISR(SPI_STC_vect) \{ | ISR(SPI_STC_vect) \{ | ||
cli(); | cli(); | ||
Ligne 193 : | Ligne 193 : | ||
sei(); | sei(); | ||
\} | \} | ||
<\ | <\syntaxhighlight> | ||
==Primitive carte mère== | ==Primitive carte mère== |
Version du 15 janvier 2024 à 19:09
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.
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
les fonctions pour les leds et pour le reste de notre projet se trouvent dans le fichier inout.c afin de ne pas surcharger le fichier keyboardHost.
Nous souhaitons ensuite communiquer avec la carte mère par le bus SPI. Nous vérifions donc que nous réussissons à lancer une interruption spi sur la carte fille. Nous vérifions également que nore carte fille peut envoyer une interruption par la ligne d'interruption du connecteur HE10. Cette ligne servira plus tard à indiquer qu'il y a des touches dans le buffer et qu'il faut que la carte mère interroge notre carte dès que possible.
Ensuite à l'aide d'un switch nous décodons l'instructions reçu lors d'une interruption SPI.
<syntaxhighlight lang="c" line> ISR(SPI_STC_vect) \{
cli(); uint8_t receivedData = SPDR; switch (receivedData){ case 0x00: SPDR = 0x01; break; case 0x01: if(!sizeSendFlag){ SPDR = sizeBuffer(); sizeSendFlag = 1; break; } SPDR = dequeue(); if(!sizeBuffer()){ setLowOutput(&PORTB, INT); sizeSendFlag = 0; } break; default: SPDR = dequeue(); if(!sizeBuffer()){ setLowOutput(&PORTB, INT); } break; } sei();
\} <\syntaxhighlight>
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.