« SE4Binome2023-1 » : différence entre les versions
(10 versions intermédiaires par 2 utilisateurs 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 159 : | Ligne 159 : | ||
{| class="wikitable" style="width:100%;" | {| class="wikitable" style="width:100%;" | ||
! scope="col" | Code | ! scope="col" | Code | ||
! scope="col" | | ! scope="col" | Video Test | ||
|- | |- | ||
| style="width:50%;" | | | style="width:50%;" | | ||
Ligne 174 : | Ligne 174 : | ||
} | } | ||
</syntaxhighlight> | </syntaxhighlight> | ||
| style="width:50%;" |[[Fichier:Video 2leds shield.mp4|centré|vignette|<div style="text-align:center;">Video | | style="width:50%;" |[[Fichier:Video 2leds shield.mp4|centré|vignette|<div style="text-align:center;">Video test du clignotement de 2 Leds </div>]] | ||
|} | |} | ||
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 | 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 | 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 | 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 | 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 : | ||
|} | |} | ||
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 | 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' | 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 | 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 | 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 | | 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 | 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 | 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 | Maintenant que cela fonctionne, nous pouvons commencer à écrire notre librairie pour faire fonctionner notre carte. | ||
=== Librairie HD44780 === | === Librairie HD44780 === | ||
Nous avons | 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 | 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 : | 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, | |}Super, ça fonctionne ! | ||
Désormais, nous voulons que notre écran affiche ce qu'il | 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 | 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 547 : | Ligne 648 : | ||
{| class="wikitable" style="width:100%;" | {| class="wikitable" style="width:100%;" | ||
! scope="col" | Code Test du Maitre | ! scope="col" | Code Test du Maitre | ||
! scope="col" | Video | ! scope="col" | Video Test | ||
|- | |- | ||
| style="width:50%;" | | | style="width:50%;" | | ||
Ligne 571 : | Ligne 672 : | ||
</syntaxhighlight> | </syntaxhighlight> | ||
| style="width:50%;" |[[Fichier:Video test SPI.mp4|centré|vignette|<div style="text-align:center;"> | | style="width:50%;" |[[Fichier:Video test SPI.mp4|centré|vignette|<div style="text-align:center;">Video test de transmission par SPI</div>]] | ||
|}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. | |}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 ==== | ==== Problèmes rencontrés ==== | ||
Plusieurs pistes ont été considérées pour comprendre le | 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 | 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 | 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 | 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 | Ensuite on génère l'interruption côté esclave à la fin de l'initialisation et on configure notre ISR. | ||
Le maitre à plusieurs | 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 | 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 | 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 | Nous avons décidé de gérer 4 caractères spéciaux différents : \n , \t, \b, \r | ||
Pour cela on | Pour cela on crée la fonction <code>HD44780_Traitement</code> qui sera notre fonction principale. | ||
Si on | 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 660 : | Ligne 761 : | ||
{| class="wikitable" style="width:100%;" | {| class="wikitable" style="width:100%;" | ||
! scope="col" | Code \n | ! scope="col" | Code \n | ||
! scope="col" | Video | ! scope="col" | Video Test | ||
|- | |- | ||
| style="width:50%;" | | | style="width:50%;" | | ||
Ligne 689 : | Ligne 790 : | ||
{| class="wikitable" style="width:100%;" | {| class="wikitable" style="width:100%;" | ||
! scope="col" | Code \r | ! scope="col" | Code \r | ||
! scope="col" | Video | ! scope="col" | Video Test | ||
|- | |- | ||
| style="width:50%;" | | | style="width:50%;" | | ||
Ligne 707 : | Ligne 808 : | ||
{| class="wikitable" style="width:100%;" | {| class="wikitable" style="width:100%;" | ||
! scope="col" | Code \b | ! scope="col" | Code \b | ||
! scope="col" | Video | ! scope="col" | Video Test | ||
|- | |- | ||
| style="width:50%;" | | | style="width:50%;" | | ||
Ligne 753 : | Ligne 854 : | ||
{| class="wikitable" style="width:100%;" | {| class="wikitable" style="width:100%;" | ||
! scope="col" | Code \t | ! scope="col" | Code \t | ||
! scope="col" | Video | ! scope="col" | Video Test | ||
|- | |- | ||
| style="width:50%;" | | | style="width:50%;" | | ||
Ligne 823 : | Ligne 924 : | ||
|} | |} | ||
=== Gestion des primitives systèmes === | === Gestion des primitives systèmes === | ||
Voici les primitives systèmes pour la carte mère, | 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 854 : | Ligne 955 : | ||
= Conclusion = | = 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 = | = 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.
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;
}
|
Puis nous avons aussi soudé l'adaptateur HE10 pour l'utilisation de la 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
}
|
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;
}
}
}
|
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);
}
}
|
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.
Puis nous avons Routé la carte et envoyé à la Fabrication.
Carte non soudée | Carte soudée | Video Test |
---|---|---|
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);
}
|
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:
- Le code ESCAPE (0x1b)
- Le CURSOR_MODE (0x1c)
- 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