SE4Binome2024-1

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

Lien git : https://gitea.plil.fr/lgrevin/PICO_Binome1.git

Réalisation d'un shield arduino

Nous avons réalisé un bouclier pour Arduino Uno afin d'implémenter un système d'ordonnancement, ce qui nous permettra de simuler le fonctionnement d'une carte mère.

Ce bouclier est conçu pour connecter jusqu’à 5 périphériques SPI via des connecteurs IDC HE10 (8 broches), en intégrant des lignes spécifiques pour la réinitialisation et l’interruption de chaque périphérique, assurant ainsi un contrôle optimal.

En plus des connexions SPI, le bouclier comprend également une mémoire, avec deux options de stockage possibles : une carte micro-SD via un connecteur Molex 10431 ou une puce mémoire AT45DB641E, laissant la flexibilité de choisir celle qui sera soudée selon les besoins. Un convertisseur de niveau (74LV125) est également intégré pour assurer la compatibilité de tension entre l’Arduino (5V) et les mémoires (3,3V), garantissant une communication stable entre les composants.

Schématique et Routage

Schématique du PicoShield
Routage du PicoShield

Shield Brasé

Shield fini

Shield sur une carte Arduino

Vérification des Leds et de la Carte SD

Code Arduino pour vérifier si les Leds fonctionnent :

https://gitea.plil.fr/lgrevin/PICO_Binome1/src/branch/main/VerificationArduino/verif.c

Nous avons écris un petit programme Arduino pour allumer les leds alternativement et donc vérifier leurs fonctionnement.

Vérification de la détection de la carte SD :

Pour vérifier si la carte SD est bien détecté nous avons utilisé un programme exemple de l'arduino :

https://gitea.plil.fr/lgrevin/PICO_Binome1/src/branch/main/Verification_Shield_et_Arduino/SD.ino

Nous avons rencontré des difficultés pour détecter la carte SD. Nous avons essayé plusieurs solutions, notamment en vérifiant l’horloge de notre Shield. Pour cela, nous avons soudé des fils aux ports GND et SCK de la puce mémoire et envoyé un code SPI à l’Arduino pour analyser le signal à l’oscilloscope, mais cela n’a pas permis d’identifier le problème. Après avoir revérifié les soudures et repassé le fer à souder sur plusieurs connexions, nous avons finalement découvert qu’une résistance mal soudée était la source du problème.


Carte SD détectée
Carte SD détectée

Code ordonnanceur

Un ordonnanceur sert à gérer l'exécution des tâches dans un système temps réel, en assurant qu'elles s'exécutent dans un ordre optimal et respectent les délais. Cela garantit que les processus critiques reçoivent les ressources nécessaires pour fonctionner sans interruption, optimisant la performance et la réactivité du système. La gestion des processus repose sur une structure qui définit chaque tâche du système. Cette structure inclut les informations nécessaires pour suivre l'état et le comportement des processus, comme indiqué ci-dessous :

typedef struct {
    uint16_t functionAdress;  // Adresse de la fonction
    uint16_t stackPointer;    // Pointeur de pile
    bool state;               // État (actif ou en sommeil)
    uint16_t sleepTime;       // Temps restant en sommeil (ms)
    uint16_t reason;          // Raison de suspension
} Process;

Voici une fonction basique que nous utilisons pour faire clignoter une LED. On peut noter l'utilisation d'une fonction delay, qui est liée à notre méthode pour endormir un processus, comme nous le verrons un peu plus tard.

void LED1() {
    while (1) {
        PORTD ^= (1 << PD1);
        delay(REASON_DELAY,DELAY1);
    }
}

Notre scheduler a deux objectifs : d'abord, décrémenter le temps de sommeil des processus endormis, puis s'assurer que "taskIndex" pointe uniquement vers un processus qui n'est pas en sommeil.

// Gestion de l'ordonnanceur
void scheduler() {
    for(int i=0; i<NB_PROCESS; i++){
        if (tableauProcess[i].state == SLEEP_STATE && tableauProcess[i].reason == REASON_DELAY){
            if (PERIODE<tableauProcess[i].sleepTime)
                tableauProcess[i].sleepTime -= PERIODE;
            else
                tableauProcess[i].sleepTime = 0;

            if(tableauProcess[i].sleepTime == 0){
                tableauProcess[i].state = AWAKE_STATE;
                tableauProcess[i].reason = NO_REASON;
            }
        }
    }

    do {
        taskIndex++;
        if (taskIndex == NB_PROCESS) taskIndex = 0;
    } while (tableauProcess[taskIndex].state == SLEEP_STATE);
}

La fonction delay met le processus en pause pour une durée donnée. Elle désactive les interruptions (cli()), passe le processus en SLEEP_STATE, enregistre la raison et la durée, réinitialise le compteur de Timer1 (TCNT1 = 0), puis réactive les interruptions (sei()). Cela permet de gérer les délais avec précision.

void delay(uint16_t reason, uint16_t ms)
{
  cli(); //Désactive les interruptions
  tableauProcess[taskIndex].state = SLEEP_STATE;
  tableauProcess[taskIndex].reason = reason;
  tableauProcess[taskIndex].sleepTime = ms;
  TCNT1 = 0; //Valeur actuelle du compteur de Timer1
  sei(); //Réactive les interruptions
  TIMER1_COMPA_vect();
}

Au final, avec notre ordonnanceur, on peut faire tourner plusieurs processus simultanément. Cela nous a permis, par exemple, de faire clignoter les LEDs de manière asynchrone, en gérant indépendamment le temps de sommeil de chaque processus.

Un problème qu'on a pu rencontrer, c'est que si tous les processus sont en état de sommeil, on se retrouve dans une boucle infinie où le temps de sommeil des processus ne se décrémente pas, ce qui bloque tout. Après avoir pris du temps pour comprendre ça, j'ai ajouté un processus IDLE qui ne fait rien. Grâce à lui, si tous les autres processus sont endormis, celui-là ne l'est pas, et cela empêche de rester bloqué dans une boucle infinie.

void IDLE(){
    while(1);
}

Pour complexifier cet ordonnanceur, nous avons ajouté une fonctionnalité en nous inspirant (très fortement) du cours : la lecture et l’écriture sur les ports séries. Par exemple, on peut lire des caractères (comme un chiffre) via le port série, et l’objectif est ensuite d’afficher ce caractère sur un afficheur 7 segments branché sur un des port HE10.

https://rex.plil.fr/Enseignement/Systeme/Systeme.PSE/systeme013.html et https://rex.plil.fr/Enseignement/Systeme/Systeme.PSE/systeme014.html

uint8_t spi_echange(uint8_t envoi){                  // Communication sur le bus SPI
    SPDR = envoi;                                        // Octet a envoyer
    while(!(SPSR & (1<<SPIF)));                          // Attente fin envoi (drapeau SPIF du statut)
    return SPDR;                                         // Octet reçu
}
void SevenSeg() {
    while (1) {
        spi_activer();

        for (int i = 0; i < 4; i++) {
            spi_echange(valeur);
        }

        spi_desactiver();

        delay(REASON_DELAY, 100);
    }
}

https://gitea.plil.fr/lgrevin/PICO_Binome1/src/branch/main/Ordonnanceur

Réalisation d'une carte clavier

Nous avons choisi de faire une matrice de touche pour réaliser la carte clavier.

Cette matrice contient 8 colonnes et 4 lignes soit un total de 32 boutons. Nous avons également choisi d'ajouter des leds RGB, une sur chaque ligne et chaque colonne qui seront commandés par 2 controleurs de leds (TLC5947 [1]) à l'aide des TLC nous pouvons jouer sur l'intensités et les couleurs des leds.

Datasheet des LEDs RGB:

https://docs.rs-online.com/988a/0900766b80e2903e.pdf

Consommation de la carte clavier

La Puissance totale maximale est de 5,55W.

Bilan de puissance (VCC = 5V)
Composant Courant Puissance
12xLEDS RGB 12x(30x3) mA 5,4W
ATMega328p 15 mA 75 mW
LED d'alim 15 mA 75 mW
TOTAL 1,11A 5,55W

Schématique & Routage

Carte Clavier Schématique
Carte Clavier Schématique
Carte Clavier routé

Brasure de la carte Clavier

Programmation de la carte Clavier

Clavier par default

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 Alt

Clavier lettre 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 Alt

Clavier chiffre et caractère spéciaux

0 1 2 3 4 5 6
7 8 9 ( ) " -
+ * / ?
Alt