« SE4Binome2024-2 » : différence entre les versions

De projets-se.plil.fr
Aller à la navigation Aller à la recherche
 
(19 versions intermédiaires par 3 utilisateurs non affichées)
Ligne 52 : Ligne 52 :
Nous avons terminé de souder complétement notre carte et il est temps de la tester. Pour vérifier le bon fonctionnement de notre travail, nous utiliserons un code test dédié à notre écran HD44780 fournis par la librairie du même nom sur l'IDE Arduino. Cette méthode de test nous convient parfaitement puisque nous voulons uniquement vérifier son fonctionnement.  
Nous avons terminé de souder complétement notre carte et il est temps de la tester. Pour vérifier le bon fonctionnement de notre travail, nous utiliserons un code test dédié à notre écran HD44780 fournis par la librairie du même nom sur l'IDE Arduino. Cette méthode de test nous convient parfaitement puisque nous voulons uniquement vérifier son fonctionnement.  


Malheureusement, le code ne fonctionne pas directement à cause de mauvais branchement sur notre connecteur. On avait relié directement les pins  Enable, et RS du connecteur écran à notre 5V.  Nous avons du alors faire du bricolage pour la réparer. Pour ce faire, nous avons donc coupé les piste du RS et Enable pour les relier à des pins non utilisés du microcontrôleur.  
Malheureusement, le code ne fonctionne pas directement à cause de mauvais branchement sur notre connecteur. On avait relié directement les pins  Enable, et RS du connecteur écran à notre 5V.  Nous avons du alors faire du bricolage pour la réparer. Pour ce faire, nous avons donc coupé les piste du RS et Enable pour les relier à des pins non utilisés du microcontrôleur.
[[Fichier:Resolution carte ecran.png|centré|vignette]] 


[PHOTO]  
Après ces réparations, on arrive à afficher le texte voulu grâce au code d'exemple Arduino :
[[Fichier:Ecran.jpg|centré|vignette]]  


Après ces réparations, on arrive à afficher le texte voulu grâce au code d'exemple Arduino :
Il nous faut désormais tester notre carte écran en code C. Pour ce faire, nous avons récupéré le code des Masters I2L et mis à jour les fuses de l'atmega328p. Cependant  lors de l'upload du code, notre Atmega n'est plus détecté. Nous devons alors vérifier la clock par le biais d'un oscilloscope. 
 
Cette carte a du être recommencée car après l'ajout de commande changeant les fuses du microprocesseur, ce dernier n'était plus reconnu. Ainsi nous avons pris le temps de resouder une carte écran. 


[PHOTO]  
Apres cela un autre problème (cette fois ci de circuit) est que nous perdons 1,5V entre le Shield et la carte écran. Apres quelque test au multimètre sans réponse nous avons vérifié la clock au niveau du Shield et au niveau de la carte écran. 
[[Fichier:Clock shield.jpg|gauche|vignette|594x594px|Clock SPI sur le Shield]]
[[Fichier:Clock carte ecran.jpg|néant|vignette|484x484px|Clock SPI sur la carte écran]]
<br>
<br>
<br>
<br>
Cela est selon nous la cause de non fonctionnement du transfert de donnée entre les deux cartes.


Il nous faut désormais tester notre carte écran en code C. Pour ce faire, nous avons récupéré le code des Masters I2L et mis à jour les fuses de l'atmega328p. Cependant  lors de l'upload du code, notre Atmega n'est plus détecté. Nous devons alors vérifier la clock par le biais d'un oscilloscope.
== Ordonnanceur et Gestion du Contexte des Tâches ==
== Ordonnanceur et Gestion du Contexte des Tâches ==


Ligne 206 : Ligne 216 :
</syntaxhighlight>
</syntaxhighlight>


1. `cli()` : désactive les interruptions.
# `cli()` : désactive les interruptions.
2. `LED_init()` et `USART_Init()` : initialisent les LED et la communication série.
# `LED_init()` et `USART_Init()` : initialisent les LED et la communication série.
3. `init_minuteur()` : initialise le minuteur avec une période.
# `init_minuteur()` : initialise le minuteur avec une période.
4. `init_pile(i)` : initialise la pile pour chaque tâche.
# `init_pile(i)` : initialise la pile pour chaque tâche.
5. `sei()` : réactive les interruptions.
# `sei()` : réactive les interruptions.


L'ordonnanceur gère ainsi le contexte des tâches en suivant une séquence de round-robin, tout en assurant que chaque tâche exécute son cycle de manière autonome et en garantissant la disponibilité des ressources matérielles.
L'ordonnanceur gère ainsi le contexte des tâches en suivant une séquence de round-robin, tout en assurant que chaque tâche exécute son cycle de manière autonome et en garantissant la disponibilité des ressources matérielles.


=== Fonction `wait()` ===
=== Fonction `wait()` ===
Comme première amélioration de votre ordonnanceur, il nous a été demandé de mettre en place un état endormi pour nos processus. Pour cela nous avons creer la procedure wait() suivante :
Comme première amélioration de votre ordonnanceur, il nous a été demandé de mettre en place un état endormi pour nos processus. Pour cela nous avons créer la procédure wait() suivante :


<syntaxhighlight lang="cpp">
<syntaxhighlight lang="cpp">
Ligne 296 : Ligne 306 :
=== Tests de l'Ordonnanceur ===
=== Tests de l'Ordonnanceur ===
* Gestion des tâches : Nous avons simulé plusieurs tâches, dont l'affichage et la gestion du clavier, et vérifié que l'ordonnanceur passe correctement d'une tâche à l'autre.
* Gestion des tâches : Nous avons simulé plusieurs tâches, dont l'affichage et la gestion du clavier, et vérifié que l'ordonnanceur passe correctement d'une tâche à l'autre.
* En utilisant le logiciel minicom nous avons pus lancer une 2eme tâche en parallèle d'une première tâche. En tapant "a" une deuxième LED se met à clignoter.
* En utilisant le logiciel minicom nous avons pu lancer une 2eme tâche en parallèle d'une première tâche. En tapant "a" une deuxième LED se met à clignoter.
[[Fichier:Videoprocess.mp4|centré|vignette|400x300px|Test de l'ordonnanceur : lancer une tâche. ]]
[[Fichier:Videoprocess.mp4|centré|vignette|400x300px|Test de l'ordonnanceur : lancer une tâche. ]]


Ligne 317 : Ligne 327 :
</syntaxhighlight>En utilisant cette fonction nous avons pu afficher un caractère. Cependant nous avons remarquer un retard lors de l'affichage du caractère. Nous avons essayer plusieurs méthode comme sectionner ce que l'on voulait faire en 3 taches mais cela n'a pas fonctionner. Apres longues observation de l'exécution de notre programme, nous avons vu que nous pouvions taper nos caractères sur Minicom, mais l'écran LCD ne se mettait a jour seulement lorsque la LED2 était allumée. Cela montre que le problème était que la LED puisqu'elle était connectée au pin CS. Le fait de la faire clignoter empêchait l'affichage. Apres correction (la LED2 devait rester allumée) :     
</syntaxhighlight>En utilisant cette fonction nous avons pu afficher un caractère. Cependant nous avons remarquer un retard lors de l'affichage du caractère. Nous avons essayer plusieurs méthode comme sectionner ce que l'on voulait faire en 3 taches mais cela n'a pas fonctionner. Apres longues observation de l'exécution de notre programme, nous avons vu que nous pouvions taper nos caractères sur Minicom, mais l'écran LCD ne se mettait a jour seulement lorsque la LED2 était allumée. Cela montre que le problème était que la LED puisqu'elle était connectée au pin CS. Le fait de la faire clignoter empêchait l'affichage. Apres correction (la LED2 devait rester allumée) :     
[[Fichier:Fin Ordonnanceur 7 Segment.mp4|centré|vignette|400x300px|7 segment et minicom]]
[[Fichier:Fin Ordonnanceur 7 Segment.mp4|centré|vignette|400x300px|7 segment et minicom]]
== Tests de la RAM SPI ==
Pour tester la mémoire RAM SPI, plusieurs étapes ont été mises en œuvre pour garantir la bonne communication et la gestion des données. Voici les principales actions réalisées :
=== Fonctions de gestion de l'accès à la RAM ===
* Les fonctions <code>ram_select</code> et <code>ram_deselect</code> ont été ajoutées pour assurer un accès exclusif à la mémoire :
<syntaxhighlight lang="c">
void ram_select() {
    SPI_PORT &= ~(1 << SPI_SS); // Sélectionne la RAM
}
void ram_deselect() {
    SPI_PORT |= (1 << SPI_SS); // Désélectionne la RAM
}
</syntaxhighlight>
* Les fonctions <code>select_microcontroller</code> et <code>select_shield</code> ont été introduites pour basculer entre le Microcontrôleur et le Shield :
<syntaxhighlight lang="c">
void select_microcontroller() {
    PORTC |= (1 << DATA0); // Active le microcontrôleur
    _delay_ms(1);        // Temps de stabilisation
}
void select_shield() {
    PORTC &= ~(1 << DATA0); // Active le Shield
    _delay_ms(1);          // Temps de stabilisation
}
</syntaxhighlight>
=== Opérations de base sur la RAM ===
* Lecture (<code>RAM_read</code>), écriture (<code>RAM_write</code>), et effacement (<code>RAM_clear</code>) ont été implémentés pour interagir avec la mémoire via le bus SPI.
* Exemple d'écriture :
<syntaxhighlight lang="c">
void RAM_write(uint16_t address, char *data) {
    RAM_write_enable();
    CS_LOW();
    spi_exch(WRITE);
    spi_exch((address >> 8) & 0xFF);
    spi_exch(address & 0xFF);
    for (uint8_t i = 0; i < strlen(data); i++) {
        spi_exch(data[i]);
    }
    RAM_write_disable();
    CS_HIGH();
}
</syntaxhighlight>
* Exemple de lecture :
<syntaxhighlight lang="c">
void RAM_read(uint16_t address, char *buffer, uint8_t length) {
    CS_LOW();
    spi_exch(READ);
    spi_exch((address >> 8) & 0xFF);
    spi_exch(address & 0xFF);
    for (uint8_t i = 0; i < length; i++) {
        buffer[i] = spi_exch(0x00);
    }
    buffer[length] = '\0';
    CS_HIGH();
}
</syntaxhighlight>
=== Validation des fonctionnalités ===
* Un test basique avec des données simples a été effectué pour vérifier que les données écrites pouvaient être lues sans erreur.
* Un test de stress par brute force a été réalisé pour écrire et lire des données massivement tout en affichant les résultats avec un <code>printf</code>. Cela a permis de confirmer la robustesse du système.
=== Résultat des tests ===
* Le premier test (illustré dans l'image ci-dessous) montre que la RAM SPI fonctionne correctement, avec des données transmises et récupérées sans perte. Cependant, on remarque l'apparition de différents symboles avant notre message affiché depuis la RAM. On a donc émis deux hypothèses :
** Il existe des zones mémoire de la RAM qui contient des données ne pouvant être modifiées et ce sont ces données là que nous lisons avant notre message.
** Notre fonction pour clear la RAM avec des '0' n'est pas bien fonctionnelle.
Avec une lecture plus approfondie de la datasheet, il n'est fait nulle part mention d' "une zone interdite" ou quelque chose qui pourrait s'en rapprocher. C'est pourquoi on pense que notre seconde hypothèse est la plus probable malgré une fonction qui, au premier regard, semble correcte.
[[Fichier:Ram fonctionne.png|centré|vignette|527x527px|La RAM fonctionne]]
* Dans une tentative de faire disparaître ces symboles non désirés, nous avons essayé une commande printf de dernier espoir ("Printf ici"). A notre grande surprise, le message de cette commande était directement affiché sur notre écran. Nous avons donc fait des recherches sur ce sujet et il semblerait qu'initialement, il aurait fallu réalisé une fonction de redirection de flux afin de pouvoir visualiser le contenu du print. Or nous n'avons rien réalisé de tel. D'après ce que nous avons trouvé sur Internet, certains dispositif tel que des boards Arduino ou autres, ont directement une fonction de redirection de flux d'implémentée.  Nous soupçonnons donc le programmateur ou le microcontrôleur utilisé d'en avoir une également.
[[Fichier:Printf.jpg|centré|vignette]]
== Conclusion ==


=== Tests de la RAM SPI ===
=== Tests de la RAM SPI ===
* Fonctions :
La mémoire RAM SPI ne fonctionne pas encore comme prévu avec notamment ces caractères non souhaités.
* Problèmes liés a la RAM :
 
Malgré nos efforts, nous avons remarqué une chute de tension significative de 1,5 V entre le Shield et la carte écran. Cette chute pourrait expliquer les dysfonctionnements observés dans la communication SPI. Bien que la carte écran fonctionne correctement lorsqu’elle est utilisée seule, elle ne parvient pas à établir une communication via le Shield. Cependant, les différentes étapes de conception et de test nous semblent alignées, et nous pensons être sur la bonne voie pour résoudre ce problème.
 
=== Ordonnanceur et Gestion des Tâches ===
Concernant l’ordonnanceur, toutes les consignes ont été respectées et implémentées avec succès. Le scheduler suit une approche Round-Robin efficace pour gérer les tâches concurrentes. Grâce aux interruptions et à une gestion des délais non bloquants, il permet une répartition optimale du temps CPU entre les tâches, tout en garantissant une bonne réponse pour les processus critiques. Les tests ont validé le bon fonctionnement des différentes tâches, comme la gestion des LEDs, la communication série et l’affichage.
 
=== Pistes d’amélioration ===
Pour améliorer le fonctionnement global et résoudre les problèmes liés au SPI, voici quelques pistes à explorer :
* Identifier la ou les causes de la chute de tension entre le Shield et la carte écran, éventuellement en vérifiant les connexions, la qualité des câbles ou l’alimentation.
* Tester la communication SPI avec d'autres configurations ou composants pour valider chaque élément individuellement.
 
En résumé, bien que des défis restent à résoudre pour la partie SPI, nous avons validé l’ensemble des fonctionnalités de l’ordonnanceur. Nous sommes confiants que les ajustements proposés nous permettront d’obtenir un système pleinement fonctionnel.

Version actuelle datée du 30 janvier 2025 à 11:40

Pico Ordinateur

Ce projet vise à concevoir un pico ordinateur divisé en plusieurs parties, réalisées par différents binômes. Le rôle de notre binôme est de développer une carte fille équipée d'un microcontrôleur ATMega328p et d'un écran LCD contrôlé par un HD44780, en plus de la conception d'un Shield et d'un ordonnanceur. Cette carte sera connectée à une carte mère via un connecteur HE10 et devra gérer l'affichage de caractères ASCII, ainsi que quelques commandes spécifiques VT100. De plus, nous ajouterons une RAM SPI pour améliorer les performances de gestion de l'affichage.


Lien du git : https://gitea.plil.fr/mbarret/pico_kaoutar_maxime

Carte Fille (Écran LCD)

Description

La carte fille que nous avons développée contient les éléments suivants :

  • Un microcontrôleur ATMega328p pour la gestion de l'affichage.
  • Un écran LCD avec contrôleur HD44780 pour l'affichage de caractères ASCII.
  • Un potentiomètre 3310P pour ajuster la luminosité des cristaux liquides de l'écran.
  • Un connecteur HE10 pour relier la carte fille à la carte mère.
  • Un connecteur AVR ISP pour la programmation du microcontrôleur.
  • Une RAM SPI (e.g. FM24C04B-GTR) pour stocker temporairement les données d'affichage.

Fonctionnalités Demandées

  • Gestion de l'écran LCD : Le microcontrôleur doit gérer les écrans HD44780 avec 1, 2, 3 ou 4 lignes et afficher des caractères ASCII.
  • Saut de ligne et retour chariot : Les caractères spéciaux `\n` et `\r` doivent être pris en compte.
  • Commandes VT100 : Quelques codes VT100, notamment ceux pour le déplacement du curseur, doivent être supportés.
  • Défilement automatique : Le contrôleur HD44780 doit être configuré pour activer le mode de défilement automatique.
  • Connexion SPI : La carte mère doit envoyer les commandes via SPI. Le premier octet envoyé doit être le code commande `0x01`, suivi des caractères à afficher.
  • Vérification de la disponibilité de l'écran : La carte fille met la ligne SPI en état haut lorsque l'affichage est en cours. La carte mère doit attendre que l'état soit bas avant de transmettre de nouvelles données, ou envoyer des commandes `0xFF` pour vérifier si l'écran est prêt.

Ajout d'une RAM SPI

Pour complexifier la carte fille, nous ajouterons une RAM SPI (e.g. FM24C04B-GTR). Cette mémoire permet de stocker temporairement les caractères reçus par SPI avant leur affichage. Le microcontrôleur de la carte fille rafraîchit régulièrement l'écran en lisant les données de la RAM, ce qui améliore la fluidité de l'affichage et permet de libérer la carte mère plus rapidement.

Nous avons choisi la RAM FM25W256-G disponible chez RS Component. https://fr.rs-online.com/web/p/memoires-fram/1254228?gb=s

Voici un schéma simplifié de la communication entre les composants :

  • La carte mère écrit dans la RAM SPI via le bus SPI.
  • Le microcontrôleur de la carte fille lit les données de la RAM SPI et les envoie à l'écran LCD pour mise à jour régulière.

Schéma de Connexion

CarteEcran.pdf

Ajout de sélecteurs

Ajouter des sélecteurs permet de choisir entre le microprocesseur et la connexion SPI venant de la carte mère. Pour cela, nous avons fait une simulation sur LTSpice. Voici le schéma et sa simulation :

Circuit de simulation.png
Simulation.png



La fonction PULSE nous permet de simuler le SELECT. Nous avons pris des modèles de transistors déjà sur le logiciel pour pouvoir simuler.

On remarque que, sur la simulation, un des transistors PMOS est inversee. Quand un des PMOS est passant, l'autre est bloquant. Cette simulation nous permet de valider notre sélecteur pour notre carte écran. Nous avons reproduit ce schéma dans Kicad.

Carte écran soudée

Nous avons terminé de souder complétement notre carte et il est temps de la tester. Pour vérifier le bon fonctionnement de notre travail, nous utiliserons un code test dédié à notre écran HD44780 fournis par la librairie du même nom sur l'IDE Arduino. Cette méthode de test nous convient parfaitement puisque nous voulons uniquement vérifier son fonctionnement.

Malheureusement, le code ne fonctionne pas directement à cause de mauvais branchement sur notre connecteur. On avait relié directement les pins Enable, et RS du connecteur écran à notre 5V. Nous avons du alors faire du bricolage pour la réparer. Pour ce faire, nous avons donc coupé les piste du RS et Enable pour les relier à des pins non utilisés du microcontrôleur.

Resolution carte ecran.png

Après ces réparations, on arrive à afficher le texte voulu grâce au code d'exemple Arduino :

Ecran.jpg

Il nous faut désormais tester notre carte écran en code C. Pour ce faire, nous avons récupéré le code des Masters I2L et mis à jour les fuses de l'atmega328p. Cependant lors de l'upload du code, notre Atmega n'est plus détecté. Nous devons alors vérifier la clock par le biais d'un oscilloscope.

Cette carte a du être recommencée car après l'ajout de commande changeant les fuses du microprocesseur, ce dernier n'était plus reconnu. Ainsi nous avons pris le temps de resouder une carte écran.

Apres cela un autre problème (cette fois ci de circuit) est que nous perdons 1,5V entre le Shield et la carte écran. Apres quelque test au multimètre sans réponse nous avons vérifié la clock au niveau du Shield et au niveau de la carte écran.

Clock SPI sur le Shield
Clock SPI sur la carte écran





Cela est selon nous la cause de non fonctionnement du transfert de donnée entre les deux cartes.

Ordonnanceur et Gestion du Contexte des Tâches

Introduction

L'ordonnanceur est un composant clé de notre pico-ordinateur, assurant la gestion et l'exécution ordonnée des différentes tâches. Il permet un partage équitable du temps processeur grâce à un algorithme de round-robin. De plus, il vérifie la disponibilité des ressources matérielles, comme les lignes SPI et les périphériques LED.

La gestion du contexte des tâches est assurée par des macros de sauvegarde (`SAVE_REGISTERS`) et de restauration (`RESTORE_REGISTERS`) des registres, permettant ainsi d'interrompre et de reprendre l'exécution de chaque tâche.

Structure des Tâches

Chaque tâche est représentée par la structure `process` :

typedef struct process {
    uint16_t stackPointer; // Adresse du pointeur de pile, définissant le contexte de la tâche
    void (*addressFunction)(void); // Adresse de la fonction à exécuter
    int state; // État de la tâche (0 = prête, 1 = en cours, 2 = terminée)
} process;

Dans ce programme, `stackPointer` stocke la position de la pile de chaque tâche, `addressFunction` est un pointeur vers la fonction de la tâche, et `state` représente l’état actuel de la tâche.

Initialisation des Tâches

Les tâches sont initialisées dans le tableau `task`, contenant des fonctions telles que `Serial_Led`, `LED1_blink`, et `Serial_Message`, chacune avec son pointeur de pile initial :

process task[NB_TASKS] = {
    {0x700, Serial_Led, 0},
    {0x600, LED1_blink, 0},
    {0x500, Serial_Message, 0}
};

Macros de Sauvegarde et Restauration des Registres

Les macros `SAVE_REGISTERS()` et `RESTORE_REGISTERS()` permettent de sauvegarder et de restaurer les registres lors du changement de tâche :

#define RESTORE_REGISTERS() \
asm volatile ( \
    /* Code pour restaurer les registres ici */ \
);

#define SAVE_REGISTERS() \
asm volatile ( \
    /* Code pour sauvegarder les registres ici */ \
);

Ces macros utilisent des instructions d’assembleur pour pousser et dépiler les registres, préservant ainsi l’état complet de la tâche au moment de l'interruption.

Ordonnancement des Tâches

L'ordonnanceur suit l'algorithme round-robin. La fonction `scheduler()` passe d'une tâche à la suivante dans le tableau `task` :

void scheduler() {
    currentTask++;
    if (currentTask == NB_TASKS) currentTask = 0; // Retour à la première tâche après la dernière
}

Gestion des Interruptions

L'interruption liée au minuteur (`TIMER1_COMPA_vect`) déclenche l'ordonnanceur et le changement de tâche, en sauvegardant et restaurant le contexte :

ISR(TIMER1_COMPA_vect, ISR_NAKED) {
    SAVE_REGISTERS();
    task[currentTask].stackPointer = SP;
    scheduler();
    SP = task[currentTask].stackPointer;
    RESTORE_REGISTERS();
    asm volatile("reti");
}
  • `SAVE_REGISTERS()` : sauvegarde le contexte de la tâche courante.
  • `scheduler()` : passe à la tâche suivante.
  • `RESTORE_REGISTERS()` : restaure le contexte de la nouvelle tâche.
  • `asm volatile("reti");` : retourne de l'interruption en restaurant l'état.

Initialisation du Système

Les fonctions d'initialisation préparent les ressources, telles que les LED et l'UART, pour la communication série.

void LED_init() {
    DDRC |= (1<<LED1) | (1<<LED2);
    DDRD |= (1<<LED3) | (1<<LED4) | (1<<LED5);
    PORTC |= (1<<LED1) | (1<<LED2);
    PORTD |= (1<<LED3) | (1<<LED4) | (1<<LED5);
}

Fonctions des Tâches

Chaque tâche a une fonction dédiée. Par exemple, `LED1_blink` fait clignoter une LED, et `Serial_Message` envoie des messages en série.

void LED1_blink() {
    while(1) {
        PORTC ^= (1<<LED1);
        _delay_ms(150);
    }
}

Ces fonctions sont répétitives pour les autres LED, avec des délais différents pour créer des clignotements variés.

Communication Série

L'initialisation de l'UART et les fonctions de réception et de transmission sont définies pour permettre la communication série :

void USART_Init(unsigned int ubrr) {
    UBRR0H = (unsigned char)(ubrr>>8);
    UBRR0L = (unsigned char)ubrr;
    UCSR0B = (1<<RXEN0) | (1<<TXEN0);
    UCSR0C = (1<<USBS0) | (3<<UCSZ00);
}

unsigned char USART_Receive(void) {
    while (!(UCSR0A & (1<<RXC0)));
    return UDR0;
}

Fonction `main`

La fonction `main` configure les LED, l'UART, et initialise l'ordonnanceur avant de lancer la première tâche :

int main(void) {
    cli();
    LED_init();
    USART_Init(MYUBRR);
    init_minuteur(256, PERIODE);
    for(int i = 1; i < NB_TASKS; i++) init_pile(i);
    SP = task[0].stackPointer;
    sei();
    task[0].addressFunction();
}
  1. `cli()` : désactive les interruptions.
  2. `LED_init()` et `USART_Init()` : initialisent les LED et la communication série.
  3. `init_minuteur()` : initialise le minuteur avec une période.
  4. `init_pile(i)` : initialise la pile pour chaque tâche.
  5. `sei()` : réactive les interruptions.

L'ordonnanceur gère ainsi le contexte des tâches en suivant une séquence de round-robin, tout en assurant que chaque tâche exécute son cycle de manière autonome et en garantissant la disponibilité des ressources matérielles.

Fonction `wait()`

Comme première amélioration de votre ordonnanceur, il nous a été demandé de mettre en place un état endormi pour nos processus. Pour cela nous avons créer la procédure wait() suivante :

void wait(int time_ms){
    task[currentTask].state = SLEEP;
    task[currentTask].sleeping_time = time_ms;
    TCNT1 = 0;
    TIMER1_COMPA_vect();
}

On modifie aussi les procedures des LED :

void LED1_blink(){
    while(1){
        PORTC ^= (1<<LED1);
        _delay_ms(200);
        wait(5000);

    }
}

Et la structure :

typedef struct{
    uint16_t stackPointer; // stack pointer, défini le début du process
    void (*addressFunction)(void); // adresse de l'instruction en cours
    int state; // état de la fonction (Awake ou Idle)
    int sleeping_time;
}process;

En faisant cela dès qu'une tâche est appelée en présence d'un wait on met à jour son état ainsi que son temps d'attente. Par la suite, on modifie le scheduler() pour réveiller les tâches endormies si nécessaire :

void scheduler (){
  for(int i=0;i<NB_TASKS;i++){
    if(task[i].state==SLEEP){
        task[i].sleeping_time -= REDUCE_TIME;
        if(task[i].sleeping_time <= 0){
            task[i].sleeping_time = 0;
            task[i].state = AWAKE;
        }
    }
}
do{
  currentTask ++;
  if(currentTask >= NB_TASKS) currentTask = 0; // ordonnanceur en mode Round Robin
  }while(task[currentTask].state == SLEEP);
}

Avec ces modifications nous avons réussi à obtenir le résultat suivant :

Nous avons eu un problème avec la LED rouge. En effet, apres de tres nombreuses tentatives, nous n'avions pas reussi a la faire clignoter. Nous avons abordé plusieurs methode pour débugger notre code comme manipuler la LED rouge seule. C'est plus tard que nous avons compris que le problème venait de l'utilisation du pin PD1 de notre carte. Le pin PD1 peut aussi être utilisé pour gérer la connexion RX. Ainsi, quand nous voulions utiliser l'instruction USART_Init(MYUBRR); cela ne permettait pas de faire clignoter la LED. Cette LED sera donc inutilisée dans la suite du projet si nous utilisons l'UART.

Carte Shield

Description

La carte Shield est une carte mère qui sert à connecter et contrôler divers périphériques, notamment la carte fille (écran), un clavier, et d'autres composants. Nous avons conçu cette carte pour faciliter la gestion des communications SPI entre la carte mère et les différentes cartes filles.

Connectivité SPI

La carte Shield utilise plusieurs lignes SPI pour communiquer avec les cartes filles. Chaque périphérique, comme l'écran, est assigné à une ligne SPI spécifique. La carte Shield vérifie également la disponibilité des périphériques avant d'envoyer des commandes via SPI. Nous avons effectué plusieurs test afin de vérifier le bon fonctionnement de notre carte Shield. Nous avons tester le fonctionnement des LED, la connectivité de la carte SD, et nous avons utiliser le Shield pour faire un timer sur un afficheur 7 segments.

Test des LEDs
Test de la carte SD
Timer sur un afficheur 7 segments


Schéma de Connexion

Voici notre Schematic Kicad pour le Shield:

Picoshield.pdf

Bilan de puissance

Afin de déterminer la puissance consommée par notre carte, et se coordonner avec le binôme s'occupant de la carte mère, nous effectuons un bilan de puissance. Pour chaque composant nous nous sommes référés à leur datasheets :

  • HD44780 : 150 µA (datasheet)
  • Ecran LCD 2x16 : 1,2 mA (datasheet)
  • LED de Test : 1,7/510 = 3 mA donc comme nous en avons 2 on a besoin de 6 mA
  • Atmega : 14 mA (datasheet)
  • FRAM25 : 2 mA (datasheet)
  • MUX CD405 : 10 mA (datasheet)

Au total, notre carte consommera 0,17 W.

Tests Réalisés

Tests de l'Ordonnanceur

  • Gestion des tâches : Nous avons simulé plusieurs tâches, dont l'affichage et la gestion du clavier, et vérifié que l'ordonnanceur passe correctement d'une tâche à l'autre.
  • En utilisant le logiciel minicom nous avons pu lancer une 2eme tâche en parallèle d'une première tâche. En tapant "a" une deuxième LED se met à clignoter.
Test de l'ordonnanceur : lancer une tâche.


  • Pour tester la connexion série du Shield avec le 7 segment on utilise l'outil Minicom de nouveau afin de faire apparaitre un caractère sur le 7 segment. Pour cela on utilise la fonction Serial Message tout en modifiant le reste du code en conséquence :
void Serial_Message(){ //lecture, ecriture port serie et ecriture sur 7 Seg
    unsigned char data;
    spi_display_cmd1(0x76);
    _delay_ms(100);
    while(1){
        data = USART_Receive();
        if (data >= '0' && data <= '9') {
            spi_display_data(data, '0', '0', '0');
        }
        USART_Transmit(data);
    }
}

En utilisant cette fonction nous avons pu afficher un caractère. Cependant nous avons remarquer un retard lors de l'affichage du caractère. Nous avons essayer plusieurs méthode comme sectionner ce que l'on voulait faire en 3 taches mais cela n'a pas fonctionner. Apres longues observation de l'exécution de notre programme, nous avons vu que nous pouvions taper nos caractères sur Minicom, mais l'écran LCD ne se mettait a jour seulement lorsque la LED2 était allumée. Cela montre que le problème était que la LED puisqu'elle était connectée au pin CS. Le fait de la faire clignoter empêchait l'affichage. Apres correction (la LED2 devait rester allumée) :

Tests de la RAM SPI

Pour tester la mémoire RAM SPI, plusieurs étapes ont été mises en œuvre pour garantir la bonne communication et la gestion des données. Voici les principales actions réalisées :

Fonctions de gestion de l'accès à la RAM

  • Les fonctions ram_select et ram_deselect ont été ajoutées pour assurer un accès exclusif à la mémoire :
void ram_select() {
    SPI_PORT &= ~(1 << SPI_SS); // Sélectionne la RAM
}

void ram_deselect() {
    SPI_PORT |= (1 << SPI_SS); // Désélectionne la RAM
}
  • Les fonctions select_microcontroller et select_shield ont été introduites pour basculer entre le Microcontrôleur et le Shield :
void select_microcontroller() {
    PORTC |= (1 << DATA0); // Active le microcontrôleur
    _delay_ms(1);         // Temps de stabilisation
}

void select_shield() {
    PORTC &= ~(1 << DATA0); // Active le Shield
    _delay_ms(1);          // Temps de stabilisation
}

Opérations de base sur la RAM

  • Lecture (RAM_read), écriture (RAM_write), et effacement (RAM_clear) ont été implémentés pour interagir avec la mémoire via le bus SPI.
  • Exemple d'écriture :
void RAM_write(uint16_t address, char *data) {
    RAM_write_enable();
    CS_LOW();
    spi_exch(WRITE);
    spi_exch((address >> 8) & 0xFF);
    spi_exch(address & 0xFF);
    for (uint8_t i = 0; i < strlen(data); i++) {
        spi_exch(data[i]);
    }
    RAM_write_disable();
    CS_HIGH();
}
  • Exemple de lecture :
void RAM_read(uint16_t address, char *buffer, uint8_t length) {
    CS_LOW();
    spi_exch(READ);
    spi_exch((address >> 8) & 0xFF);
    spi_exch(address & 0xFF);
    for (uint8_t i = 0; i < length; i++) {
        buffer[i] = spi_exch(0x00);
    }
    buffer[length] = '\0';
    CS_HIGH();
}

Validation des fonctionnalités

  • Un test basique avec des données simples a été effectué pour vérifier que les données écrites pouvaient être lues sans erreur.
  • Un test de stress par brute force a été réalisé pour écrire et lire des données massivement tout en affichant les résultats avec un printf. Cela a permis de confirmer la robustesse du système.

Résultat des tests

  • Le premier test (illustré dans l'image ci-dessous) montre que la RAM SPI fonctionne correctement, avec des données transmises et récupérées sans perte. Cependant, on remarque l'apparition de différents symboles avant notre message affiché depuis la RAM. On a donc émis deux hypothèses :
    • Il existe des zones mémoire de la RAM qui contient des données ne pouvant être modifiées et ce sont ces données là que nous lisons avant notre message.
    • Notre fonction pour clear la RAM avec des '0' n'est pas bien fonctionnelle.

Avec une lecture plus approfondie de la datasheet, il n'est fait nulle part mention d' "une zone interdite" ou quelque chose qui pourrait s'en rapprocher. C'est pourquoi on pense que notre seconde hypothèse est la plus probable malgré une fonction qui, au premier regard, semble correcte.

La RAM fonctionne
  • Dans une tentative de faire disparaître ces symboles non désirés, nous avons essayé une commande printf de dernier espoir ("Printf ici"). A notre grande surprise, le message de cette commande était directement affiché sur notre écran. Nous avons donc fait des recherches sur ce sujet et il semblerait qu'initialement, il aurait fallu réalisé une fonction de redirection de flux afin de pouvoir visualiser le contenu du print. Or nous n'avons rien réalisé de tel. D'après ce que nous avons trouvé sur Internet, certains dispositif tel que des boards Arduino ou autres, ont directement une fonction de redirection de flux d'implémentée. Nous soupçonnons donc le programmateur ou le microcontrôleur utilisé d'en avoir une également.
Printf.jpg

Conclusion

Tests de la RAM SPI

La mémoire RAM SPI ne fonctionne pas encore comme prévu avec notamment ces caractères non souhaités.

Malgré nos efforts, nous avons remarqué une chute de tension significative de 1,5 V entre le Shield et la carte écran. Cette chute pourrait expliquer les dysfonctionnements observés dans la communication SPI. Bien que la carte écran fonctionne correctement lorsqu’elle est utilisée seule, elle ne parvient pas à établir une communication via le Shield. Cependant, les différentes étapes de conception et de test nous semblent alignées, et nous pensons être sur la bonne voie pour résoudre ce problème.

Ordonnanceur et Gestion des Tâches

Concernant l’ordonnanceur, toutes les consignes ont été respectées et implémentées avec succès. Le scheduler suit une approche Round-Robin efficace pour gérer les tâches concurrentes. Grâce aux interruptions et à une gestion des délais non bloquants, il permet une répartition optimale du temps CPU entre les tâches, tout en garantissant une bonne réponse pour les processus critiques. Les tests ont validé le bon fonctionnement des différentes tâches, comme la gestion des LEDs, la communication série et l’affichage.

Pistes d’amélioration

Pour améliorer le fonctionnement global et résoudre les problèmes liés au SPI, voici quelques pistes à explorer :

  • Identifier la ou les causes de la chute de tension entre le Shield et la carte écran, éventuellement en vérifiant les connexions, la qualité des câbles ou l’alimentation.
  • Tester la communication SPI avec d'autres configurations ou composants pour valider chaque élément individuellement.

En résumé, bien que des défis restent à résoudre pour la partie SPI, nous avons validé l’ensemble des fonctionnalités de l’ordonnanceur. Nous sommes confiants que les ajustements proposés nous permettront d’obtenir un système pleinement fonctionnel.