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

De projets-se.plil.fr
Aller à la navigation Aller à la recherche
Ligne 153 : Ligne 153 :


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.
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.
Pour s'assurer d'être dans le bon mode de clavier, nous avons placé 3 LEDs qui indiquent chacune un mode différent.
Ligne 213 : Ligne 215 :


volatile uint8_t *PIN_Col[NB_COL] = {&PINC,&PINC,&PINC,&PIND,&PIND,&PIND};
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.<syntaxhighlight lang="c" line="1" start="28">
</syntaxhighlight>Nous avons également déclaré une matrice 3D représentant notre clavier avec les codes ascii décimaux correspondant à chaque touche. <syntaxhighlight lang="c" line="1" start="28">
int bind[NB_MODE][NB_ROW][NB_COL]={                                    //ASCII décimal
int bind[NB_MODE][NB_ROW][NB_COL]={                                    //ASCII décimal
    { {97,98,99,100,101,102},   
    { {97,98,99,100,101,102},   
Ligne 245 : Ligne 247 :
     SPCR = (1 << SPE) | (1 << MSTR) | (1 << SPR0);
     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é. Si on dépasse le nombre de touches appuyéess elle s'arrête sinon elle sauvegarde la ligne et la colonne correspondantes à la touche pressée dans un tableau déclaré globalement. Après le parcours de toute la ligne cette dernière est désactivée. <syntaxhighlight lang="c" line="1" start="62">
</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é. 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" line="1" start="62">
void scanTouche(void){
void scanTouche(void){
   counter_pressed = 0;  
   counter_pressed = 0;  
Ligne 278 : Ligne 282 :
</syntaxhighlight>
</syntaxhighlight>


===== Boucle while =====
==== Boucle while ====
 
On scanne tout d'abord la touche pressée et on place ses coordonnées dans des variables plus compréhensibles.<syntaxhighlight lang="c">
On scanne tout d'abord la touche pressée et on place ses coordonnées dans des variables plus compréhensibles.<syntaxhighlight lang="c">
while(1){
while(1){
Ligne 287 : Ligne 292 :
</syntaxhighlight>
</syntaxhighlight>


===== Analyse de la touche =====
==== Analyse de la touche ====
 
Premièrement on vérifie si la touche est SYMBOLE ou MAJ, si c'est le cas on change le mode en la valeur correspondante seulement si le mode n'a pas déjà été changé ce qui voudrait dire que l'autre touche aurait été pressée et dans ce cas la touche ENTER est envoyée. <syntaxhighlight lang="c">
Premièrement on vérifie si la touche est SYMBOLE ou MAJ, si c'est le cas on change le mode en la valeur correspondante seulement si le mode n'a pas déjà été changé ce qui voudrait dire que l'autre touche aurait été pressée et dans ce cas la touche ENTER est envoyée. <syntaxhighlight lang="c">
  if(r == MAJ_ROW && c == MAJ_COL ){
  if(r == MAJ_ROW && c == MAJ_COL ){
Ligne 467 : Ligne 473 :
       tue_tache(led1);
       tue_tache(led1);
       //...tue leds
       //...tue leds
       for(int i=0; i<10;i++){
       for(int i=0; i<10;i++){/*Clignotement*/}
PORTC |= 0b00001001;
PORTD |= 0b10010000;
PORTB |= 0b00000001;
_delay_ms(25);
PORTC = 0;
PORTD = 0;
PORTB = 0;
_delay_ms(25);}
       break;
       break;
     }
     }

Version du 12 décembre 2025 à 11:09

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

Screenshot 2025-11-14 13-52-53.png

En utilisant un modèle donné nous avons fait cette schématique pour notre shield.

Routage

Nous avons routé le shield comme ceci

shield


Notre carte ressemblant à cellue du binôme 1, nous utiliserons par conséquent la leur.

Tests

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.

Mode 1: Minuscule
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
Mode 2 : Majuscule
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
Mode 3 : Num et 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.

Carte2.png

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 :

Image du routage du clavier

Images plus en détails de la carte :

Matrice touche.png
Partie avr.png








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.

#include <avr/interrupt.h>
#include <avr/io.h>
#include <stdint.h>
#include <util/delay.h>

#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 enregistre les ports exacts de chaques lignes et colonnes.

uint8_t BIT_Row[NB_ROW] = {PD1,PD0,PC5,PC4,PC3};                                  
uint8_t BIT_Col[NB_COL] = {PC0,PC1,PC2,PD2,PD3,PD4};

volatile uint8_t *DDR_Row[NB_ROW] = {&DDRD,&DDRD,&DDRC,&DDRC,&DDRC};              
volatile uint8_t *DDR_Col[NB_COL] = {&DDRC,&DDRC,&DDRC,&DDRD,&DDRD,&DDRD};

volatile uint8_t *PORT_Row[NB_ROW] = {&PORTD,&PORTD,&PORTC,&PORTC,&PORTC};        
volatile uint8_t *PORT_Col[NB_COL] = {&PORTC,&PORTC,&PORTC,&PORTD,&PORTD,&PORTD};

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.

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 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é. 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]);                       
    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 scanne tout d'abord la touche pressée et on place ses coordonnées dans des variables plus compréhensibles.

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, si c'est le cas on change le mode en la valeur correspondante seulement si le mode n'a pas déjà été changé ce qui voudrait dire que l'autre touche aurait été pressée et dans ce cas la touche ENTER est envoyée.

 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 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 continuer 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