« SE4Binome2025-3 » : différence entre les versions
Aucun résumé des modifications |
m (→Conclusion) |
||
| (26 versions intermédiaires par le même utilisateur non affichées) | |||
| Ligne 8 : | Ligne 8 : | ||
Nous avons tout d'abord commencé par le routage d'un shield Arduino en guise de carte de test pour les cartes filles, dans le cas où la carte mère ne serait pas opérationnelle. | Nous avons tout d'abord commencé par le routage d'un shield Arduino en guise de carte de test pour les cartes filles, dans le cas où la carte mère ne serait pas opérationnelle. | ||
=== Schématique === | |||
[[Fichier:Screenshot 2025-11-14 13-52-53.png|centré|sans_cadre|729x729px]] | |||
En utilisant un modèle donné nous avons fait cette schématique pour notre shield. | |||
=== Routage === | === Routage === | ||
Nous avons | Nous avons routé le shield comme ceci | ||
[[Fichier:Carte shield.png|alt=shield| | [[Fichier:Carte shield.png|alt=shield|sans_cadre|Image du routage du shield |769x769px|centré]] | ||
Notre carte ressemblant à celle du [https://projets-se.plil.fr/mediawiki/index.php/SE4Binome2025-1 binôme 1], nous utiliserons par conséquent la leur. | Notre carte ressemblant à celle du [https://projets-se.plil.fr/mediawiki/index.php/SE4Binome2025-1 binôme 1], nous utiliserons par conséquent la leur. | ||
== Partie Carte Clavier == | == Partie Carte Clavier == | ||
=== Mise en place === | === Mise en place === | ||
Nous avons d'abord eu à définir le projet pour le clavier et nous avons décidé de réaliser entièrement notre carte clavier. Nous avons choisi de créer un clavier de 30 touches permettant d'écrire 26 caractères par mode avec 3 modes différents. Les 4 touches restantes serviront de touches Supprimer, Espace, Maj et Symbole. Ces touches seront communes aux 3 modes du clavier. Nous allons profiter du fait d'avoir attribué une diode à chaque touche pour pouvoir en presser deux en même temps. Comme le fonctionnement de la touche Shift, il faut garder la touche enfoncée pour changer de mode. | Nous avons d'abord eu à définir le projet pour le clavier et nous avons décidé de réaliser entièrement notre carte clavier de manière matricielle. | ||
Nous avons choisi de créer un clavier de 30 touches permettant d'écrire 26 caractères par mode avec 3 modes différents. Les 4 touches restantes serviront de touches Supprimer, Espace, Maj et Symbole. Ces touches seront communes aux 3 modes du clavier. Nous allons profiter du fait d'avoir attribué une diode à chaque touche pour pouvoir en presser deux en même temps. Comme le fonctionnement de la touche Shift, il faut garder la touche enfoncée pour changer de mode. | |||
MAJ enfoncée pour les majuscules et SYMBOLE pour les chiffres et symboles. En appuyant sur MAJ et SYMBOLE simultanément on pourra activer la touche Entrée. | |||
=== Disposition des touches === | === Disposition des touches === | ||
La disposition est trouvable dans le fichier [https://gitea.plil.fr/mgourves/SE4-PICO-B3/src/branch/main/bind_clavier.ods bind_clavier] trouvable sur le git. | La disposition est trouvable dans le fichier [https://gitea.plil.fr/mgourves/SE4-PICO-B3/src/branch/main/bind_clavier.ods bind_clavier] trouvable sur le git. | ||
{| class="wikitable" | {| class="wikitable" | ||
|+Mode 1: Minuscule | |+Mode 1: Minuscule | ||
| Ligne 43 : | Ligne 47 : | ||
|j | |j | ||
|k | |k | ||
| | |i | ||
|- | |- | ||
|m | |m | ||
| Ligne 142 : | Ligne 146 : | ||
=== Kicad === | === Kicad === | ||
Nous avons donc mis en place cette matrice. | Nous avons donc mis en place cette matrice sur notre carte [https://gitea.plil.fr/mgourves/SE4-PICO-B3/src/branch/main/picoClavier/picoClavier.kicad_pro KiCad] à l'aide d'un atmega328 programmable par un ISP. | ||
[[Fichier:Carte2.png|centré|sans_cadre|1015x1015px]] | [[Fichier:Carte2.png|centré|sans_cadre|1015x1015px]] | ||
Les | Les lignes et colonnes de touches sont donc reliées aux broches des ports C et D et chacun des boutons est relié à une diode, ce qui nous permettra de taper sur plusieurs touches en même temps, nous permettant de changer les modes de fonctionnement du clavier entre minuscule, majuscule et symbole. | ||
On place également des résistances de pull up sur les colonnes pour stabiliser la détection et lire l'état bas plutôt que l'état haut. | |||
Notre | Pour s'assurer d'être dans le bon mode de clavier, nous avons placé 3 LEDs qui indiquent chacune un mode différent. | ||
Notre carte sera relié à la carte mère via un connecteur 1*8 placé à l'extremité supérieure de la carte. | |||
| Ligne 159 : | Ligne 165 : | ||
''Images plus en détails de la carte :'' | ''Images plus en détails de la carte :'' | ||
[[Fichier:Matrice touche.png|droite|sans_cadre|469x469px]][[Fichier:Partie avr.png|centré|sans_cadre|445x445px]] | [[Fichier:Matrice touche.png|droite|sans_cadre|469x469px]][[Fichier:Partie avr.png|centré|sans_cadre|445x445px]] | ||
=== Code clavier === | |||
Le code C du clavier peut être trouvé sur le [https://gitea.plil.fr/mgourves/SE4-PICO-B3/src/branch/main/codeClavier/clavier.c git]. | |||
==== Variables et constantes ==== | |||
Tout d'abord nous avons initialisé les différentes librairies et les constantes globales utiles au programme. | |||
Nous avons donc 6 colonnes et 5 lignes, on retient aussi les "coordonnées" des touches MAJ et SYMBOLE. | |||
La constante d'anti rebond permet de ralentir le programme pour éviter que plusieurs caractères soient envoyés avec un seul appui.<syntaxhighlight lang="c"> | |||
#define NB_COL 6 | |||
#define NB_ROW 5 | |||
#define NB_MODE 3 | |||
#define ASCII_ENTER 13 | |||
#define MAJ 128 | |||
#define MAJ_ROW 4 | |||
#define MAJ_COL 4 | |||
#define SYMBOLE 129 //pour dépasser la table ascii "normale" | |||
#define SYMBOLE_ROW 4 | |||
#define SYMBOLE_COL 5 | |||
#define MAX_PRESSED_KEYS 2 | |||
#define ANTI_REBOND_mS 10 //10ms pour éviter le "spam" d'envoi | |||
</syntaxhighlight>Ensuite, nous avons déclaré les variables globales liées aux ports utilisés par les touches du clavier. On mappe les colonnes et les lignes avec les pins correspondants.<syntaxhighlight lang="c" start="18"> | |||
uint8_t BIT_Row[NB_ROW] = {PD1,PD0,PC5,PC4,PC3}; | |||
uint8_t BIT_Col[NB_COL] = {/*...*/}; | |||
volatile uint8_t *DDR_Row[NB_ROW] = {&DDRD,&DDRD,&DDRC,&DDRC,&DDRC}; | |||
volatile uint8_t *DDR_Col[NB_COL] = {/*...*/}; | |||
volatile uint8_t *PORT_Row[NB_ROW] = {&PORTD,/*...*/}; | |||
volatile uint8_t *PORT_Col[NB_COL] = {/*...*/}; | |||
volatile uint8_t *PIN_Col[NB_COL] = {&PINC,&PINC,&PINC,&PIND,&PIND,&PIND}; | |||
</syntaxhighlight>Nous avons également déclaré une matrice 3D représentant notre clavier avec les codes ASCII décimaux correspondant à chaque touche. | |||
Les constantes MAJ et SYMBOLE étant déclarées avec des valeurs dépassant la table ASCII basique pour éviter les conflits. <syntaxhighlight lang="c" start="28"> | |||
int bind[NB_MODE][NB_ROW][NB_COL]={ //ASCII décimal | |||
{ {97,98,99,100,101,102}, | |||
{103,104,105,106,107,108}, | |||
{109,110,111,112,113,114}, | |||
{115,116,117,118,119,120}, | |||
{121,122,127,32,MAJ,SYMBOLE} } , //mode défaut | |||
{ {...} } , // Maj | |||
{ {...} } ; //Symbole | |||
</syntaxhighlight> | |||
==== Fonctions ==== | |||
Les ports doivent êtres initialisés, les ports correspondant aux colonnes seront en entrée pour détecter un appui et les lignes en sortie pour pouvoir les activer et désactiver pour la lecture au cas par cas. | |||
Pour le SPI, on le met en maître et on assigne MOSI,SCK et SS en sortie.<syntaxhighlight lang="c" start="46"> | |||
void initIn(void){ | |||
for(int row = 0; row < NB_ROW ; row++){ | |||
*(DDR_Row[row]) |= (1 << BIT_Row[row]); | |||
} | |||
for(int col = 0; col < NB_COL ; col++){ | |||
*(DDR_Col[col]) &= ~(1 << BIT_Col[col]); | |||
*(PORT_Col[col]) |= (1 << BIT_Col[col]); | |||
} | |||
} | |||
[...] | |||
void initSPI(void) { | |||
DDRB |= (1 << PB2) | (1 << PB3) | (1 << PB5); | |||
SPCR = (1 << SPE) | (1 << MSTR) | (1 << SPR0); | |||
} | |||
</syntaxhighlight>Notre fonction principale permettant le scan des touches parcourt d'abord chaque ligne en l'activant puis en parcourant chaque colonne pour vérifier si un appui est réalisé. | |||
On y ajoute un délai pour laisser le temps de scanner la touche. | |||
Si on dépasse le nombre de touches appuyées elle s'arrête sinon elle sauvegarde la ligne et la colonne correspondantes à la touche pressée dans le tableau pressed_keys[][] déclaré globalement. | |||
Après le parcours de toute la ligne cette dernière est désactivée. <syntaxhighlight lang="c" start="62"> | |||
void scanTouche(void){ | |||
counter_pressed = 0; | |||
for(uint8_t row = 0 ; row < NB_ROW; row++ ){ | |||
*(PORT_Row[row]) &= ~(1 << BIT_Row[row]); | |||
_delay_us(1); | |||
for(uint8_t col = 0; col < NB_COL; col++){ | |||
if(counter_pressed > MAX_PRESSED_KEYS-1){return;} | |||
if(!(*(PIN_Col[col])&(1 << BIT_Col[col]))){ | |||
pressed_keys[counter_pressed][0] = row; | |||
pressed_keys[counter_pressed][1] = col; | |||
counter_pressed++; | |||
} | |||
} | |||
*(PORT_Row[row]) |= (1 <<(BIT_Row[row])); | |||
} | |||
} | |||
</syntaxhighlight> | |||
==== Main ==== | |||
On initialise les variables utiles au programme. | |||
* valeur_touche est la valeur ASCII de la touche, à 0 initialement pour le caractère NULL. | |||
* row_touche est l'index de la ligne, à -1 initialement pour mettre une condition bloquante à l'envoi si seule une touche MAJ ou SYMBOLE est pressée. | |||
* mode est à 0 car le mode minuscule est le mode par défaut. | |||
<syntaxhighlight lang="c"> | |||
int valeur_touche = 0; | |||
int row_touche = -1; | |||
int col_touche = -1; | |||
int mode = 0; | |||
</syntaxhighlight> | |||
==== Boucle while ==== | |||
On utilise d'abord notre fonction pour scanner la touche pressée et on place ses coordonnées dans des variables r et c plus claires.<syntaxhighlight lang="c"> | |||
while(1){ | |||
scanTouche(); | |||
for(int i = 0;i < counter_pressed ; i++){ | |||
int r=pressed_keys[i][0]; | |||
int c=pressed_keys[i][1]; | |||
</syntaxhighlight> | |||
==== Analyse de la touche ==== | |||
Premièrement on vérifie si la touche est SYMBOLE ou MAJ avec les coordonées déclarées. Si c'est le cas on vérifie que l'autre touche spéciale ne soit pas déjà pressée en vérifiant que le mode soit égal à 0, si ce n'est pas le cas on envoie la touche Entrée. Sinon on change le mode pour la valeur correspondante. <syntaxhighlight lang="c"> | |||
if(r == MAJ_ROW && c == MAJ_COL ){ | |||
if(mode == 2) { | |||
valeur_touche = ASCII_ENTER; | |||
sendSPI(valeur_touche); | |||
break; | |||
} | |||
mode = 1; | |||
} | |||
else if(r == SYMBOLE_ROW && c == SYMBOLE_COL ){ | |||
if(mode == 1) { | |||
valeur_touche = ASCII_ENTER; | |||
sendSPI(valeur_touche); | |||
break; | |||
} | |||
mode = 2; | |||
} | |||
</syntaxhighlight>Dans le cas d'une touche "normale" on retient les coordonnées, et on envoie via SPI le caractère correspondant à ces coordonnées dans la matrice si jamais une touche est pressée. On rajoute un délai pour éviter le rebond lors d'un appui.<syntaxhighlight lang="c"> | |||
else { | |||
row_touche = r; | |||
col_touche = c; | |||
} | |||
if(counter_pressed > 0 && row_touche > -1 && col_touche > -1){ | |||
valeur_touche = bind[mode][row_touche][col_touche]; | |||
sendSPI(valeur_touche); | |||
} | |||
if (valeur_touche!=0) {_delay_ms(ANTI_REBOND_mS);} | |||
</syntaxhighlight>On finit ensuite par reset les valeurs des variables pour recommencer le scan.<syntaxhighlight lang="c"> | |||
mode = 0; | |||
row_touche = -1; | |||
col_touche = -1; | |||
valeur_touche = 0; | |||
</syntaxhighlight> | |||
=== Ordonnanceur === | |||
Le code complet de l'ordonnanceur est trouvable ici sur le [https://gitea.plil.fr/mgourves/SE4-PICO-B3/src/branch/main/ordo/ordonnanceur.c git]. Les définitions sont trouvable dans le [https://gitea.plil.fr/mgourves/SE4-PICO-B3/src/branch/main/ordo/ordonnanceur.h .h] associé. | |||
On commence par créer une structure qui représente une tâche, elle contient : | |||
* Un pointeur de la fonction correspondante | |||
* Sa place dans la pile pour permettre de la retrouver | |||
* Son état soit endormi soit reveillé | |||
* Un temps de sommeil | |||
<syntaxhighlight lang="c"> | |||
typedef struct{ | |||
void (*addr_fonction)(void); | |||
uint16_t stackPointer; // Pointeur de pile | |||
bool state; // État (actif vide ou finie) | |||
int sleepTime; | |||
} Process; | |||
</syntaxhighlight>La tâche principale consiste à faire clignoter une led et de donner un temps de sommeil.<syntaxhighlight lang="c"> | |||
void led1(){ | |||
while (1){ | |||
PORTC ^= 1; | |||
sleep(287); | |||
} | |||
} | |||
//[led2,led3...] | |||
</syntaxhighlight>On ajoute également une fonction idle, pour qu'il y ai toujours une tâche reveillée si les autres sont endormies, cela empêche un blocage dans une boucle infinie.<syntaxhighlight lang="c"> | |||
void anti_block(){ //tache vide pour éviter de rester bloqué si toutes les taches sont endormies | |||
while(1); | |||
} | |||
</syntaxhighlight>La fonction sleep() permet de remettre le timer de l'ISR à (F_CPU/1000*periode/diviseur - 1) et ensuite changer le temps de sommeil de la tâche actuelle par un temps donné.<syntaxhighlight lang="c"> | |||
void sleep(int ms_sleep){ | |||
cli(); | |||
TCNT1=max_counter-1; | |||
liste_process[process_actuel].sleepTime = ms_sleep; | |||
sei(); | |||
} | |||
</syntaxhighlight>La fonction change_process() commence par décrementer les temps de sommeil de toutes les tâches en liste ou mettre ce temps à 0 si il est inférieur au paramètre de décrementation. Ensuite il parcourt toutes les tâches reveillées et avec un temps de sommeil supérieur à 0.<syntaxhighlight lang="c"> | |||
void change_process(){ | |||
for (int i=0 ; i<NB_PROCESS ; i++){ | |||
if (liste_process[i].sleepTime - S_DECR< 0){ | |||
liste_process[i].sleepTime = 0; | |||
} | |||
else{ | |||
liste_process[i].sleepTime -= S_DECR; | |||
} | |||
} | |||
do{ | |||
process_actuel++; | |||
if (process_actuel >= NB_PROCESS){ | |||
process_actuel = 0; | |||
} | |||
} | |||
while (liste_process[process_actuel].sleepTime > 0 || !(liste_process[process_actuel].state)); | |||
} | |||
</syntaxhighlight>La fonction ajout_tache() permet d'ajouter dynamiquement une tâche à la liste. | |||
On désactive les interruptions puis on parcourt la liste pour trouver la première tâche endormie. | |||
A cette place on réveille la tâche, on y place le pointeur de fonction passée en paramètre et on met à jour le stack pointer et la pile. | |||
On réactive finalement les interruptions.<syntaxhighlight lang="c"> | |||
void ajout_tache(void (*fonction)(void)){ | |||
cli(); | |||
int counter = 0; | |||
while(liste_process[counter].state){ | |||
counter++; | |||
} | |||
liste_process[counter].state = true; | |||
liste_process[counter].addr_fonction = fonction; | |||
init_stackPointer_tasks(counter); | |||
InitialisationPile(counter); | |||
sei(); | |||
} | |||
</syntaxhighlight>D'une manière légèrement symétrique, la fonction tue_tache() retire dynamiquement une tâche de la liste. | |||
On désactive les interruptions puis on parcourt la liste pour trouver la tâche avec la fonction correspondante à celle donnée en paramètre. | |||
Si on la trouve, on endort la tâche et on remplace le pointeur de fonction par le pointeur NULL | |||
On réactive finalement les interruptions.<syntaxhighlight lang="c"> | |||
void tue_tache(void (*fonction)(void)){ | |||
cli(); | |||
for(int counter = 0; counter<NB_PROCESS; counter++){ | |||
if(liste_process[counter].addr_fonction == fonction){ | |||
liste_process[counter].addr_fonction = NULL; | |||
liste_process[counter].state = false; | |||
liste_process[counter].sleepTime = 0; | |||
} | |||
} | |||
sei(); | |||
} | |||
</syntaxhighlight>Pour l'initialisation, on ajoute les tâches une par une avec notre fonction. | |||
On impose les ports des leds à être en sortie. | |||
On appelle les différentes fonctions d'initialisation notamment de stack pointer, de pile,du minuteur pour les interruptions, du SPI et d'état. | |||
Cette dernière parcourt le nombre de tâches possible et les mets vraies si il y a un pointeur de fonction.<syntaxhighlight lang="c"> | |||
void initialisation(){ | |||
//setup fonctions | |||
ajout_tache(led1); | |||
//...leds | |||
ajout_tache(anti_block); | |||
//config ports | |||
DDRC |= 0b00001001; | |||
DDRD |= 0b10010000; | |||
DDRB |= 0b00000001; | |||
init_minuteur(DIVISEUR,PERIODE); | |||
init_etat(); | |||
serial_init(SPEED); | |||
for(int current_process = 0 ; current_process < NB_PROCESS ; current_process ++){ | |||
init_stackPointer_tasks(current_process); | |||
InitialisationPile(current_process); | |||
} | |||
} | |||
void init_etat(){ | |||
for(int i = 0; i < NB_PROCESS; i ++){ | |||
liste_process[i].state = (liste_process[i].addr_fonction != NULL); | |||
} | |||
} | |||
</syntaxhighlight>En guise d'amélioration, on créer une fonction capable de faire choisir via le clavier, quel tâche on endort ou on réveille, ici quelle led fait clignoter ou non. Dans le cas d'un caractère autre que prévu, toutes les tâche sont endormie et, les leds clignotent brièvement pour l'indiquer.<syntaxhighlight lang="c"> | |||
void choix_led(){ | |||
char carac; | |||
while(1){ | |||
carac = serial_get(); | |||
switch (carac){ | |||
case '0': | |||
ajout_tache(led1); | |||
break; | |||
//...pour les ajouts | |||
case '5': | |||
tue_tache(led1); | |||
break; | |||
//...pour les tuer | |||
default: | |||
tue_tache(led1); | |||
//...tue leds | |||
for(int i=0; i<10;i++){/*Clignotement*/} | |||
break; | |||
} | |||
} | |||
} | |||
</syntaxhighlight> | |||
==== Vidéos test ordonnanceur ==== | |||
[[Fichier:Vidéo test.mp4|vignette|gauche]] | |||
== Conclusion == | |||
Version actuelle datée du 15 décembre 2025 à 17:07
Présentation projet :
Dans ce projet Pico, l'objectif est de réaliser un pico ordinateur composé de différentes cartes fille. La conception des cartes est répartie entre les binômes et dans notre cas, nous nous occuperons de la carte fille clavier.
Pour suivre l'avancée du travail, les fichiers seront déposés sur notre archive git.
Partie Shield
Nous avons tout d'abord commencé par le routage d'un shield Arduino en guise de carte de test pour les cartes filles, dans le cas où la carte mère ne serait pas opérationnelle.
Schématique
En utilisant un modèle donné nous avons fait cette schématique pour notre shield.
Routage
Nous avons routé le shield comme ceci
Notre carte ressemblant à celle du binôme 1, nous utiliserons par conséquent la leur.
Partie Carte Clavier
Mise en place
Nous avons d'abord eu à définir le projet pour le clavier et nous avons décidé de réaliser entièrement notre carte clavier de manière matricielle.
Nous avons choisi de créer un clavier de 30 touches permettant d'écrire 26 caractères par mode avec 3 modes différents. Les 4 touches restantes serviront de touches Supprimer, Espace, Maj et Symbole. Ces touches seront communes aux 3 modes du clavier. Nous allons profiter du fait d'avoir attribué une diode à chaque touche pour pouvoir en presser deux en même temps. Comme le fonctionnement de la touche Shift, il faut garder la touche enfoncée pour changer de mode.
MAJ enfoncée pour les majuscules et SYMBOLE pour les chiffres et symboles. En appuyant sur MAJ et SYMBOLE simultanément on pourra activer la touche Entrée.
Disposition des touches
La disposition est trouvable dans le fichier bind_clavier trouvable sur le git.
| a | b | c | d | e | f |
| g | h | i | j | k | i |
| m | n | o | p | q | r |
| s | t | u | v | w | x |
| y | z | del | space | maj | symbole |
| A | B | C | D | E | F |
| G | H | I | J | K | L |
| M | N | O | P | Q | R |
| S | T | U | V | W | X |
| Y | Z | del | space | maj | symbole |
| 1 | 2 | 3 | & | ; | , |
| 4 | 5 | 6 | " | ? | ! |
| 7 | 8 | 9 | ' | = | + |
| / | 0 | : | ( | ) | _ |
| * | - | del | space | maj | symbole |
Kicad
Nous avons donc mis en place cette matrice sur notre carte KiCad à l'aide d'un atmega328 programmable par un ISP.
Les lignes et colonnes de touches sont donc reliées aux broches des ports C et D et chacun des boutons est relié à une diode, ce qui nous permettra de taper sur plusieurs touches en même temps, nous permettant de changer les modes de fonctionnement du clavier entre minuscule, majuscule et symbole.
On place également des résistances de pull up sur les colonnes pour stabiliser la détection et lire l'état bas plutôt que l'état haut.
Pour s'assurer d'être dans le bon mode de clavier, nous avons placé 3 LEDs qui indiquent chacune un mode différent.
Notre carte sera relié à la carte mère via un connecteur 1*8 placé à l'extremité supérieure de la carte.
Notre carte, une fois routée, ressemble donc à ceci :
Images plus en détails de la carte :
Code clavier
Le code C du clavier peut être trouvé sur le git.
Variables et constantes
Tout d'abord nous avons initialisé les différentes librairies et les constantes globales utiles au programme.
Nous avons donc 6 colonnes et 5 lignes, on retient aussi les "coordonnées" des touches MAJ et SYMBOLE.
La constante d'anti rebond permet de ralentir le programme pour éviter que plusieurs caractères soient envoyés avec un seul appui.
#define NB_COL 6
#define NB_ROW 5
#define NB_MODE 3
#define ASCII_ENTER 13
#define MAJ 128
#define MAJ_ROW 4
#define MAJ_COL 4
#define SYMBOLE 129 //pour dépasser la table ascii "normale"
#define SYMBOLE_ROW 4
#define SYMBOLE_COL 5
#define MAX_PRESSED_KEYS 2
#define ANTI_REBOND_mS 10 //10ms pour éviter le "spam" d'envoi
Ensuite, nous avons déclaré les variables globales liées aux ports utilisés par les touches du clavier. On mappe les colonnes et les lignes avec les pins correspondants.
uint8_t BIT_Row[NB_ROW] = {PD1,PD0,PC5,PC4,PC3};
uint8_t BIT_Col[NB_COL] = {/*...*/};
volatile uint8_t *DDR_Row[NB_ROW] = {&DDRD,&DDRD,&DDRC,&DDRC,&DDRC};
volatile uint8_t *DDR_Col[NB_COL] = {/*...*/};
volatile uint8_t *PORT_Row[NB_ROW] = {&PORTD,/*...*/};
volatile uint8_t *PORT_Col[NB_COL] = {/*...*/};
volatile uint8_t *PIN_Col[NB_COL] = {&PINC,&PINC,&PINC,&PIND,&PIND,&PIND};
Nous avons également déclaré une matrice 3D représentant notre clavier avec les codes ASCII décimaux correspondant à chaque touche. Les constantes MAJ et SYMBOLE étant déclarées avec des valeurs dépassant la table ASCII basique pour éviter les conflits.
int bind[NB_MODE][NB_ROW][NB_COL]={ //ASCII décimal
{ {97,98,99,100,101,102},
{103,104,105,106,107,108},
{109,110,111,112,113,114},
{115,116,117,118,119,120},
{121,122,127,32,MAJ,SYMBOLE} } , //mode défaut
{ {...} } , // Maj
{ {...} } ; //Symbole
Fonctions
Les ports doivent êtres initialisés, les ports correspondant aux colonnes seront en entrée pour détecter un appui et les lignes en sortie pour pouvoir les activer et désactiver pour la lecture au cas par cas.
Pour le SPI, on le met en maître et on assigne MOSI,SCK et SS en sortie.
void initIn(void){
for(int row = 0; row < NB_ROW ; row++){
*(DDR_Row[row]) |= (1 << BIT_Row[row]);
}
for(int col = 0; col < NB_COL ; col++){
*(DDR_Col[col]) &= ~(1 << BIT_Col[col]);
*(PORT_Col[col]) |= (1 << BIT_Col[col]);
}
}
[...]
void initSPI(void) {
DDRB |= (1 << PB2) | (1 << PB3) | (1 << PB5);
SPCR = (1 << SPE) | (1 << MSTR) | (1 << SPR0);
}
Notre fonction principale permettant le scan des touches parcourt d'abord chaque ligne en l'activant puis en parcourant chaque colonne pour vérifier si un appui est réalisé.
On y ajoute un délai pour laisser le temps de scanner la touche.
Si on dépasse le nombre de touches appuyées elle s'arrête sinon elle sauvegarde la ligne et la colonne correspondantes à la touche pressée dans le tableau pressed_keys[][] déclaré globalement.
Après le parcours de toute la ligne cette dernière est désactivée.
void scanTouche(void){
counter_pressed = 0;
for(uint8_t row = 0 ; row < NB_ROW; row++ ){
*(PORT_Row[row]) &= ~(1 << BIT_Row[row]);
_delay_us(1);
for(uint8_t col = 0; col < NB_COL; col++){
if(counter_pressed > MAX_PRESSED_KEYS-1){return;}
if(!(*(PIN_Col[col])&(1 << BIT_Col[col]))){
pressed_keys[counter_pressed][0] = row;
pressed_keys[counter_pressed][1] = col;
counter_pressed++;
}
}
*(PORT_Row[row]) |= (1 <<(BIT_Row[row]));
}
}
Main
On initialise les variables utiles au programme.
- valeur_touche est la valeur ASCII de la touche, à 0 initialement pour le caractère NULL.
- row_touche est l'index de la ligne, à -1 initialement pour mettre une condition bloquante à l'envoi si seule une touche MAJ ou SYMBOLE est pressée.
- mode est à 0 car le mode minuscule est le mode par défaut.
int valeur_touche = 0;
int row_touche = -1;
int col_touche = -1;
int mode = 0;
Boucle while
On utilise d'abord notre fonction pour scanner la touche pressée et on place ses coordonnées dans des variables r et c plus claires.
while(1){
scanTouche();
for(int i = 0;i < counter_pressed ; i++){
int r=pressed_keys[i][0];
int c=pressed_keys[i][1];
Analyse de la touche
Premièrement on vérifie si la touche est SYMBOLE ou MAJ avec les coordonées déclarées. Si c'est le cas on vérifie que l'autre touche spéciale ne soit pas déjà pressée en vérifiant que le mode soit égal à 0, si ce n'est pas le cas on envoie la touche Entrée. Sinon on change le mode pour la valeur correspondante.
if(r == MAJ_ROW && c == MAJ_COL ){
if(mode == 2) {
valeur_touche = ASCII_ENTER;
sendSPI(valeur_touche);
break;
}
mode = 1;
}
else if(r == SYMBOLE_ROW && c == SYMBOLE_COL ){
if(mode == 1) {
valeur_touche = ASCII_ENTER;
sendSPI(valeur_touche);
break;
}
mode = 2;
}
Dans le cas d'une touche "normale" on retient les coordonnées, et on envoie via SPI le caractère correspondant à ces coordonnées dans la matrice si jamais une touche est pressée. On rajoute un délai pour éviter le rebond lors d'un appui.
else {
row_touche = r;
col_touche = c;
}
if(counter_pressed > 0 && row_touche > -1 && col_touche > -1){
valeur_touche = bind[mode][row_touche][col_touche];
sendSPI(valeur_touche);
}
if (valeur_touche!=0) {_delay_ms(ANTI_REBOND_mS);}
On finit ensuite par reset les valeurs des variables pour recommencer le scan.
mode = 0;
row_touche = -1;
col_touche = -1;
valeur_touche = 0;
Ordonnanceur
Le code complet de l'ordonnanceur est trouvable ici sur le git. Les définitions sont trouvable dans le .h associé.
On commence par créer une structure qui représente une tâche, elle contient :
- Un pointeur de la fonction correspondante
- Sa place dans la pile pour permettre de la retrouver
- Son état soit endormi soit reveillé
- Un temps de sommeil
typedef struct{
void (*addr_fonction)(void);
uint16_t stackPointer; // Pointeur de pile
bool state; // État (actif vide ou finie)
int sleepTime;
} Process;
La tâche principale consiste à faire clignoter une led et de donner un temps de sommeil.
void led1(){
while (1){
PORTC ^= 1;
sleep(287);
}
}
//[led2,led3...]
On ajoute également une fonction idle, pour qu'il y ai toujours une tâche reveillée si les autres sont endormies, cela empêche un blocage dans une boucle infinie.
void anti_block(){ //tache vide pour éviter de rester bloqué si toutes les taches sont endormies
while(1);
}
La fonction sleep() permet de remettre le timer de l'ISR à (F_CPU/1000*periode/diviseur - 1) et ensuite changer le temps de sommeil de la tâche actuelle par un temps donné.
void sleep(int ms_sleep){
cli();
TCNT1=max_counter-1;
liste_process[process_actuel].sleepTime = ms_sleep;
sei();
}
La fonction change_process() commence par décrementer les temps de sommeil de toutes les tâches en liste ou mettre ce temps à 0 si il est inférieur au paramètre de décrementation. Ensuite il parcourt toutes les tâches reveillées et avec un temps de sommeil supérieur à 0.
void change_process(){
for (int i=0 ; i<NB_PROCESS ; i++){
if (liste_process[i].sleepTime - S_DECR< 0){
liste_process[i].sleepTime = 0;
}
else{
liste_process[i].sleepTime -= S_DECR;
}
}
do{
process_actuel++;
if (process_actuel >= NB_PROCESS){
process_actuel = 0;
}
}
while (liste_process[process_actuel].sleepTime > 0 || !(liste_process[process_actuel].state));
}
La fonction ajout_tache() permet d'ajouter dynamiquement une tâche à la liste.
On désactive les interruptions puis on parcourt la liste pour trouver la première tâche endormie.
A cette place on réveille la tâche, on y place le pointeur de fonction passée en paramètre et on met à jour le stack pointer et la pile.
On réactive finalement les interruptions.
void ajout_tache(void (*fonction)(void)){
cli();
int counter = 0;
while(liste_process[counter].state){
counter++;
}
liste_process[counter].state = true;
liste_process[counter].addr_fonction = fonction;
init_stackPointer_tasks(counter);
InitialisationPile(counter);
sei();
}
D'une manière légèrement symétrique, la fonction tue_tache() retire dynamiquement une tâche de la liste.
On désactive les interruptions puis on parcourt la liste pour trouver la tâche avec la fonction correspondante à celle donnée en paramètre.
Si on la trouve, on endort la tâche et on remplace le pointeur de fonction par le pointeur NULL
On réactive finalement les interruptions.
void tue_tache(void (*fonction)(void)){
cli();
for(int counter = 0; counter<NB_PROCESS; counter++){
if(liste_process[counter].addr_fonction == fonction){
liste_process[counter].addr_fonction = NULL;
liste_process[counter].state = false;
liste_process[counter].sleepTime = 0;
}
}
sei();
}
Pour l'initialisation, on ajoute les tâches une par une avec notre fonction.
On impose les ports des leds à être en sortie.
On appelle les différentes fonctions d'initialisation notamment de stack pointer, de pile,du minuteur pour les interruptions, du SPI et d'état.
Cette dernière parcourt le nombre de tâches possible et les mets vraies si il y a un pointeur de fonction.
void initialisation(){
//setup fonctions
ajout_tache(led1);
//...leds
ajout_tache(anti_block);
//config ports
DDRC |= 0b00001001;
DDRD |= 0b10010000;
DDRB |= 0b00000001;
init_minuteur(DIVISEUR,PERIODE);
init_etat();
serial_init(SPEED);
for(int current_process = 0 ; current_process < NB_PROCESS ; current_process ++){
init_stackPointer_tasks(current_process);
InitialisationPile(current_process);
}
}
void init_etat(){
for(int i = 0; i < NB_PROCESS; i ++){
liste_process[i].state = (liste_process[i].addr_fonction != NULL);
}
}
En guise d'amélioration, on créer une fonction capable de faire choisir via le clavier, quel tâche on endort ou on réveille, ici quelle led fait clignoter ou non. Dans le cas d'un caractère autre que prévu, toutes les tâche sont endormie et, les leds clignotent brièvement pour l'indiquer.
void choix_led(){
char carac;
while(1){
carac = serial_get();
switch (carac){
case '0':
ajout_tache(led1);
break;
//...pour les ajouts
case '5':
tue_tache(led1);
break;
//...pour les tuer
default:
tue_tache(led1);
//...tue leds
for(int i=0; i<10;i++){/*Clignotement*/}
break;
}
}
}
Vidéos test ordonnanceur
