« SE4Binome2023-1 » : différence entre les versions

De projets-se.plil.fr
Aller à la navigation Aller à la recherche
 
(7 versions intermédiaires par un autre utilisateur non affichées)
Ligne 2 : Ligne 2 :


== Soudure et Test du SHIELD ==
== Soudure et Test du SHIELD ==
Au cours des 2 premières séances nous avons soudé et testé notre Shield
Au cours des 2 premières séances nous avons soudé et testé notre Shield.
[[Fichier:ShieldSoudé.jpg|thumb|center|300x300px|<div style="text-align:center;">Shield soudé et monté sur Arduino</div>]]
[[Fichier:ShieldSoudé.jpg|thumb|center|300x300px|<div style="text-align:center;">Shield soudé et monté sur Arduino</div>]]
   
   
Sur l'un des connecteurs, la led est abîmée dû à une soudure excessive. Sachant que les leds sont uniquement utilisées en tant qu'indicateurs pour savoir quelle carte fille à le contrôle et que nous n'en utiliserons pas plus de 3 pour tester nos programmes, cela ne vas pas avoir d'influence négative.
Sur l'un des connecteurs, la led est abîmée dû à une soudure excessive. Sachant que les leds sont uniquement utilisées en tant qu'indicateurs pour savoir quelle carte fille à le contrôle et que nous n'en utiliserons pas plus de 3 pour tester nos programmes, cela ne va pas avoir d'influence négative.
Le potentiomètre permet de régler le niveau de luminosité de l'écran et le connecteur HE10 permettra de comuniquer avec l'écran.
Le potentiomètre permet de régler le niveau de luminosité de l'écran et le connecteur HE10 permettra de communiquer avec l'écran.
Les problèmes rencontrés sont les suivants : Au départ, la carte ne fonctionnait pas, le soucis étant le quartz mal soudé, c'est à l'aide d'un pistolet à air chaud que celui-ci à été ressoudé. Un autre problème était le reset de la carte fille qui ne fonctionnait pas non plus, une ressoudure classique a permis de le faire fonctionner.
Les problèmes rencontrés sont les suivants : Au départ, la carte ne fonctionnait pas, le souci étant le quartz mal soudé, c'est à l'aide d'un pistolet à air chaud que celui-ci a été ressoudé. Un autre problème était le reset de la carte fille qui ne fonctionnait pas non plus, une ressoudure classique a permis de le faire fonctionner.


{| class=wikitable style="width:100%;"
{| class=wikitable style="width:100%;"
Ligne 35 : Ligne 35 :
[[Fichier:ShieldLed.jpg|thumb|center|300x300px|<div style="text-align:center;">Test des Leds du Shield</div>]]
[[Fichier:ShieldLed.jpg|thumb|center|300x300px|<div style="text-align:center;">Test des Leds du Shield</div>]]
|}
|}
Puis nous avons aussi soudé l'adaptateur HE10 pour l'utilisation de la matrice de leds[[Fichier:Adaptateur HE10.jpg|thumb|center|300x300px|<div style="text-align:center;">Adaptateur HE10 pour Matrice de leds</div>]]
Puis nous avons aussi soudé l'adaptateur HE10 pour l'utilisation de la matrice de leds.[[Fichier:Adaptateur HE10.jpg|thumb|center|300x300px|<div style="text-align:center;">Adaptateur HE10 pour Matrice de leds</div>]]


== Programmation de l'Ordonnanceur ==
== Programmation de l'Ordonnanceur ==
Nous commençons maintenant l'ordonnanceur, tout d'abord nous créeons la structure de nos Taches.
Nous commençons maintenant l'ordonnanceur, tout d'abord nous créons la structure de nos taches.
{| class="wikitable" style="width:50%;"
{| class="wikitable" style="width:50%;"
! scope="col" | Code
! scope="col" | Code
Ligne 51 : Ligne 51 :
</syntaxhighlight>  
</syntaxhighlight>  
|}
|}
Puis nous ajoutons les 2 premières taches demandées qui sont de faire clignoter 2 leds différentes à des timing différents.
Puis nous ajoutons les 2 premières taches demandées qui sont de faire clignoter 2 leds différentes à des timings différents.
{| class="wikitable" style="width:50%;"
{| class="wikitable" style="width:50%;"
! scope="col" | Code
! scope="col" | Code
Ligne 109 : Ligne 109 :
</syntaxhighlight>
</syntaxhighlight>
|}
|}
Nous avons décider de faire clignoter une LED (PORTD ^= 0x02) à chaques utilisations de la fonction ordonnanceur() dans l'ISR.
Nous avons décidé de faire clignoter une LED (PORTD ^= 0x02) à chaque utilisation de la fonction ordonnanceur() dans l'ISR.


La variable courant indique la Tache qui est actuellement en cours.
La variable courant indique la Tache qui est actuellement en cours.


A chaque chaques changements de tache, l'ISR se déclenche enregistre la tache en cours (les registres ainsi que le Stack Pointer), appel l'ordonnanceur, puis change "charge" la tache suivante.
A chaque changement de tache, l'ISR se déclenche enregistre la tache en cours (les registres ainsi que le Stack Pointer), appel l'ordonnanceur, puis change "charge" la tache suivante.


On oublie pas maintenant de générer l'interruption toute les 20ms, pour cela on choisit un prescaler de 1024 et un nombre de ticks de 312.
On n'oublie pas maintenant de générer l'interruption toute les 20ms, pour cela on choisit un prescaler de 1024 et un nombre de ticks de 312.
{| class="wikitable" style="width:50%;"
{| class="wikitable" style="width:50%;"
! scope="col" | Code
! scope="col" | Code
Ligne 137 : Ligne 137 :
</syntaxhighlight>
</syntaxhighlight>
|}
|}
Ainsi que d'initialiser les Taches pour assurer le bon fonctionnement de notre ordonnanceur.
Ainsi que d'initialiser les taches pour assurer le bon fonctionnement de notre ordonnanceur.
{| class="wikitable" style="width:50%;"
{| class="wikitable" style="width:50%;"
! scope="col" | Code
! scope="col" | Code
Ligne 179 : Ligne 179 :
Les 2 leds clignotent bien avec le bon timing, et la led témoin de l'ordonnanceur clignotent aussi, ce qui indique que l'ISR est bien actif.
Les 2 leds clignotent bien avec le bon timing, et la led témoin de l'ordonnanceur clignotent aussi, ce qui indique que l'ISR est bien actif.


Nous pouvons donc maitenant passer à la gestion des "sleeps".
Nous pouvons donc maintenant passer à la gestion des "sleeps".


En effet, l'utilisation de _delay_ms dans chaque tache n'est pas la manière la plus optimale de procéder. Et meme cela nous bloquera par la suite.
En effet, l'utilisation de _delay_ms dans chaque tache n'est pas la manière la plus optimale de procéder. Et même cela nous bloquera par la suite.


Nous ajoute donc à la structure de nos Taches, une structure InfoEndormi, qui, comme son nom l'indique nous donne des informations si la tache est endormie. A savoir la raison mais aussi des donnees comme par exemple le temps que cette tache doit passer endormie.
Nous ajoute donc à la structure de nos taches, une structure InfoEndormi, qui, comme son nom l'indique nous donne des informations si la tache est endormie. A savoir la raison mais aussi des données comme par exemple le temps que cette tache doit passer endormie.


Puis afin de remplacer _delay_ms, nous créeons une fonction Attente qui endors une tache pour un temps donné.
Puis afin de remplacer _delay_ms, nous créons une fonction Attente qui endort une tache pour un temps donné.
{| class="wikitable" style="width:100%;"
{| class="wikitable" style="width:100%;"
! scope="col" | Code
! scope="col" | Code
Ligne 222 : Ligne 222 :


|}
|}
Voila maitenant nos taches, il est important de créer une TacheZombie qui comme son nom l'indique est toujours révéillée.
Voilà maintenant nos taches, il est important de créer une TacheZombie qui comme son nom l'indique est toujours réveillée.
{| class="wikitable" style="width:50%;"
{| class="wikitable" style="width:50%;"
! scope="col" | Code
! scope="col" | Code
Ligne 255 : Ligne 255 :
</syntaxhighlight>
</syntaxhighlight>
|}
|}
Il est important maitenant de modifier notre ordonnanceur afin que celui-ci puisse recalculer le nouveau temps de sleep d'une tache et si ce temps devient inférieur ou ègale à 0 alors l'ordonnacnceur réveille la tache.
Il est important de modifier notre ordonnanceur afin que celui-ci puisse recalculer le nouveau temps de sleep d'une tache et si ce temps devient inférieur ou ègal à 0 alors l'ordonnanceur réveille la tache.
{| class="wikitable" style="width:50%;"
{| class="wikitable" style="width:50%;"
! scope="col" | Code
! scope="col" | Code
Ligne 294 : Ligne 294 :
</syntaxhighlight>
</syntaxhighlight>
|}
|}
Maintenant que cela est fait, nous pouvons ajouter 2 nouvelles taches qui sont la lecture et l'ecriture sur le port série.
Maintenant que cela est fait, nous pouvons ajouter 2 nouvelles taches qui sont la lecture et l'écriture sur le port série.


Pour cela, nous allons procéder avec des sémaphores. En effet, il ne peut y avoir que 1 seule tache en écriture ou en lecture, il faut donc que si plusieurs taches essaient de prendre la ressource, cette ressource soit donner à la première arrivé et que les autres taches en attendant patientent.
Pour cela, nous allons procéder avec des sémaphores. En effet, il ne peut y avoir que 1 seule tache en écriture ou en lecture, il faut donc que si plusieurs taches essaient de prendre la ressource, cette ressource soit donné au premier arrivé et que les autres taches en attendant patientent.
{| class="wikitable" style="width:50%;"
{| class="wikitable" style="width:50%;"
! scope="col" | Code
! scope="col" | Code
Ligne 335 : Ligne 335 :
</syntaxhighlight>
</syntaxhighlight>
|}
|}
On fait la meme chose pour la lecture.
On fait la même chose pour la lecture.


On oublie pas d'initialiser le port série avec les bons paramètres, créer les taches, puis on test.
On n'oublie pas d'initialiser le port série avec les bons paramètres, créer les taches, puis on test.
{| class="wikitable" style="width:100%;"
{| class="wikitable" style="width:100%;"
! scope="col" | Code
! scope="col" | Code
Ligne 353 : Ligne 353 :


</syntaxhighlight>
</syntaxhighlight>
| style="width:50%;" |[[Fichier:Lecture EcritureSerie.jpg|centré|vignette|356x356px|<div style="text-align:center;">Test de la lecture et de l'écriture sur le port série.(On écrie 9 caractères puis le shield nous renvoie les mêmes 9) </div>]]
| style="width:50%;" |[[Fichier:Lecture EcritureSerie.jpg|centré|vignette|356x356px|<div style="text-align:center;">Test de la lecture et de l'écriture sur le port série.(On écrit 9 caractères puis le shield nous renvoie les mêmes 9) </div>]]


|}
|}
On passe à la matrice de leds.
On souhaite pouvoir lire sur le port série et afficher ce qu'il s'y écrit. Nous procéderons uniquement avec les chiffres.
{| class="wikitable" style="width:100%;"
! scope="col" | Code
! scope="col" | Image
|-
| style="width:50%;" |
<syntaxhighlight lang="c" line="1">
void Tache_Matrice()
{
    int val = -1;
    DDRB |= (1<<2);
    PORTB |= (1<<2);
    _delay_ms(500);
    matrice_init(1);
    while(1)
    {
        char c = chaine[0];
        if(c == '0' && val != 0)
        {
            Afficher_0();
            val = 0;
        }
        if(c == '1' && val != 1)
        {
            Afficher_1();
            val = 1;
        }
        if(c == '2' && val != 2)
        {
            Afficher_2();
            val = 2;
        }
        if(c == '3' && val != 3)
        {
            Afficher_3();
            val = 3;
        }
        if(c == '4' && val != 4)
        {
            Afficher_4();
            val = 4;
        }
        if(c == '5' && val != 5)
        {
            Afficher_5();
            val = 5;
        }
        if(c == '6' && val != 6)
        {
            Afficher_6();
            val = 6;
        }
        if(c == '7' && val != 7)
        {
            Afficher_7();
            val = 7;
        }
        if(c == '8' && val != 8)
        {
            Afficher_8();
            val = 8;
        }
        if(c == '9' && val != 9)
        {
            Afficher_9();
            val = 9;
        }
    }
}
</syntaxhighlight>
| style="width:50%;" |[[Fichier:Test Matrice de Leds.jpg|centré|vignette|474x474px|<div style="text-align:center;">Test de la matrice de leds (On écrit un chiffre sur le port série puis la matrice affiche le même chiffre) </div>]]
|}
Pour finir, on fait la même chose pour l'afficheur 7 segments.
{| class="wikitable" style="width:100%;"
! scope="col" | Code
! scope="col" | Image
|-
| style="width:50%;" |
<syntaxhighlight lang="c" line="1">
void Tache_7Seg()
{
    DDRB |= (1<<2);     
    PORTB |= (1<<2);
    _delay_ms(500);
    seg_init();
    while(1)
    {
        char c = chaine[0];
        seg_affiche(c);
    }
}
</syntaxhighlight>
| style="width:50%;" |[[Fichier:Test afficheur 7 segments.png|centré|vignette|355x355px|<div style="text-align:center;">Test de l'afficheur 7 segments. (On écrit un chiffre sur le port série puis l'afficheur affiche cette valeur.) </div>]]
|}
L'ordonnanceur est maintenant plus ou moins complet, toutes les taches fonctionnent individuellement.
Malheureusement nous ne pouvons pas essayer le tout ensemble car l'afficheur 7 segments et la matrice de leds communique tous les 2 via SPI. Il faudrait donc rajouter des sémaphores par exemple pour savoir, si la ressource est utilisée ou non. Par manque de temps, nous ne l'avons donc pas fait.


= Carte électronique numérique =
= Carte électronique numérique =
Ligne 362 : Ligne 463 :
Nous avons choisi la Carte Fille : Ecran LCD  
Nous avons choisi la Carte Fille : Ecran LCD  


Pendant la 3ème séance nous avons finis le Schématic de la carte  
Pendant la 3ème séance nous avons fini le schematic de la carte.
[[Fichier:SCH.png|centré|vignette|700x700px|<div style="text-align:center;">Schematic de la Carte Fille : Ecran LCD</div>]]
[[Fichier:SCH.png|centré|vignette|700x700px|<div style="text-align:center;">Schematic de la Carte Fille : Ecran LCD</div>]]
Puis nous avons Routé la carte et envoyé à la Fabrication
Puis nous avons Routé la carte et envoyé à la Fabrication.
[[Fichier:PCB LCD.png|centré|vignette|550x550px|<div style="text-align:center;">Routage de la Carte Fille : Ecran LCD</div>]]
[[Fichier:PCB LCD.png|centré|vignette|550x550px|<div style="text-align:center;">Routage de la Carte Fille : Ecran LCD</div>]]


Ligne 379 : Ligne 480 :


== Programmation de la carte LCD ==
== Programmation de la carte LCD ==
Tout d'abord, nous avons voulu tester le bon fonctionnement de notre carte mais surtout du bon controle de l'ecran HD44780 grâce à l'IDE Arduino.
Tout d'abord, nous avons voulu tester le bon fonctionnement de notre carte mais surtout du bon contrôle de l'écran HD44780 grâce à l'IDE Arduino.
{| class="wikitable" style="width:100%;"
{| class="wikitable" style="width:100%;"
! scope="col" | Code
! scope="col" | Code
Ligne 409 : Ligne 510 :


|}
|}
Maintenant que cela fonctionne, nous pouvons commencer à écrire notre Librairie pour faire fonctionner notre carte.
Maintenant que cela fonctionne, nous pouvons commencer à écrire notre librairie pour faire fonctionner notre carte.


=== Librairie HD44780 ===
=== Librairie HD44780 ===
Nous avons commencer par reprendre la librairie d'ancien élève, merci à eux pour leur travail.
Nous avons commencé par reprendre la librairie d'ancien élève, merci à eux pour leur travail.


Dans notre cas, pour l'utilisation la plus simple nous avons besoin de plusieurs fonctions. La première écrit un caractère sur l'écran, la deuxième copie une ligne et la dernière efface une ligne.
Dans notre cas, pour l'utilisation la plus simple nous avons besoin de plusieurs fonctions. La première écrit un caractère sur l'écran, la deuxième copie une ligne et la dernière efface une ligne.
Ligne 438 : Ligne 539 :
HD44780_EraseLine  :
HD44780_EraseLine  :


Permet d'effacer une ligne. Pour ce faire on se place au début de la ligne voulue, et on écrit une suite de caractère vide.
Permet d'effacer une ligne. Pour ce faire on se place au début de la ligne voulue, et on écrit une suite de caractères vide.
{| class="wikitable" style="width:50%;"
{| class="wikitable" style="width:50%;"
! scope="col" |Code
! scope="col" |Code
Ligne 461 : Ligne 562 :
Permet d'écrire un caractère sur l'écran. Chaque caractère écrit sur la seconde ligne est placé dans un tableau de caractère.  
Permet d'écrire un caractère sur l'écran. Chaque caractère écrit sur la seconde ligne est placé dans un tableau de caractère.  


Lorsqu'on arrive au bout d'une ligne : Si nous sommes à la première ligne (Ligne 0), on passe à la seconde (Ligne 1) et on se replace au début des colonnes. Si nous sommes à la seconde ligne, on efface les deux lignes et on copie la seconde ligne sur la première ligne de l'écran, et on se replace au début de la seconde.   
Lorsqu'on arrive au bout d'une ligne : si nous sommes à la première ligne (Ligne 0), on passe à la seconde (Ligne 1) et on se replace au début des colonnes. Si nous sommes à la seconde ligne, on efface les deux lignes et on copie la seconde ligne sur la première ligne de l'écran, et on se replace au début de la seconde.   
{| class="wikitable" style="width:50%;"
{| class="wikitable" style="width:50%;"
! scope="col" |Code
! scope="col" |Code
Ligne 496 : Ligne 597 :
|-
|-
| style="width:50%;" |[[Fichier:Video test de la librairie.mp4|centré|vignette|<div style="text-align:center;">Video test de la librairie</div>]]
| style="width:50%;" |[[Fichier:Video test de la librairie.mp4|centré|vignette|<div style="text-align:center;">Video test de la librairie</div>]]
|}Super, ca fonctionne !   
|}Super, ça fonctionne !   


Désormais, nous voulons que notre écran affiche ce qu'il recoit via le bus SPI.   
Désormais, nous voulons que notre écran affiche ce qu'il reçoit via le bus SPI.   


=== Communication SPI ===
=== Communication SPI ===
Pour cela il nous faut initialiser le maitre (un arduino UNO) et l'esclave (notre carte LCD).   
Pour cela il nous faut initialiser le maitre (un arduino UNO) et l'esclave (notre carte LCD).   


Puis que le maitre transmette des données et que l'esclave les recoivent.   
Puis, que le maître transmette des données et que l'esclave les reçoive.   
{| class="wikitable" style="width:100%;"
{| class="wikitable" style="width:100%;"
! scope="col" | Code Maitre
! scope="col" | Code Maitre
Ligne 576 : Ligne 677 :


==== Problèmes rencontrés ====
==== Problèmes rencontrés ====
Plusieurs pistes ont été considérées pour comprendre le problème qu'on avait avec le SPI, était-ce l'arduino UNO? Notre connecteur? Le shield? Notre carte?
Plusieurs pistes ont été considérées pour comprendre le problèmes qu'on avait avec le SPI, était-ce l'arduino UNO? Notre connecteur? Le shield? Notre carte?
Au départ, par soucis de praticité, nous voulions utiliser l'arduino UNO qui disposait de fonctionnalités de test simple avant de passer au shield, mais à chaque essai, le SPI ne fonctionait pas correctement bien qu'on pouvait observer à l'oscilloscope que le message était bien envoyé à notre carte.
Au départ, par souci de praticité, nous voulions utiliser l'arduino UNO qui disposait de fonctionnalités de test simple avant de passer au shield, mais à chaque essai, le SPI ne fonctionnait pas correctement bien qu'on pouvait observer à l'oscilloscope que le message était bien envoyé à notre carte.


Finalement, nous sommes directement passé à la communication SPI avec notre shield, qui cette fois-ci, permettait d'avoir un résultat satisfaisant. Le résultat n'était pas parfait car on remarquait que la position des fils déterminait si la communication allait fonctionner, il fallait donc trouver la position qui permettait cette communication.
Finalement, nous sommes directement passé à la communication SPI avec notre shield, qui cette fois-ci, permettait d'avoir un résultat satisfaisant. Le résultat n'était pas parfait car on remarquait que la position des fils déterminait si la communication allait fonctionner, il fallait donc trouver la position qui permettait cette communication.
Pour régler ce problème, nous avons déterminé la fréquence de travail du SPI utilisé par le shield et notre carte, pour vérifier qu'il n'y ait pas de problèmes de synchronisation ou que la fréquence utilisée soit bien adaptée. Pour ce faire nous avons observé à l'oscilloscope les différentes divisions de fréquences possibles en vérifiant en même temps que la communication SPI fonctionne bien. Les résultats sont surprenant, car peu importe la fréquence de travail, aucun problème n'a été détécté.
Pour régler ce problème, nous avons déterminé la fréquence de travail du SPI utilisé par le shield et notre carte, pour vérifier qu'il n'y ait pas de problème de synchronisation ou que la fréquence utilisée soit bien adaptée. Pour ce faire nous avons observé à l'oscilloscope les différentes divisions de fréquences possibles en vérifiant en même temps que la communication SPI fonctionne bien. Les résultats sont surprenants, car peu importe la fréquence de travail, aucun problème n'a été détécté.


=== Communication SPI par interruption ===
=== Communication SPI par interruption ===
Il nous faut maitenant communiquer par SPI par interruption car cela offre certains avantages par rapport à la communication SPI standard sans interruption, en particulier en termes d'efficacité et de gestion du temps. En effet, l'utilisation d'interruptions permet au microcontrôleur de gérer d'autres tâches pendant les transferts SPI. Plutôt que d'attendre de manière synchrone la fin d'une transaction SPI, le microcontrôleur peut être informé via une interruption lorsque les données sont prêtes à être lues ou écrites. Cela permet un multitâche plus efficace. Ce qui va nous être trés pratique sachant que le projet comporte plusieurs cartes filles.
Il nous faut désormais communiquer par SPI par interruption car cela offre certains avantages par rapport à la communication SPI standard sans interruption, en particulier en matière d'efficacité et de gestion du temps. En effet, l'utilisation d'interruptions permet au microcontrôleur de gérer d'autres tâches pendant les transferts SPI. Plutôt que d'attendre de manière synchrone la fin d'une transaction SPI, le microcontrôleur peut être informé via une interruption lorsque les données sont prêtes à être lues ou écrites. Cela permet un multitâche plus efficace. Ce qui va nous être très pratique sachant que le projet comporte plusieurs cartes filles.


Pour cela il nous faut coté maitre et esclave activé le mode SPI Interrupt lors de l'initialisation du SPI, on rajoute donc <code>SPCR |= (1<<SPIE);</code> lors à l'initialisation.
Pour cela il nous faut coté maitre et esclave activé le mode SPI Interrupt lors de l'initialisation du SPI, on rajoute donc <code>SPCR |= (1<<SPIE);</code> lors à l'initialisation.


Ensuite on génère l'interruption coté esclave à la fin de l'initalisation et on configure notre ISR.
Ensuite on génère l'interruption côté esclave à la fin de l'initialisation et on configure notre ISR.


Le maitre à plusieurs type d'envoie possible :
Le maitre à plusieurs types d'envoi possible :


0x00 : Demande le type de la carte.
0x00 : Demande le type de la carte.
Ligne 597 : Ligne 698 :
0xff  : Met fin à la communication.
0xff  : Met fin à la communication.


Voici comment l'ISR lui fonctionne, si je recois 0x00 je renvoie on type, 0x01 je passe en mode EnReception.  
Voici comment l'ISR lui fonctionne, si je reçois 0x00 je renvoie on type, 0x01 je passe en mode EnReception.  


En mode EnReception : lorsque je recois un octet je l'ajoute à un buffer, lorsque je recois 0xff je désactive le mode en Reception.
En mode EnReception : lorsque je reçois un octet je l'ajoute à un buffer, lorsque je recois 0xff je désactive le mode en Reception.


Dans mon main, une fois que mon mode EnRecption est désactivé mais que mon indice du buffer et supérieur à 0 alors je peux traiter mes octets.
Dans mon main, une fois que mon mode EnRecption est désactivé mais que mon indice du buffer et supérieur à 0 alors je peux traiter mes octets.
Ligne 646 : Ligne 747 :


=== Gestion des caractères spéciaux ===
=== Gestion des caractères spéciaux ===
Nous avons décider de gérer 4 caractères spéciaux différents : \n , \t, \b, \r
Nous avons décidé de gérer 4 caractères spéciaux différents : \n , \t, \b, \r


Pour cela on créé la fonction <code>HD44780_Traitement</code> qui sera notre fonction principale.
Pour cela on crée la fonction <code>HD44780_Traitement</code> qui sera notre fonction principale.


Si on recoit un caractère classique alors on l'affiche grâce à <code>HD44780_WriteChar</code> précédemment crée.
Si on reçoit un caractère classique alors on l'affiche grâce à <code>HD44780_WriteChar</code> précédemment créé.


Sinon il sagit d'un caractère spécial et alors on le traite dans un switch case :
Sinon il sagit d'un caractère spécial et alors on le traite dans un switch case :
Ligne 823 : Ligne 924 :
|}
|}
=== Gestion des primitives systèmes ===
=== Gestion des primitives systèmes ===
Voici les primitives systèmes pour la carte mère, elle permettent de communiquer les chaînes de caractères à envoyer en préparant une transmission SPI constituée de l'octet de commande 0x01 et des données correspondants aux caractères.
Voici les primitives systèmes pour la carte-mère, elles permettent de communiquer les chaînes de caractères à envoyer en préparant une transmission SPI constituée de l'octet de commande 0x01 et des données correspondant aux caractères.


La fonction Reset_Display permet de reset l'affichage de l'écran et permet aussi à la carte mère de le remarquer en lui attribuant un numéro de port.
La fonction Reset_Display permet de reset l'affichage de l'écran et permet aussi à la carte-mère de le remarquer en lui attribuant un numéro de port.


La fonction Transmit_to_display est la fonction de transmission de données par SPI. Elle utilise les fonctions SPI créées précédemment pour envoyer les données à afficher.
La fonction Transmit_to_display est la fonction de transmission de données par SPI. Elle utilise les fonctions SPI créées précédemment pour envoyer les données à afficher.
Ligne 856 : Ligne 957 :
Pour conclure, faisons un bilan de ce qui a été produit:
Pour conclure, faisons un bilan de ce qui a été produit:


-Une carte fille fonctionnelle qui gère l'affichage d'un écran LCD
*Une carte fille fonctionnelle qui gère l'affichage d'un écran LCD
 
-Communication SPI par interruption
 
-Défilement de automatique de l'écran
 
-Gestion des caractères spéciaux
 
-Primitives systèmes pour la carte mère


-Ordonnanceur avec un système de priorité à l'aide de sémaphore
** Communication SPI par interruption
** Défilement automatique de l'écran
** Gestion des caractères spéciaux et code VT100
*Primitives systèmes pour la carte mère
*Ordonnanceur avec un système de priorité à l'aide de sémaphores


Malheureusement, par manque de temps, nous n'avons pas pu faire communiquer les cartes filles ensemble en les connectant à la carte mère.  
Malheureusement, par manque de temps, nous n'avons pas pu faire communiquer les cartes filles ensemble en les connectant à la carte-mère. Anisi que finir à 100% l'ordonnanceur.  


Nous restons cependant très satisfait du résultat et des fonctionnalités de notre carte!
Nous restons cependant très satisfaits du résultat et des fonctionnalités de notre carte !


= Références utiles =
= Références utiles =

Version actuelle datée du 2 février 2024 à 08:54

Ordonnanceur

Soudure et Test du SHIELD

Au cours des 2 premières séances nous avons soudé et testé notre Shield.

Shield soudé et monté sur Arduino

Sur l'un des connecteurs, la led est abîmée dû à une soudure excessive. Sachant que les leds sont uniquement utilisées en tant qu'indicateurs pour savoir quelle carte fille à le contrôle et que nous n'en utiliserons pas plus de 3 pour tester nos programmes, cela ne va pas avoir d'influence négative. Le potentiomètre permet de régler le niveau de luminosité de l'écran et le connecteur HE10 permettra de communiquer avec l'écran. Les problèmes rencontrés sont les suivants : Au départ, la carte ne fonctionnait pas, le souci étant le quartz mal soudé, c'est à l'aide d'un pistolet à air chaud que celui-ci a été ressoudé. Un autre problème était le reset de la carte fille qui ne fonctionnait pas non plus, une ressoudure classique a permis de le faire fonctionner.

Code Image
#include <avr/io.h>

void init_led(){
  DDRD=0x92;
  DDRC=0x09;
}

int main(){
  init_led();
  while(1)
    {
	PORTD=0x92;
	PORTC=0x09;
    }
  return 0;
}
Test des Leds du Shield

Puis nous avons aussi soudé l'adaptateur HE10 pour l'utilisation de la matrice de leds.

Adaptateur HE10 pour Matrice de leds

Programmation de l'Ordonnanceur

Nous commençons maintenant l'ordonnanceur, tout d'abord nous créons la structure de nos taches.

Code
struct TacheInfo{
	void (*depart)(void);
	uint16_t SPointeur;
	int etat;
};

Puis nous ajoutons les 2 premières taches demandées qui sont de faire clignoter 2 leds différentes à des timings différents.

Code
void TacheA(){
    while(1)
    {
        PORTC ^= 0x08;
	_delay_ms(300);
    }
}

void TacheB(){
    while(1)
    {
        PORTD ^= 0x80;
	_delay_ms(500);
    }
}

struct TacheInfo Taches[2]={
  {TacheA,0x0600,0},
  {TacheB,0x0700,0}
};

On s'occupe maintenant de l'ordonnanceur, ainsi que de l'ISR.

Code
void ordonnanceur ()
{	
	PORTD ^= 0x02;
	courant++;
	if (courant == NBR_TACHE) courant = 0;
}

ISR(TIMER1_COMPA_vect,ISR_NAKED)
{
	/* Sauvegarde du contexte de la tâche interrompue */
	SAVE_REGISTER();
	Taches[courant].SPointeur = SP;

	/* Appel à l'ordonnanceur */
	ordonnanceur();

	/* Récupération du contexte de la tâche ré-activée */
	SP = Taches[courant].SPointeur;
	RESTORE_REGISTER();
	asm volatile ( "reti" );
}

Nous avons décidé de faire clignoter une LED (PORTD ^= 0x02) à chaque utilisation de la fonction ordonnanceur() dans l'ISR.

La variable courant indique la Tache qui est actuellement en cours.

A chaque changement de tache, l'ISR se déclenche enregistre la tache en cours (les registres ainsi que le Stack Pointer), appel l'ordonnanceur, puis change "charge" la tache suivante.

On n'oublie pas maintenant de générer l'interruption toute les 20ms, pour cela on choisit un prescaler de 1024 et un nombre de ticks de 312.

Code
#define PRESCALER 1024
#define NB_TICK  312
#define CTC1  WGM12

void init_timer(){
TCCR1A = 0;  // No output pin connected, no PWM mode enabled
TCCR1B = 1<<CTC1; // No input pin used, clear timer counter on compare match
#if (PRESCALER==1024)
 TCCR1B |= (1<<CS12 | 1<<CS10);
#endif
OCR1A = NB_TICK;
TCNT1 = 0;
TIMSK1 = (1<<OCIE1A); // No overflow mode enabled, no input interrupt, output compare interrupt
}

Ainsi que d'initialiser les taches pour assurer le bon fonctionnement de notre ordonnanceur.

Code
void init_taches(int pid)
{
	int save=SP;
	SP=Taches[pid].SPointeur;
	uint16_t adresse=(uint16_t)Taches[pid].depart;
	asm volatile ("push %0 \n\t" : : "r" (adresse & 0x00ff));
	asm volatile ("push %0 \n\t" : : "r" ((adresse & 0xff00) >> 8));
	SAVE_REGISTER();
	Taches[pid].SPointeur=SP;
	SP=save;
}

On peut maintenant tester notre ordonnanceur.

Code Video Test
#define NBR_TACHE 2
int main(void){
	init_ports();
	init_timer();
	init_taches(1);
	sei();
	SP = Taches[courant].SPointeur;
	Taches[courant].depart();
	return 0;
}

Les 2 leds clignotent bien avec le bon timing, et la led témoin de l'ordonnanceur clignotent aussi, ce qui indique que l'ISR est bien actif.

Nous pouvons donc maintenant passer à la gestion des "sleeps".

En effet, l'utilisation de _delay_ms dans chaque tache n'est pas la manière la plus optimale de procéder. Et même cela nous bloquera par la suite.

Nous ajoute donc à la structure de nos taches, une structure InfoEndormi, qui, comme son nom l'indique nous donne des informations si la tache est endormie. A savoir la raison mais aussi des données comme par exemple le temps que cette tache doit passer endormie.

Puis afin de remplacer _delay_ms, nous créons une fonction Attente qui endort une tache pour un temps donné.

Code Code
#define REVEILLE 0
#define ENDORMI 1
#define RAISON_DELAY 0

struct InfoEndormi{
    int raison;
    uint16_t donnee;
}sleep_t;


struct TacheInfo{
    void (*depart)(void); 
    uint16_t SPointeur;
    int etat;
    struct InfoEndormi endormi;
};
void Attente(uint16_t temps_ms){
    cli();
    Taches[Tache_courante].etat = ENDORMI;
    Taches[Tache_courante].endormi.raison = RAISON_DELAY;
    Taches[Tache_courante].endormi.donnee = temps_ms;
    TCNT1 = 0;
    sei();
}

Voilà maintenant nos taches, il est important de créer une TacheZombie qui comme son nom l'indique est toujours réveillée.

Code
void TacheZombie() //Tache toujours réveillé
{
	while(1)
	{
		_delay_ms(1.5*PERIODE_INTERUPTION); //Delay légèrement supèrieur à la periode pour éviter tout soucis
	}
}

void TacheA()
{
	while(1)
	{
		PORTC ^= 0x08;
		Attente(300);
    }
}

void TacheB()
{
	while(1)
	{
		PORTD ^= 0x80;
		Attente(500);
    }
}

Il est important de modifier notre ordonnanceur afin que celui-ci puisse recalculer le nouveau temps de sleep d'une tache et si ce temps devient inférieur ou ègal à 0 alors l'ordonnanceur réveille la tache.

Code
void ordonnanceur ()
{
	PORTD ^= 0x02;
	for(int i = 0; i < NBR_TACHE; i++)
	{
		if(Taches[i].etat == ENDORMI && Taches[Tache_courante].endormi.raison == RAISON_DELAY)
		{
			uint16_t diff_temps = PERIODE_INTERUPTION;
			if(TCNT1 != 0)
			{
				//Calcul précis de la différence de temps (pas obligatoirement 20 ms)
				diff_temps = ((TCNT1*PERIODE_INTERUPTION*10)/OCR1A)/10;
				TCNT1 = 0;
			}
			Taches[i].endormi.donnee  -= diff_temps;
            
			if(Taches[i].endormi.donnee <= 0)
			{
				Taches[i].etat = REVEILLE;
			}
		}
	}
    
	do
	{
		Tache_courante++;
		if (Tache_courante == NBR_TACHE) Tache_courante = 0;
    }
	while(Taches[Tache_courante].etat == ENDORMI);
}

Maintenant que cela est fait, nous pouvons ajouter 2 nouvelles taches qui sont la lecture et l'écriture sur le port série.

Pour cela, nous allons procéder avec des sémaphores. En effet, il ne peut y avoir que 1 seule tache en écriture ou en lecture, il faut donc que si plusieurs taches essaient de prendre la ressource, cette ressource soit donné au premier arrivé et que les autres taches en attendant patientent.

Code
void semaphore_e(int ACTION){
	if (ACTION == PRENDRE)
	{
		while(1)
		{
			cli();
			if (semaphore_ecriture > 0) //Si ressource dispo 
			{
				semaphore_ecriture = 0;
				sei();
				break;
        		} else
			{
				// La ressource est occupée, on endort la tache
				Taches[Tache_courante].etat = ENDORMI;
				Taches[Tache_courante].endormi.raison = RAISON_ECRITURE;
				TCNT1=0;
				TIMER1_COMPA_vect();
			}
			sei();
			while(semaphore_ecriture==0);
		}
			
	}
	else if (ACTION == LAISSER)
	{
		cli();
		semaphore_ecriture = 1;
		sei();
	}
}

On fait la même chose pour la lecture.

On n'oublie pas d'initialiser le port série avec les bons paramètres, créer les taches, puis on test.

Code Image
void init_serie(long int vitesse){
	UBRR0=FREQ_CPU/(((unsigned long int)vitesse)<<4)-1; 
        // configure la vitesse
	UCSR0B=(1<<TXEN0 | 1<<RXEN0);   // autorise l'envoi et la réception
	UCSR0C=(1<<UCSZ01 | 1<<UCSZ00); // 8 bits et 1 bit de stop
	UCSR0A &= ~(1 << U2X0);         // double vitesse désactivée
}
Test de la lecture et de l'écriture sur le port série.(On écrit 9 caractères puis le shield nous renvoie les mêmes 9)

On passe à la matrice de leds.

On souhaite pouvoir lire sur le port série et afficher ce qu'il s'y écrit. Nous procéderons uniquement avec les chiffres.

Code Image
void Tache_Matrice()
{
    int val = -1;
    DDRB |= (1<<2);
    PORTB |= (1<<2);
    _delay_ms(500);
    matrice_init(1);
    while(1)
    {
        char c = chaine[0];
        if(c == '0' && val != 0)
        {
            Afficher_0();
            val = 0;
        }
        if(c == '1' && val != 1)
        {
            Afficher_1();
            val = 1;
        }
        if(c == '2' && val != 2)
        {
            Afficher_2();
            val = 2;
        }
        if(c == '3' && val != 3)
        {
            Afficher_3();
            val = 3;
        }
        if(c == '4' && val != 4)
        {
            Afficher_4();
            val = 4;
        }
        if(c == '5' && val != 5)
        {
            Afficher_5();
            val = 5;
        }
        if(c == '6' && val != 6)
        {
            Afficher_6();
            val = 6;
        }
        if(c == '7' && val != 7)
        {
            Afficher_7();
            val = 7;
        }
        if(c == '8' && val != 8)
        {
            Afficher_8();
            val = 8;
        }
        if(c == '9' && val != 9)
        {
            Afficher_9();
            val = 9;
        }
    }
}
Test de la matrice de leds (On écrit un chiffre sur le port série puis la matrice affiche le même chiffre)

Pour finir, on fait la même chose pour l'afficheur 7 segments.

Code Image
void Tache_7Seg()
{
    DDRB |= (1<<2);       
    PORTB |= (1<<2);
    _delay_ms(500);
    seg_init();
    while(1)
    {
        char c = chaine[0];
        seg_affiche(c);
    }
}
Test de l'afficheur 7 segments. (On écrit un chiffre sur le port série puis l'afficheur affiche cette valeur.)

L'ordonnanceur est maintenant plus ou moins complet, toutes les taches fonctionnent individuellement.

Malheureusement nous ne pouvons pas essayer le tout ensemble car l'afficheur 7 segments et la matrice de leds communique tous les 2 via SPI. Il faudrait donc rajouter des sémaphores par exemple pour savoir, si la ressource est utilisée ou non. Par manque de temps, nous ne l'avons donc pas fait.

Carte électronique numérique

Conception et Création

Nous avons choisi la Carte Fille : Ecran LCD

Pendant la 3ème séance nous avons fini le schematic de la carte.

Schematic de la Carte Fille : Ecran LCD

Puis nous avons Routé la carte et envoyé à la Fabrication.

Routage de la Carte Fille : Ecran LCD
Carte non soudée Carte soudée Video Test
Carte non soudée
Carte soudée

Programmation de la carte LCD

Tout d'abord, nous avons voulu tester le bon fonctionnement de notre carte mais surtout du bon contrôle de l'écran HD44780 grâce à l'IDE Arduino.

Code Image
const int rs = 2, en = 18, d4 = 14, d5 = 15, d6 = 16, d7 = 17;
LiquidCrystal lcd(rs, en, d4, d5, d6, d7);

void setup() {
  // set up the LCD's number of columns and rows:
  pinMode(9,OUTPUT);
  digitalWrite(9,LOW);
  lcd.begin(16, 2);
  // Print a message to the LCD.
  lcd.print("Je compte");
}

void loop() {
  // set the cursor to column 0, line 1
  // (note: line 1 is the second row, since counting begins with 0):
  lcd.setCursor(0, 1);
  // print the number of seconds since reset:
  lcd.print(millis() / 1000);
}
Test LCD via IDE Arduino

Maintenant que cela fonctionne, nous pouvons commencer à écrire notre librairie pour faire fonctionner notre carte.

Librairie HD44780

Nous avons commencé par reprendre la librairie d'ancien élève, merci à eux pour leur travail.

Dans notre cas, pour l'utilisation la plus simple nous avons besoin de plusieurs fonctions. La première écrit un caractère sur l'écran, la deuxième copie une ligne et la dernière efface une ligne.

HD44780_CopyLine1in0 :

Permet de copier la seconde ligne sur la première. Pour ce faire, on se place au début de la première ligne, on écrit le contenu du tableau de caractère, et on le vide ensuite (EmptyLine1).

Code
void HD44780_CopyLine1in0(char line1[], int nbrows, int nbcols, int ind_line1) {
    for (int i = 0; i < ind_line1; i++) {
        int address_ligne0 = HD44780_XY2Adrr(nbrows, nbcols, 0, i);
        HD44780_WriteCommand(LCD_ADDRSET | address_ligne0);
        HD44780_WriteData(line1[i],0);
    }
    HD44780_EmptyLine1(line1,nbcols);
}


HD44780_EraseLine  :

Permet d'effacer une ligne. Pour ce faire on se place au début de la ligne voulue, et on écrit une suite de caractères vide.

Code
void HD44780_EraseLine(int nbrows, int nbcols, int line) {
    int address_line1;
    char espace = ' ';
    for (int i = 0; i < nbcols; i++) {
    	address_line1 = HD44780_XY2Adrr(nbrows, nbcols, line, i);
    	HD44780_WriteCommand(LCD_ADDRSET | address_line1);
	    HD44780_WriteData(espace,0);
    }
}


HD44780_WriteChar  :

Permet d'écrire un caractère sur l'écran. Chaque caractère écrit sur la seconde ligne est placé dans un tableau de caractère.

Lorsqu'on arrive au bout d'une ligne : si nous sommes à la première ligne (Ligne 0), on passe à la seconde (Ligne 1) et on se replace au début des colonnes. Si nous sommes à la seconde ligne, on efface les deux lignes et on copie la seconde ligne sur la première ligne de l'écran, et on se replace au début de la seconde.

Code
void HD44780_WriteChar(char car, char line1[], int* ind_line1, int nbrows, int nbcols, int* row, int* col) {
    if ((*col) == nbcols) {
        (*col) = 0;
        if ((*row) <= 1) (*row)++;
        if ((*row) > 1) {
            (*row) = 1;
            HD44780_EraseLine(nbrows, nbcols,0);
            HD44780_EraseLine(nbrows, nbcols,1);
            HD44780_CopyLine1in0(line1, nbrows, nbcols,*ind_line1);
        }
        (*ind_line1) = 0;
    }

    int address = HD44780_XY2Adrr(nbrows, nbcols, *row, *col);
    HD44780_WriteCommand(LCD_ADDRSET | address);
    HD44780_WriteData(car,1);
    (*col)++;
    if ((*row) == 1) 
    {
        line1[*ind_line1] = car;
        (*ind_line1)++;
    }
}
Video Test

Super, ça fonctionne !

Désormais, nous voulons que notre écran affiche ce qu'il reçoit via le bus SPI.

Communication SPI

Pour cela il nous faut initialiser le maitre (un arduino UNO) et l'esclave (notre carte LCD).

Puis, que le maître transmette des données et que l'esclave les reçoive.

Code Maitre Code Esclave
void SPI_init()
{
    DDRB |= (1 << SPI_MOSI) | (1 << SPI_SCK) | (1 << SPI_SS);
    DDRB &= ~(1 << SPI_MISO);
    SPCR |= (1 << MSTR);
    SPCR |= (1 << SPE) | (1<<SPR0) | (1<<SPR1);
}

void SPI_MasterTransmit(char cData)
{
SPDR = cData;
while(!(SPSR & (1<<SPIF)));
}
void SPI_init()
{
    DDRB |= (1 << SPI_MISO);
    DDRB &= ~((1 << SPI_MOSI) | (1 << SPI_SCK) | (1 << SPI_SS));
    SPCR |= (1 << SPE); 
}

char SPI_SlaveReceive(void)
{
    /* Attendre la fin de la réception */
    while (!(SPSR & (1 << SPIF)));
    char receivedData = SPDR;
    return receivedData;
}

Faisons un petit test en envoyant des 'A' suivis de 'B' et enfin un 'X' pour terminer.

Code Test du Maitre Video Test
int main()
{
    ...
    ...
    while (x<17)
    {
      SPI_MasterTransmit('A');
      _delay_ms(100);
      SPI_MasterTransmit('B');
      x++;
      PORTD ^= (1<<7);
      _delay_ms(100);
    }
    SPI_MasterTransmit('X');
    while(1)
    {
    }
}

Notre test fonctionne mais cela n'a pas été sans peine. Nous avons rencontré énormément de problème sur la communication SPI. La communication était clairement douteuse et aléatoire.

Problèmes rencontrés

Plusieurs pistes ont été considérées pour comprendre le problèmes qu'on avait avec le SPI, était-ce l'arduino UNO? Notre connecteur? Le shield? Notre carte? Au départ, par souci de praticité, nous voulions utiliser l'arduino UNO qui disposait de fonctionnalités de test simple avant de passer au shield, mais à chaque essai, le SPI ne fonctionnait pas correctement bien qu'on pouvait observer à l'oscilloscope que le message était bien envoyé à notre carte.

Finalement, nous sommes directement passé à la communication SPI avec notre shield, qui cette fois-ci, permettait d'avoir un résultat satisfaisant. Le résultat n'était pas parfait car on remarquait que la position des fils déterminait si la communication allait fonctionner, il fallait donc trouver la position qui permettait cette communication. Pour régler ce problème, nous avons déterminé la fréquence de travail du SPI utilisé par le shield et notre carte, pour vérifier qu'il n'y ait pas de problème de synchronisation ou que la fréquence utilisée soit bien adaptée. Pour ce faire nous avons observé à l'oscilloscope les différentes divisions de fréquences possibles en vérifiant en même temps que la communication SPI fonctionne bien. Les résultats sont surprenants, car peu importe la fréquence de travail, aucun problème n'a été détécté.

Communication SPI par interruption

Il nous faut désormais communiquer par SPI par interruption car cela offre certains avantages par rapport à la communication SPI standard sans interruption, en particulier en matière d'efficacité et de gestion du temps. En effet, l'utilisation d'interruptions permet au microcontrôleur de gérer d'autres tâches pendant les transferts SPI. Plutôt que d'attendre de manière synchrone la fin d'une transaction SPI, le microcontrôleur peut être informé via une interruption lorsque les données sont prêtes à être lues ou écrites. Cela permet un multitâche plus efficace. Ce qui va nous être très pratique sachant que le projet comporte plusieurs cartes filles.

Pour cela il nous faut coté maitre et esclave activé le mode SPI Interrupt lors de l'initialisation du SPI, on rajoute donc SPCR |= (1<<SPIE); lors à l'initialisation.

Ensuite on génère l'interruption côté esclave à la fin de l'initialisation et on configure notre ISR.

Le maitre à plusieurs types d'envoi possible :

0x00 : Demande le type de la carte.

0x01 : Active la communication avec la carte LCD.

0xff  : Met fin à la communication.

Voici comment l'ISR lui fonctionne, si je reçois 0x00 je renvoie on type, 0x01 je passe en mode EnReception.

En mode EnReception : lorsque je reçois un octet je l'ajoute à un buffer, lorsque je recois 0xff je désactive le mode en Reception.

Dans mon main, une fois que mon mode EnRecption est désactivé mais que mon indice du buffer et supérieur à 0 alors je peux traiter mes octets.

Code ISR Code Main
ISR(SPI_STC_vect) {
    uint8_t  data = SPDR;
    if(EnReception == 1)
        switch (data){
	   case 0xff:
		EnReception=0;
		break;
	   default :
		buffer[IndBuffer++]=data;
	}
    else
        switch (data){
            case 0x00: 
		sendType();
		break;
           case 0x01:
		EnReception=1;
		break;
	}
}
while (1) {
	if (EnReception == 0 && IndBuffer>0)
	{
		for(int i=0; i < IndBuffer; i++)
		{
			"Fonction de traitement"
		}
		IndBuffer=0;
	}
    }

Gestion des caractères spéciaux

Nous avons décidé de gérer 4 caractères spéciaux différents : \n , \t, \b, \r

Pour cela on crée la fonction HD44780_Traitement qui sera notre fonction principale.

Si on reçoit un caractère classique alors on l'affiche grâce à HD44780_WriteChar précédemment créé.

Sinon il sagit d'un caractère spécial et alors on le traite dans un switch case :

\n

Si on est sur la première ligne, on se place au début de la seconde.

Si on est sur la seconde ligne, on efface les deux lignes et on copie la seconde ligne sur la première ligne de l'écran, et on se replace au début de la seconde.

Code \n Video Test
        case '\n': //Retour à la ligne
            switch (*row)
            {
                case 0:
                    (*col) = 0;
                    (*row) = 1;
                    break;
                case 1:
                    *col = 0;
                    HD44780_EraseLine(nbrows, nbcols,0);
                    HD44780_EraseLine(nbrows, nbcols,1);
                    HD44780_CopyLine1in0(line1, nbrows, nbcols,*ind_line1); 
                    *ind_line1 = 0;
                    break;    
            }
            break;

\r

On se replace au début de la ligne sur laquelle on est actuellement en train d'écrire.

Code \r Video Test
case '\r': // Retour chariot
            *col = 0;
            (*ind_line1) = 0;
            HD44780_EmptyLine1(line1,nbcols);
            break;

\r

On efface le caractère précédent, ajuste la position du curseur, et passe à la ligne supérieure si nécessaire.

Code \b Video Test
case '\b': // Retour arrière
            switch (*row)
            {
                case 0:
                    if (*col > 0)
                    {
                        (*col)--;//Retourne sur le caractère précédent
                        char espace = ' ';
                        int address = HD44780_XY2Adrr(nbrows, nbcols, *row, *col);
                        HD44780_WriteCommand(LCD_ADDRSET | address);
                        HD44780_WriteData(espace, 1);//"L'efface"
                        _delay_ms(500);

                    }
                    break;
                case 1:
                    if (*col > 0)
                    {
                      (*col)--;
                      (*ind_line1) --;
                    }
                    else 
                    {
                      (*row) = 0;
                      (*col) = 15;
                    }
                    char espace = ' ';
                    int address = HD44780_XY2Adrr(nbrows, nbcols, *row, *col);
                    HD44780_WriteCommand(LCD_ADDRSET | address);
                    HD44780_WriteData(espace, 1);
                    break;
            }
            break;

\t

On écrit 4 espaces, à la suite de notre position actuelle.

Code \t Video Test
case '\t': // Tabulation de 4 caractères
            for (int i=0; i < 4; i++)
            {   
                char espace = ' ';
                HD44780_WriteChar(espace,line1,ind_line1,nbrows,nbcols,row,col);
            }
            break;

Gestion des codes VT100

Pour finir, nous allons gérer quelques codes VT100, les déplacements de curseurs.

Pour envoyer un code VT100 pour déplacer le curseur le maitre doit nous envoyer:

  1. Le code ESCAPE (0x1b)
  2. Le CURSOR_MODE (0x1c)
  3. Et enfin le mouvement du curseur souhaité CURSOR_UP (0x41), CURSOR_DOWN (0x42), CURSOR_RIGHT (0x43), CURSOR_LEFT (0x44).

On gère cela dans la fonction VT100_Traitement.

Code
void VT100_Traitement(char car, int nbrows, int nbcols, int* row, int* col, int* vt100_mode)
{
    if (*vt100_mode == 1) // CURSOR_MODE
    {
        int address;

        switch (car)
        {
            case CURSOR_UP:
                *row = (*row == 1) ? 0 : *row;
                break;

            case CURSOR_DOWN:
                *row = (*row == 0) ? 1 : *row;
                break;

            case CURSOR_RIGHT:
                *col = (*col < nbcols - 1) ? (*col + 1) : *col;
                break;

            case CURSOR_LEFT:
                *col = (*col > 0) ? (*col - 1) : *col;
                break;
        }

        address = HD44780_XY2Adrr(nbrows, nbcols, *row, *col);
        HD44780_WriteCommand(LCD_ADDRSET | address);
        HD44780_WriteCommand(LCD_ON | CURSOR_BLINK);
    }
}
Video Test

Gestion des primitives systèmes

Voici les primitives systèmes pour la carte-mère, elles permettent de communiquer les chaînes de caractères à envoyer en préparant une transmission SPI constituée de l'octet de commande 0x01 et des données correspondant aux caractères.

La fonction Reset_Display permet de reset l'affichage de l'écran et permet aussi à la carte-mère de le remarquer en lui attribuant un numéro de port.

La fonction Transmit_to_display est la fonction de transmission de données par SPI. Elle utilise les fonctions SPI créées précédemment pour envoyer les données à afficher.

Code
void Reset_display(volatile uint8_t *resPort, volatile uint8_t resSel) //Utile pour reset l'ecran
{
    *resPort &= ~(1<<resSel);
    _delay_ms(1);
    *resPort |= (1<<resSel);
}


void Transmit_To_display(uint8_t data, volatile uint8_t *ssPort, volatile uint8_t ss)
{
	selectSlaveSPI(ssPort,ss);
	_delay_ms(1);
	transferSPI(data);
	_delay_ms(1);
	unselectSlaveSPI(ssPort,ss);
	_delay_ms(1);
}

Conclusion

Pour conclure, faisons un bilan de ce qui a été produit:

  • Une carte fille fonctionnelle qui gère l'affichage d'un écran LCD
    • Communication SPI par interruption
    • Défilement automatique de l'écran
    • Gestion des caractères spéciaux et code VT100
  • Primitives systèmes pour la carte mère
  • Ordonnanceur avec un système de priorité à l'aide de sémaphores

Malheureusement, par manque de temps, nous n'avons pas pu faire communiquer les cartes filles ensemble en les connectant à la carte-mère. Anisi que finir à 100% l'ordonnanceur.

Nous restons cependant très satisfaits du résultat et des fonctionnalités de notre carte !

Références utiles

Lien du git :

https://archives.plil.fr/mchauvel/PICO_Taha_NEHARI_Martin_CHAUVELIERE.git