« SE4Binome2024-6 » : différence entre les versions
(80 versions intermédiaires par 2 utilisateurs non affichées) | |||
Ligne 4 : | Ligne 4 : | ||
=Objectif= | =Objectif= | ||
<div style="text-align: justify;"> | <div style="text-align: justify;"> | ||
Nous avons pour objectif avec les | Nous avons pour objectif, avec les trois autres binômes de notre groupe, de construire un pico-ordinateur qui intégrera plusieurs éléments essentiels. Voici les composants que nous allons inclure : | ||
*Un processeur de type microcontrôleur : Cela constituera le cœur de notre pico-ordinateur | * Un processeur de type microcontrôleur : Cela constituera le cœur de notre pico-ordinateur et permettra de gérer toutes les opérations. | ||
* Un clavier : Il permettra l'entrée de données et d'interagir facilement avec notre dispositif. | |||
* Un dispositif d'affichage : Cela nous permettra de visualiser les informations ainsi que les résultats des opérations effectuées par notre pico-ordinateur. | |||
* Un système d'exploitation : Il sera stocké dans la mémoire flash du microcontrôleur, garantissant un fonctionnement fluide de l'appareil. | |||
* Une mémoire de masse : Nous prévoyons d'ajouter une mémoire de masse pour stocker davantage de données. | |||
* Un dispositif de communication externe : Cela permettra d'interagir avec d'autres dispositifs ou réseaux. | |||
Enfin, pour assurer la communication entre ces éléments, nous mettrons en place un bus série. Cela facilitera les échanges de données et garantira une intégration harmonieuse des composants. | |||
Notre binôme se concentrera sur la réalisation d'une carte écran, qui devra remplir les tâches précisées dans l'énoncé du projet. | |||
En premier lieu, nous avons d'abord réalisé un shield qui nous servira, dans le cas où la carte mère ne serait pas fonctionnelle, à tester nos cartes filles à l'aide d'un Arduino Uno pour prototyper le cœur de la carte mère. | |||
</div> | </div> | ||
Ligne 26 : | Ligne 24 : | ||
==Realisation== | ==Realisation== | ||
<div style="text-align: justify;"> | <div style="text-align: justify;"> | ||
Nous nous sommes aidés des indications | Nous nous sommes aidés des indications données par nos encadrants lors de la première séance pour réaliser le schéma de notre shield. Nous y avons ajouté une puce mémoire AT45DB161D, et la datasheet nous a permis de comprendre comment l'intégrer correctement dans notre schéma. Vous pouvez voir ci-dessous la version finale du routage du PCB du bouclier ainsi que ce dernier après la soudure des différents composants. | ||
</div> | </div> | ||
{|class="wikitable" | {|class="wikitable" | ||
Ligne 39 : | Ligne 37 : | ||
<div style="text-align: justify;"> | <div style="text-align: justify;"> | ||
Suite à une erreur d'orientation de nos broches | Suite à une erreur d'orientation de nos broches, connecter l'Arduino et notre bouclier est devenue une tâche bien plus "compliquée" que prévu. Cependant, grâce à une idée ingénieuse proposée par nos encadrants (M. BOE et M. REDON), nous y sommes parvenus. Le bouclier se connecte donc à l'Arduino de la manière suivante : | ||
</div> | </div> | ||
{|class="wikitable" | {|class="wikitable" | ||
Ligne 48 : | Ligne 46 : | ||
==Tests== | ==Tests== | ||
=== | ===LEDs=== | ||
Après avoir connecté notre bouclier et l' | Après avoir connecté notre bouclier et l'Arduino nous avons effectué les tests et constaté l'allumage des différentes LEDs; attestant du bon fonctionnement de notre bouclier. | ||
Voici le code C (GIT : pico_yahiani_zongo/Software/clignotement) qui nous permet de faire clignoter les | Voici le code C (GIT : pico_yahiani_zongo/Software/codes_Shield/clignotement) qui nous permet de faire clignoter les LEDs. | ||
{|class = "wikitable" | {|class = "wikitable" | ||
|- | |- | ||
Ligne 100 : | Ligne 98 : | ||
[[Fichier:Snapchat-1046850592.jpg|vignette|centre|fonctionnement effectif du bouclier (LED allumées)]] | [[Fichier:Snapchat-1046850592.jpg|vignette|centre|fonctionnement effectif du bouclier (LED allumées)]] | ||
|} | |} | ||
===Puce mémoire=== | ===Puce mémoire=== | ||
Après avoir testé les LEDs, nous cherchons à tester notre puce mémoire pour assurer son bon fonctionnement. Cependant, nous avons rencontré un petit problème. | |||
====Problème==== | ====Problème==== | ||
En effet, lors du test de notre puce mémoire nous avons introduit une carte SD pour vérifier via le logiciel Arduino si notre puce mémoire détecte la carte SD, | En effet, lors du test de notre puce mémoire, nous avons introduit une carte SD pour vérifier, via le logiciel Arduino, si notre puce mémoire détecte la carte SD. Cependant, cela fut sans succès. Nous avons donc vérifié l'horloge de notre Arduino Uno à l'aide d'un oscilloscope en y injectant ce code (GIT : pico_yahiani_zongo/Software/codes_Shield/test_clk_arduino) : | ||
{|class = "wikitable" | {|class = "wikitable" | ||
|- | |- | ||
!Code | !Code arduino !! Visualisation | ||
|- | |- | ||
| | | | ||
Ligne 146 : | Ligne 139 : | ||
Le signal d'horloge de notre Arduino est correct. | Le signal d'horloge de notre Arduino est correct. | ||
Le problème ne venant pas de l'Arduino, nous avons donc dessoudé puis ressoudé notre puce mémoire et le problème à été résolu, comme vous pouvez le voir ci-dessous. | |||
Le problème ne venant pas de l'Arduino, nous avons donc dessoudé | |||
[[Fichier:Csd.png|200px|thumb|centre|Carte SD reconnue]] | [[Fichier:Csd.png|200px|thumb|centre|Carte SD reconnue]] | ||
===Connecteurs HE10=== | ===Connecteurs HE10=== | ||
À l'aide d'un afficheur 7 | À l'aide d'un afficheur 7 segments, nous vérifions le bon fonctionnement des connecteurs HE10 en affichant "GrP6" pour Groupe 6. Pour cela, nous configurons la communication SPI avec avr-gcc grâce au code présent dans notre fichier spi.c, grandement inspiré du code disponible sur le site de M. Redon (https://rex.plil.fr/Enseignement/Systeme/Systeme.PSE/systeme.html). Ci-dessous, vous trouverez un aperçu du code source utilisé pour gérer la communication SPI ainsi que celui utilisé pour gérer l'affichage du 7 segments. | ||
{|class="wikitable" | {|class="wikitable" | ||
|- | |- | ||
Ligne 164 : | Ligne 156 : | ||
#define SPI_PORT PORTC | #define SPI_PORT PORTC | ||
#define SPI_SS_M 2 // | #define SPI_SS_M 2 // | ||
#define SPI_MOSI 3 | #define SPI_MOSI 3 | ||
#define SPI_MISO 4 | #define SPI_MISO 4 | ||
Ligne 234 : | Ligne 226 : | ||
== Clignotement de deux LEDS == | == Clignotement de deux LEDS == | ||
<div style="text-align: justify;"> | <div style="text-align: justify;"> | ||
La principale tâche à accomplir en premier lieu est de faire | La principale tâche à accomplir en premier lieu est de faire clignoter deux LEDs à des fréquences différentes. Pour cela nous passerons par plusieurs étapes. La première tâche à réaliser est de programmer le '''minuteur 1''' de l'ATMega328p de sorte à ce qu'il génère une interruption toutes les '''20ms'''. Pour cela nous nous sommes inspirés du code donné par l'un de nos encadrants (M. REDON) et avons programmé le minuteur 1 comme vous pouvez le voir dans notre Git : '''pico_yahiani_zongo/Software/codes_Shield/ISR_nonNue_1tache/timer.c'''. | ||
Le Timer 1 est configuré en mode '''CTC''' c'est à dire qu'il se réinitialise automatiquement à chaque fois qu'il atteint la valeur OCR1A.Le prescaler est configuré à 256, ce qui divise la fréquence de l'horloge système( | Le Timer 1 est configuré en mode '''CTC''', c'est-à-dire qu'il se réinitialise automatiquement à chaque fois qu'il atteint la valeur OCR1A. Le prescaler est configuré à 256, ce qui divise la fréquence de l'horloge système (16000000 Hz) par 256. Nous avons donc une nouvelle fréquence d'horloge à 62500 Hz, ce qui implique que les coups d'horloge (ticks) s'effectuent toutes les 16 µs. Le timer compte jusqu'à 1250 ticks, ce qui génère une interruption toutes les 20 ms. | ||
La seconde étape consistait à faire clignoter la LED broche Arduino 13(PB5) en utilisant une ISR '''non nue''' avec notre timer. Le code suivant nous a permis de réaliser cette tâche. | La seconde étape consistait à faire clignoter la LED sur la broche Arduino 13 (PB5) en utilisant une ISR '''non nue''' avec notre timer. Le code suivant nous a permis de réaliser cette tâche. | ||
</div> | </div> | ||
{|class = "wikitable" | {|class = "wikitable" | ||
Ligne 252 : | Ligne 244 : | ||
#define PRESCALER 256 | #define PRESCALER 256 | ||
#define NB_TICK 1250 | #define NB_TICK 1250 | ||
#define CTC1 WGM12 | #define CTC1 WGM12 | ||
Ligne 290 : | Ligne 282 : | ||
Pour passer d'une ISR '''non nue''' à une ISR '''naked''' il faut écrire les macros de sauvegardes et de restaurations des registres, ces macros sont écrites dans des fichiers présents dans notre GIT : | Pour passer d'une ISR '''non nue''' à une ISR '''naked''' il faut écrire les macros de sauvegardes et de restaurations des registres, ces macros sont écrites dans des fichiers présents dans notre GIT : | ||
* '''pico_yahiani_zongo/Software/ordo_tourni_2taches/save_registers.h''' | * '''pico_yahiani_zongo/Software/codes_Shield/ordo_tourni_2taches/save_registers.h''' | ||
* '''pico_yahiani_zongo/Software/ordo_tourni_2taches/restore_registers.h''' | * '''pico_yahiani_zongo/Software/codes_Shield/ordo_tourni_2taches/restore_registers.h''' | ||
Dans la suite nous avons défini nos processus comme des structures que nous avons stockées dans un tableau. | Dans la suite nous avons défini nos processus comme des structures que nous avons stockées dans un tableau. | ||
Ligne 305 : | Ligne 297 : | ||
</syntaxhighlight> | </syntaxhighlight> | ||
Notre prochain objectif est de lancer deux tâches en parallèle en incluant un ordonnanceur à torniquet dans l'ISR. Pour cela nous utilisons en plus du timer présenté plus haut | Notre prochain objectif est de lancer deux tâches en parallèle en incluant un ordonnanceur à torniquet dans l'ISR. Pour cela, nous utilisons, en plus du timer présenté plus haut, une fonction d'initialisation des tâches ('''init_taches()''') et une '''ISR nue''', cette fois, qui gère la succession des tâches. Nous utilisons cette fois une '''ISR NAKED''' pour avoir un contrôle total sur la gestion des registres et pour éviter la surcharge des sauvegardes et/ou restaurations automatiques générées par le compilateur. | ||
{|class = "wikitable" | {|class = "wikitable" | ||
|- | |- | ||
Ligne 349 : | Ligne 341 : | ||
||[[Fichier:Cli2LED.mp4|vignette|Les 2 LEDS clignotent à des fréquences differentes]] | ||[[Fichier:Cli2LED.mp4|vignette|Les 2 LEDS clignotent à des fréquences differentes]] | ||
|} | |} | ||
== Gestion de l'état endormi == | == Gestion de l'état endormi == | ||
<div style="text-align: justify;"> | <div style="text-align: justify;"> | ||
Notre ordonnanceur est désormais capable de lancer | Notre ordonnanceur est désormais capable de lancer plusieurs tâches en parallèle, nous pouvons donc passer à la gestion de l'état endormi de nos processus. Jusqu'à présent, pour effectuer des pauses dans l'exécution de nos tâches, nous utilisions la fonction '''_delay_ms''' en incluant la bibliothèque '''<util/delay.h>'''. Pour améliorer notre ordonnanceur, nous effectuons maintenant les pauses avec une fonction que nous avons écrite ('''_delay'''). | ||
Pour l'utilisation de notre fonction, nous avons redéfini la structure des tâches à laquelle nous avons ajouté un champ "'''temps'''" représentant le temps pendant lequel la tâche reste endormie. Nous avons également écrit une fonction '''ordonnanceur''' qui décrémente le champ "'''temps'''" d'une tâche puis la réveille une fois le temps écoulé. Ci-dessous, vous pouvez voir à quoi ressemblent ces fonctions. | |||
</div> | </div> | ||
{|class = "wikitable" | {|class = "wikitable" | ||
Ligne 430 : | Ligne 419 : | ||
<div style="text-align: justify;"> | <div style="text-align: justify;"> | ||
Ci-dessous, une vidéo de fonctionnement de la gestion de l'état endormi des processus. Vous pouvez voir au | Ci-dessous, une vidéo de fonctionnement de la gestion de l'état endormi des processus. Vous pouvez voir au début de la vidéo que seule la LED verte est active (état '''WAKE''') pendant que la LED orange est endormie. Une fois le temps d'endormissement écoulé, vous pouvez voir la LED orange commencer à clignoter (elle est passée à l'état '''WAKE''') à la même fréquence que la LED verte. | ||
Pour une bonne gestion de l'état endormi, nous ajoutons à notre liste de tâches une tâche '''_wait''' qui ne fait rien en bouclant juste indéfiniment. Cette dernière est importante car lorsque toutes les autres tâches de l'ordonnanceur sont inactives ou en attente, une tâche "neutre" garantit qu'il y a toujours une activité en cours, même si elle est minimale. Cela évite que le processeur tombe dans un état imprévisible ou commence à exécuter du code non souhaité. | |||
</div> | </div> | ||
{|class = "wikitable" | {|class = "wikitable" | ||
Ligne 448 : | Ligne 439 : | ||
||[[Fichier:Sleepy.mp4|centre|Fonctionnement état endormi]] | ||[[Fichier:Sleepy.mp4|centre|Fonctionnement état endormi]] | ||
|} | |} | ||
== Gestion communication Port serie et SPI == | == Gestion communication Port serie et SPI == | ||
=== Avec afficheur sept-segments === | |||
<div style="text-align: justify;"> | <div style="text-align: justify;"> | ||
Notre ordonnanceur est maintenant capable de gérer le | Notre ordonnanceur est maintenant capable de gérer le clignotement de deux LEDs en parallèle ainsi que les processus endormis. Il s'agira maintenant pour nous de le complexifier en lui permettant de gérer les communications SPI et port série. Pour ce faire, nous utilisons des codes grandement inspirés de ceux présents sur le site de M. REDON (https://rex.plil.fr/Enseignement/Systeme/Systeme.PSE/systeme.html), qui se trouvent dans les fichiers SPI.c (pour la gestion de la communication SPI) et serial.c (pour le port série). | ||
</div> | </div> | ||
{|class = "wikitable" | {|class = "wikitable" | ||
|- | |- | ||
!Gestion SPI !! Gestion | !Gestion SPI !! Gestion port série | ||
|- | |- | ||
| | | | ||
Ligne 475 : | Ligne 462 : | ||
#define SPI_SS_M 2 | #define SPI_SS_M 2 | ||
#define SPI_MOSI 3 | #define SPI_MOSI 3 | ||
#define SPI_MISO 4 | #define SPI_MISO 4 | ||
Ligne 544 : | Ligne 531 : | ||
<div style="text-align: justify;"> | <div style="text-align: justify;"> | ||
Les fonctions '''serie_envoyer''' et '''serie_recevoir''' comportent chacune une phase d'attente de disponibilité avec les lignes '''loop_until_bit_is_set(UCSR0A,UDRE0) | Les fonctions '''serie_envoyer''' et '''serie_recevoir''' comportent chacune une phase d'attente de disponibilité avec les lignes '''loop_until_bit_is_set(UCSR0A, UDRE0)'''; (qui attend que le bit UDRE0 dans le registre UCSR0A soit à 1 ; ce bit indique que le registre de transmission (UDR0) est prêt à recevoir un nouvel octet) et '''loop_until_bit_is_set(UCSR0A, RXC0)'''; (qui attend que le bit RXC0 dans le registre UCSR0A soit à 1 ; ce bit indique qu'un octet a été reçu via le port série et est disponible dans le registre UDR0). | ||
Il y a ensuite une phase de transmission pour serie_envoyer avec '''UDR0 = c'''; : l'octet est chargé dans le registre UDR0. Une fois chargé, l'octet est automatiquement envoyé via le port série. Pour serie_recevoir, la phase suivante est la lecture avec '''return UDR0'''; la fonction renvoie directement le contenu du registre UDR0, qui contient l'octet reçu. | |||
Dans le fichier serial.c (image droite ci-dessus), nous avons ajouté la fonction '''set_valeur''' qui utilise simultanément les fonctions serie_envoyer et serie_recevoir pour gérer l'écriture et la lecture via le port série dans la même tâche. La valeur mise dans serie_recevoir est la même que celle utilisée dans la tâche de gestion de l'afficheur à sept segments. Nous ajoutons ensuite set_valeur dans notre liste de tâches. Vous pouvez voir le résultat que nous obtenons ci-dessous. | |||
</div> | |||
{|class = "wikitable" | |||
|- | |||
!Liste des tâches !! Vidéo de fonctionnement | |||
|- | |||
| | |||
<code> | |||
<syntaxhighlight lang="c" line> | |||
tache taches[TACHE_MAX] = | |||
{{_wait,0x0800,10,SLEEPY}, | |||
{gestion_LED4,0x0400,1000,SLEEPY}, | |||
{gestion_LED2,0x0600,10,WAKE}, | |||
{ctrl7segments,0x500,10,WAKE}, | |||
{set_valeur,0x300,10,WAKE} | |||
}; | |||
</syntaxhighlight> | |||
</code> | |||
||[[Fichier:PS et 7 seg.mp4|communication SPI et Port Série]] | |||
|} | |||
=== Avec Matrice de LEDS === | |||
<div style="text-align: justify;"> | |||
De la même manière qu'avec le sept-segments, nous utilisons notre ordonnanceur pour commander la matrice de LEDs par l'intermédiaire des modes de communication SPI et port série. À la différence du sept-segment, la matrice de LEDs nécessite une configuration des différents caractères qu'elle pourra afficher. Pour cela, nous initialisons une matrice définissant les LEDs qui doivent s'allumer pour afficher les caractères souhaités. Nous affichons l'ensemble des caractères hexadécimaux grâce à cette matrice (contenue dans le fichier matrice.h). Ci-dessous, vous pouvez voir les résultats que nous obtenons ainsi que les codes que nous avons utilisés. | |||
</div> | </div> | ||
{|class="wikitable" | |||
|- | |||
!Matrice de configuration des LEDs | |||
|- | |||
|colspan="2"| | |||
<code> | |||
<syntaxhighlight lang="c" line="1" start="1"> | |||
char hex[16][8] = { | |||
{0x7e,0x42,0x42,0x42,0x42,0x42,0x7e,0x00}, //--> 0 | |||
{0x08,0x18,0x28,0x08,0x08,0x08,0x3e,0x00}, //--> 1 | |||
{0x18,0x24,0x04,0x08,0x10,0x20,0x7c,0x00}, //--> 2 | |||
{0x18,0x24,0x04,0x18,0x04,0x04,0x38,0x00}, //--> 3 | |||
{0x20,0x20,0x24,0x24,0x3e,0x04,0x04,0x00}, //--> 4 | |||
{0x7e,0x40,0x40,0x7e,0x02,0x02,0x7e,0x00}, //--> 5 | |||
{0x7e,0x40,0x40,0x7e,0x42,0x42,0x7e,0x00}, //--> 6 | |||
{0x7f,0x02,0x04,0x08,0x10,0x20,0x40,0x00}, //--> 7 | |||
{0x3c,0x42,0x42,0x3c,0x42,0x42,0x3c,0x00}, //--> 8 | |||
{0x7e,0x42,0x42,0x7e,0x02,0x02,0x7e,0x00}, //--> 9 | |||
{0x18,0x24,0x42,0x42,0x7e,0x42,0x42,0x42}, //--> A | |||
{0x7c,0x42,0x42,0x42,0x7c,0x42,0x42,0x7e}, //--> B | |||
{0x7e,0x40,0x40,0x40,0x40,0x40,0x40,0x7e}, //--> C | |||
{0x78,0x44,0x42,0x42,0x42,0x42,0x42,0x7c}, //--> D | |||
{0x7e,0x40,0x40,0x7e,0x40,0x40,0x40,0x7e}, //--> E | |||
{0x7e,0x40,0x40,0x7c,0x40,0x40,0x40,0x40}, //--> F | |||
}; | |||
</syntaxhighlight> | |||
</code> | |||
|- | |||
!Code de gestion de la matrice de LEDs !! Vidéo de fonctionnement | |||
|- | |||
|<code> | |||
<syntaxhighlight lang="c" line="1" start="1"> | |||
int selection(char select)// pour choisir l index dans le tableau pour la matrice | |||
{ | |||
int result = 0; | |||
if(select >= '0' && select <= '9') | |||
result = select - 48; | |||
else if(select >= 'a' && select <= 'f') | |||
result = 10 + (select - 'a'); | |||
else if(select >= 'A' && select <= 'F') | |||
result = 10 + (select - 'A'); | |||
return result; | |||
} | |||
void aff_matrix(void){ | |||
char a; | |||
cli(); | |||
int index; | |||
spi_activer(); //Activate the RGB Matrix | |||
spi_echange('%'); | |||
spi_echange(1); | |||
// spi_echange(0x26); | |||
spi_desactiver(); //Activate the RGB Matrix | |||
while(1){ | |||
index = selection(valeur); | |||
spi_activer(); | |||
//sleepy(50); | |||
for(int LED=0; LED<8; LED++){ | |||
for(int j=0; j<8;j++){ | |||
a = hex[index][LED] & (1<<j); | |||
spi_echange(a); | |||
} | |||
} | |||
_delay(10); | |||
spi_desactiver(); | |||
_delay(25); | |||
} | |||
sei(); | |||
} | |||
</syntaxhighlight> | |||
</code> | |||
||[[Fichier:ML.mp4|vignette|centre|Matrice de LEDs programmée]] | |||
|- | |||
|} | |||
=Carte fille écran LCD= | |||
==Conception de la carte fille== | |||
<div style="text-align: justify;"> | <div style="text-align: justify;"> | ||
La carte fille écran comporte un ATMega328p et un écran LCD à base de contrôleur HD44780. Nous utilisons comme precisé dans le wiki un potentiomètre pour régler la luminosité des cristaux liquides ainsi qu'un connecteur HE10 pour la connexion de cette carte fille à notre carte mère sur lequel nous avons bien prevu une ligne de sélection SPI. Aucune ligne d'interruption n'était initialement nécessaire, mais suite au rajout d'une RAM SPI nous avons dû en rajouter une pour gérer les interruptions.La carte mère écrit dans la RAM et le | La carte fille écran comporte un ATMega328p et un écran LCD à base de contrôleur HD44780. Nous utilisons, comme precisé dans le wiki, un potentiomètre pour régler la luminosité des cristaux liquides ainsi qu'un connecteur HE10 pour la connexion de cette carte fille à notre carte mère sur lequel nous avons bien prevu une ligne de sélection SPI. Aucune ligne d'interruption n'était initialement nécessaire, mais suite au rajout d'une RAM SPI (FM25W256-G) nous avons dû en rajouter une pour gérer les interruptions.La carte mère écrit dans la RAM et le microcontôleur de la carte fille rafraichit l'écran régulièrement; en outre pour éviter les conflits entre la carte mère qui écrit et la carte fille qui lit il est nécessaire d'effectuer une gestion de priorités entre les différents signaux de MOSI, d'horloge et de selection. Cette gestion se fait à l'aide de circuits intermédaires présents dans notre schématique inspirés des circuits "NMOS highside" que nous avons réalisé avec l'aide de nos Encadrants M. BOE et M. REDON. | ||
</div> | </div> | ||
Voici les étapes de conceptions de notre carte fille: | Voici les étapes de conceptions de notre carte fille: | ||
===Première Version=== | |||
{|class="wikitable" | {|class="wikitable" | ||
Ligne 571 : | Ligne 659 : | ||
réception de la carte PCB: | réception de la carte PCB: | ||
[[Fichier:Carte.pdf.pdf|centre|vignette]] | {|class = "wikitable" | ||
|- | |||
!Carte fille avant soudure !! Carte fille après soudure | |||
|- | |||
|[[Fichier:Carte.pdf.pdf|centre|vignette]] | |||
||[[Fichier:First cart soud.jpg|vignette]] | |||
|} | |||
===Problèmes rencontrés avec la première version de la carte=== | |||
Lors de la conception et du routage de la première version de la carte destinée à gérer l'écran LCD HD44780, plusieurs erreurs ont été identifiées. Ces erreurs ont gravement impacté le fonctionnement de la carte, rendant nécessaire la conception d'une nouvelle version. Voici un récapitulatif des problèmes rencontrés et de leur impact : | |||
* '''Erreur sur l'empreinte du regulateur ISR''' | |||
Une empreinte SMD a été utilisée pour le régulateur ISR, alors que nous ne disposions que du composant en version traversante. | |||
Nous avons dû improviser en soudant des fils pour relier les différentes broches (MOSI, MISO, RST, etc.). | |||
Cette approche temporaire a engendré des problèmes majeurs, tels que des connexions instables, des court-circuits et une exposition accrue au bruit électromagnétique. | |||
Ces problèmes ont compromis la fiabilité des communications SPI et l'alimentation du circuit, comme nous l'a fait remarquer l'un de nos encadrants, M. REDON. Nous avons donc dans la nouvelle version, remplacé cette empreinte par sa version traversante. | |||
* '''Connexion incorrecte des broches E et RS''' | |||
Les broches E (Enable) et RS (Register Select) de l'écran LCD ont été connectées directement au 5V, au lieu d’être reliées aux broches programmables de l’ATMega328p. Ces broches jouent un rôle essentiel dans la communication avec le contrôleur HD44780, en permettant de sélectionner le registre de commande ou de données et de déclencher des opérations d'écriture. | |||
En les connectant directement au 5V, il était impossible de les programmer ou de les utiliser correctement. | |||
Cela a totalement empêché la configuration et l’envoi de commandes à l’écran LCD. Dans la nouvelle version de la carte, nous avons donc relié les broches E et RS à des broches numériques de l’ATMega328p pour assurer leur contrôle logiciel. | |||
* '''Erreur sur l'alimentation de l’écran LCD''' | |||
L'alimentation de l’écran a été liée en série avec un condensateur, au lieu d'être connectée en parallèle. Cette configuration en série empêchait la bonne alimentation de l'écran après les modifications effectuées sur les broches RS et E, rendant ce dernier inopérant. Nous avons donc modifié le schéma pour connecter le condensateur en parallèle avec l’alimentation, ce qui est nécessaire pour stabiliser la tension d’alimentation, comme nous l'a fait remarquer l'un de nos encadrants, M. BOE. | |||
===Seconde Version=== | |||
{|class="wikitable" | |||
|- | |||
|[[Fichier:Carte ecran copy.pdf|vignette|centre| schematique carte fille 2nde version]] | |||
||[[Fichier:Rtg.png|vignette|centre|routage carte fille 2nde version]] | |||
|- | |||
|[[Fichier:Carte ecran 3D.png|vignette|gauche|visualisation 3D (face avant)]] | |||
||[[Fichier:Carte ecran 3D arr.png|vignette|droite|visualisation 3D (face arrière)]] | |||
|- | |||
|} | |||
Une fois la seconde version de la carte reçue nous somme passés à la soudure des différents composants. Ci-dessous vous pouvez voir notre carte fille avant et après la phase de soudure. | |||
{|class = "wikitable" | |||
|- | |||
!Carte fille avant soudure !! Carte fille après soudure | |||
|- | |||
|[[Fichier:Carte non soudée.jpg|vignette]] | |||
||[[Fichier:Carte .jpg|vignette ]] | |||
|} | |||
==Programmation de la carte fille== | |||
===Test de la carte fille=== | |||
Avant d'entamer la gestion des différentes fonctionnalités de l'écran LCD, nous vérifions si les composants ont été bien soudés et si la carte est fonctionnelle et programmable, en faisant clignoter une de ses LEDs avec du code Arduino présent dans notre répertoire Git. Vous pouvez voir les résultats de ce test ci-dessous : | |||
{|class = "wikitable" | |||
|- | |||
!Code arduino !! Vidéo fonctionnement | |||
|- | |||
|<code> | |||
<syntaxhighlight lang="c" line> | |||
#include <SPI.h> | |||
#include <avr/io.h> | |||
#define LED_PIN 4 | |||
#define DELAY_MS 500 | |||
void setup() | |||
{ | |||
DDRB |= (1 << LED_PIN); | |||
} | |||
void loop() | |||
{ | |||
PORTB |= (1 << LED_PIN); | |||
delay(DELAY_MS); | |||
PORTB &= (1 << LED_PIN); | |||
delay(DELAY_MS); | |||
} | |||
</syntaxhighlight> | |||
</code> | |||
|| | |||
[[Fichier:Test blink.mp4|vignette|LED d'alimentation allumée et LED programmable clignotante]] | |||
|} | |||
===Test de l'écran LCD HD44780=== | |||
Maintenant que notre carte fille est fonctionnelle, nous commençons la programmation de notre écran LCD. Encore une fois, nous passons par une étape de test où nous vérifions le bon fonctionnement de l'écran LCD. À l'aide de code Arduino, nous affichons le classique '''"Hello, World!"''' sur notre écran. Vous pouvez voir les résultats de ce test ci-dessous. | |||
{|class = "wikitable" | |||
|- | |||
!Code arduino !! Image résultat | |||
|- | |||
|<code> | |||
<syntaxhighlight lang="c" line> | |||
#include <LiquidCrystal.h> | |||
// initialize the library by associating any needed LCD interface pin | |||
// with the arduino pin number it is connected to | |||
const int rs = 18, en = 19, d4 = 3, d5 = 4, d6 = 5, d7 = 6; | |||
LiquidCrystal lcd(rs, en, d4, d5, d6, d7); | |||
void setup() | |||
{ | |||
pinMode(15, OUTPUT); | |||
digitalWrite(15, LOW); | |||
// set up the LCD's number of columns and rows: | |||
lcd.begin(16, 2); | |||
// Print a message to the LCD. | |||
lcd.print("hello, world!"); | |||
} | |||
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); | |||
} | |||
</syntaxhighlight> | |||
</code> | |||
|| | |||
[[Fichier:Hello ecr.jpg|vignette]] | |||
|} | |||
=== Gestion de l'affichage === | |||
====Défilement==== | |||
Notre écran affiche bien les caractères souhaités, nous pouvons donc passer à la réalisation des différentes fonctions de gestion de notre écran LCD HD44780. Pour ce faire, nous avons utilisé le code de gestion du HD44780 que nous a donné l'un de nos encadrants, M. REDON. En utilisant les fonctions présentes dans ce code nous arrivons à faire défiler des caractères sur notre écran. | |||
{|class = "wikitable" | |||
|- | |||
!Code arduino !! Image résultat | |||
|- | |||
|<code> | |||
<syntaxhighlight lang="c" line> | |||
int main(void) | |||
{ | |||
//Configuration et séquence d'initilisation de l'écran LCD | |||
HD44780_Initialize(); | |||
HD44780_WriteCommand(LCD_ON|CURSOR_NONE); | |||
HD44780_WriteCommand(LCD_CLEAR); | |||
HD44780_WriteCommand(LCD_HOME); | |||
HD44780_WriteCommand(LCD_INCR_RIGHT); | |||
_delay_ms(50); | |||
//display_HB(MESSAGE); | |||
while (1) { | |||
display_GD(MESSAGE); // Affiche le message sur l'écran | |||
defiler_GD(MESSAGE); | |||
_delay_ms(5); // Délai pour rendre le mouvement lisible | |||
} | |||
} | |||
</syntaxhighlight> | |||
</code> | |||
|| | |||
[[Fichier:Defiler.mp4|vignette]] | |||
|} | |||
Dans l'exemple ci-dessus nous utilisons les fonctions '''display_GD''' et '''defiler_GD''' que nous plaçons dans une boucle infinie ce qui nous permet de gérer un défilement continu. Vous pouvez voir le contenu de ces fonctions ci-dessous. | |||
{|class = "wikitable" | |||
|- | |||
!display_GD !! defiler_GD | |||
|- | |||
|<code> | |||
<syntaxhighlight lang="c" line> | |||
void display_GD(char *message) { | |||
for(int c = 0; c < NB_COLS; c++){ | |||
int address=HD44780_XY2Adrr(NB_ROWS,NB_COLS,0,c); | |||
HD44780_WriteCommand(LCD_ADDRSET|address); | |||
if(message[c] == '\0') | |||
HD44780_WriteData(' '); | |||
else | |||
HD44780_WriteData(message[c]); | |||
} | |||
if(strlen(message) > 16){ | |||
defiler_GD(message); | |||
_delay_ms(50); | |||
} | |||
} | |||
</syntaxhighlight> | |||
</code> | |||
|| | |||
<code> | |||
<syntaxhighlight lang="c" line> | |||
void defiler_GD(char *message) { | |||
char temp = message[0]; | |||
int length = strlen(message); | |||
for(int i = 0; i < length - 1; i++) { | |||
message[i] = message[i+1]; | |||
} | |||
message[length-1] = temp; | |||
} | |||
</syntaxhighlight> | |||
</code> | |||
|} | |||
La fonction '''display_GD''' affiche le message sur une seule ligne de l'écran et le fait défiler si sa longueur dépasse 16 caractères, tandis que '''defiler_GD''' sauvegarde le premier caractère du message puis décale tous les caractères vers la gauche et enfin replace le premier caractère à la fin du message. Le message est donc modifié pour simuler un défilement. | |||
====Caractères spéciaux et quelques codes VT100==== | |||
Pour gérer les caractères spéciaux nous avons écrit la fonction ''' process_special_characters''' qui, avec ses différentes conditions, prend en compte les effets attendus pour le retour à la ligne ('''\n'''), le retour chariot ('''\r'''), et quelques codes VT100 qui déplacent le curseur. L'implémentation de cette fonction est visible ci-dessous. | |||
{|class = "wikitable" | |||
|- | |||
!fonction de gestion des caractères spéciaux | |||
|- | |||
|<code> | |||
<syntaxhighlight lang="c" line> | |||
void process_special_characters(char *message) { | |||
static int current_row = 0; | |||
static int current_col = 0; | |||
static int message_length = 0; // pour stocker la longueur du message affiché | |||
// Si le message a été entièrement affiché, réinitialiser les indices. | |||
if (message_length == strlen(message)) { | |||
current_row = 0; | |||
current_col = 0; | |||
} | |||
// On garde une trace de la longueur du message affiché pour éviter les répétitions. | |||
for (int i = message_length; message[i] != '\0'; i++) { | |||
if (message[i] == '\n') { | |||
// Saut de ligne : on passe à la ligne suivante | |||
current_row = (current_row + 1) % NB_ROWS; | |||
current_col = 0; // Retour au début de la nouvelle ligne | |||
} else if (message[i] == '\r') { | |||
// Retour chariot : on revient au début de la ligne actuelle | |||
current_col = 0; // Retour au début de la ligne | |||
} else if (message[i] == '\033') { // Détection des séquences VT100 | |||
if (message[i + 1] == '[') { | |||
int j = i + 2; // Position après '\033[' | |||
int value = 0; // Valeur numérique du déplacement | |||
// Lire les chiffres jusqu'à ce qu'on atteigne un caractère non numérique | |||
while (message[j] >= '0' && message[j] <= '9') { | |||
value = value * 10 + (message[j] - '0'); | |||
j++; | |||
} | |||
// Vérifier et appliquer le déplacement en fonction du caractère suivant | |||
if (message[j] == 'H') { // Déplacement en haut à gauche (reset du curseur) | |||
current_row = 0; | |||
current_col = 0; | |||
} else if (message[j] == 'A') { // Curseur vers le haut | |||
current_row = (current_row - value + NB_ROWS) % NB_ROWS; | |||
} else if (message[j] == 'B') { // Curseur vers le bas | |||
current_row = (current_row + value) % NB_ROWS; | |||
} else if (message[j] == 'C') { // Curseur vers la droite | |||
current_col = (current_col + value) % NB_COLS; | |||
} else if (message[j] == 'D') { // Curseur vers la gauche | |||
current_col = (current_col - value + NB_COLS) % NB_COLS; | |||
} | |||
i = j; // Mettre à jour l'index pour sauter les caractères traités | |||
} | |||
} else { | |||
// Affichage du caractère normal | |||
int address = HD44780_XY2Adrr(NB_ROWS, NB_COLS, current_row, current_col); | |||
HD44780_WriteCommand(LCD_ADDRSET | address); | |||
HD44780_WriteData(message[i]); | |||
current_col = (current_col + 1) % NB_COLS; | |||
// Gestion du retour automatique à la ligne (si nécessaire) | |||
if (current_col == 0) { | |||
current_row = (current_row + 1) % NB_ROWS; | |||
} | |||
} | |||
// Ajout d'un petit délai pour afficher un caractère à la fois | |||
_delay_ms(20); // Délai de 20ms entre chaque caractère pour l'effet d'affichage | |||
} | |||
// Mettre à jour la longueur du message affiché | |||
message_length = strlen(message); | |||
} | |||
</syntaxhighlight> | |||
</code> | |||
|} | |||
*Retour à la ligne | |||
Le code présenté ci-dessus nous permet de gérer le retour à la ligne. Nous affichons la chaîne de caractères "'''retour...\n a la ligne'''". Vous pouvez voir le résultat du test ci-dessous. | |||
{|class = "wikitable" | |||
|- | |||
!section de gestion du retour à la ligne !! Vidéo de fonctionnement | |||
|- | |||
|<code> | |||
<syntaxhighlight lang="c" line> | |||
for (int i = message_length; message[i] != '\0'; i++) { | |||
if (message[i] == '\n') { | |||
// Saut de ligne : on passe à la ligne suivante | |||
current_row = (current_row + 1) % NB_ROWS; | |||
current_col = 0; // Retour au début de la nouvelle ligne | |||
} | |||
</syntaxhighlight> | |||
</code> | |||
|| | |||
[[Fichier:Rl.mp4|vignette]] | |||
|} | |||
*Retour chariot | |||
Nous pouvons également gérer le retour chariot. Nous affichons la chaîne de caractères "'''retour...\r charriot'''". Vous pouvez voir le résultat du test ci-dessous. | |||
{|class = "wikitable" | |||
|- | |||
!section de gestion du retour à la ligne + retour chariot !! Vidéo de fonctionnement | |||
|- | |||
|<code> | |||
<syntaxhighlight lang="c" line> | |||
for (int i = message_length; message[i] != '\0'; i++) { | |||
if (message[i] == '\n') { | |||
// Saut de ligne : on passe à la ligne suivante | |||
current_row = (current_row + 1) % NB_ROWS; | |||
current_col = 0; // Retour au début de la nouvelle ligne | |||
} else if (message[i] == '\r') { | |||
// Retour chariot : on revient au début de la ligne actuelle | |||
current_col = 0; // Retour au début de la ligne | |||
} | |||
</syntaxhighlight> | |||
</code> | |||
|| | |||
[[Fichier:Ret char.mp4|vignette]] | |||
|} | |||
* Codes VT100 | |||
Nous pouvons également gérer quelques codes VT100. Nous affichons la chaîne de caractères "'''PICO\033[3Cecran\033[1Bbin6\033[1B \rbye ;)'''". Vous pouvez voir le résultat du test ci-dessous. | |||
{|class = "wikitable" | |||
|- | |||
!section de gestion du retour à la ligne + retour chariot + VT100 !! Vidéo de fonctionnement | |||
|- | |||
|<code> | |||
<syntaxhighlight lang="c" line> | |||
for (int i = message_length; message[i] != '\0'; i++) { | |||
if (message[i] == '\n') { | |||
// Saut de ligne : on passe à la ligne suivante | |||
current_row = (current_row + 1) % NB_ROWS; | |||
current_col = 0; // Retour au début de la nouvelle ligne | |||
} else if (message[i] == '\r') { | |||
// Retour chariot : on revient au début de la ligne actuelle | |||
current_col = 0; // Retour au début de la ligne | |||
} else if (message[i] == '\033') { // Détection des séquences VT100 | |||
if (message[i + 1] == '[') { | |||
int j = i + 2; // Position après '\033[' | |||
int value = 0; // Valeur numérique du déplacement | |||
// Lire les chiffres jusqu'à ce qu'on atteigne un caractère non numérique | |||
while (message[j] >= '0' && message[j] <= '9') { | |||
value = value * 10 + (message[j] - '0'); | |||
j++; | |||
} | |||
// Vérifier et appliquer le déplacement en fonction du caractère suivant | |||
if (message[j] == 'H') { // Déplacement en haut à gauche (reset du curseur) | |||
current_row = 0; | |||
current_col = 0; | |||
} else if (message[j] == 'A') { // Curseur vers le haut | |||
current_row = (current_row - value + NB_ROWS) % NB_ROWS; | |||
} else if (message[j] == 'B') { // Curseur vers le bas | |||
current_row = (current_row + value) % NB_ROWS; | |||
} else if (message[j] == 'C') { // Curseur vers la droite | |||
current_col = (current_col + value) % NB_COLS; | |||
} else if (message[j] == 'D') { // Curseur vers la gauche | |||
current_col = (current_col - value + NB_COLS) % NB_COLS; | |||
} | |||
i = j; // Mettre à jour l'index pour sauter les caractères traités | |||
} | |||
} | |||
</syntaxhighlight> | |||
</code> | |||
|| | |||
[[Fichier:VT cent.mp4|vignette]] | |||
|} | |||
=== Test sur la RAM (FM25W256-G) === | |||
Afin de vérifier le bon fonctionnement de notre RAM (FM25W256-G), nous tentons d'écrire dessus, puis d'afficher les caractères écrits sur notre écran (HD44780). Pour cela, nous utilisons le code de gestion de l'affichage sur l'écran, auquel nous ajoutons quelques fonctions de notre conception permettant l'écriture sur la RAM. | |||
Pour la configuration de la RAM, nous utilisons le tableau de commandes ci-dessous présent dans la datasheet du FM25W256-G. | |||
{|class = "wikitable" | |||
|- | |||
!Table de commande FM25W256-G | |||
|- | |||
|[[Fichier:Table com.png|vignette]] | |||
|} | |||
Ces commandes sont définies dans notre code | |||
{| class="wikitable" | |||
|+ Définition des commandes de la RAM | |||
|- | |||
| <syntaxhighlight lang="c" line> | |||
// Instructions SPI pour la RAM FM25W256 | |||
#define WREN 0x06 // Write Enable | |||
#define WRITE 0x02 // écriture dans la mémoire | |||
#define READ 0x03 // lecture de la mémoire | |||
#define FRAM_SIZE 32768 // Taille de la mémoire FM25W256 en octets (32 Ko) | |||
</syntaxhighlight> | |||
|} | |||
Les principales fonctions que nous utilisons pour la configuration de la RAM SPI sont '''FRAM_read''' ,'''FRAM_write''' et '''FRAM_clear''' qui nous permettent respectivement de lire le contenu de la RAM, d'écrire dans la RAM et enfin d'effacer le contenu de la RAM. Vous pouvez voir ces fonctions ci-dessous. | |||
{| class="wikitable" | |||
|+ Définition des fonctions pour la RAM FM25W256 | |||
|- | |||
! Lecture de la RAM | |||
|- | |||
| <syntaxhighlight lang="c"> | |||
void FRAM_read(uint16_t address, char *buffer, uint8_t length) { | |||
CS_LOW(); | |||
SPI_transfer(READ); // Commande de lecture | |||
SPI_transfer((address >> 8) & 0xFF); // Adresse haute | |||
SPI_transfer(address & 0xFF); // Adresse basse | |||
for (uint8_t i = 0; i < length; i++) { | |||
buffer[i] = SPI_transfer(0x00); // Lire les données | |||
} | |||
CS_HIGH(); | |||
} | |||
</syntaxhighlight> | |||
|- | |||
! Écriture dans la RAM | |||
|- | |||
| <syntaxhighlight lang="c"> | |||
void FRAM_write(uint16_t address, char *data) { | |||
FRAM_write_enable(); | |||
CS_LOW(); | |||
SPI_transfer(WRITE); // Commande d'écriture | |||
SPI_transfer((address >> 8) & 0xFF); // Adresse haute | |||
SPI_transfer(address & 0xFF); // Adresse basse | |||
for (uint8_t i = 0; i < strlen(data); i++) { | |||
SPI_transfer(data[i]); // Écrire chaque caractère | |||
} | |||
CS_HIGH(); | |||
} | |||
</syntaxhighlight> | |||
|- | |||
! Effaçage de la RAM | |||
|- | |||
| <syntaxhighlight lang="c"> | |||
void FRAM_clear() { | |||
FRAM_write_enable(); // Activer l'écriture | |||
CS_LOW(); | |||
SPI_transfer(WRITE); // Commande d'écriture | |||
SPI_transfer(0x00); // Adresse haute (MSB) | |||
SPI_transfer(0x00); // Adresse basse (LSB) | |||
for (uint16_t i = 0; i < FRAM_SIZE; i++) { | |||
SPI_transfer(0x00); // Écrire 0x00 à chaque adresse | |||
} | |||
CS_HIGH(); | |||
} | |||
</syntaxhighlight> | |||
|} | |||
Grâce à ces fonctions nous écrivons la chaîne de caractères '''"PICO_Bin6"''' dans la RAM SPI puis nous affichons le contenu de la RAM avec notre écran HD44780. Vous pouvez voir le résultat ci-dessous. | |||
{| class="wikitable" | |||
|- | |||
! Utilisation des fonctions dans le main | |||
! image résultat | |||
|- | |||
| <syntaxhighlight lang="c"> | |||
int main(void) { | |||
HD44780_Initialize(); | |||
HD44780_WriteCommand(LCD_ON|CURSOR_NONE); | |||
HD44780_WriteCommand(LCD_CLEAR); | |||
HD44780_WriteCommand(LCD_HOME); | |||
HD44780_WriteCommand(LCD_INCR_RIGHT); | |||
_delay_ms(50); | |||
char read_buffer[10] = {0}; // Buffer pour lire les données | |||
SPI_init(); | |||
FRAM_clear(); // Effacer toute la mémoire avant d'écrire | |||
// Écriture dans la RAM à l'adresse 0x0000 | |||
FRAM_write(0x0000, "PICO_bin6"); | |||
_delay_ms(100); | |||
// Lecture des données depuis l'adresse 0x0000 | |||
FRAM_read(0x0000, read_buffer, 9); | |||
read_buffer[9] = '\0'; // Ajout d'un caractère de fin de chaîne | |||
while (1) { | |||
// Boucle infinie | |||
display_GD(read_buffer); | |||
_delay_ms(500); | |||
} | |||
return 0; | |||
} | |||
</syntaxhighlight> | |||
| | |||
[[Fichier:Lect ram.jpg|vignette]] | |||
|} | |||
Nous n'arrivons cependant pas à expliquer l'origine des caractères présents après ceux que nous avons écrits sur la RAM. Notre principale hypothèse est qu'il s'agit de caractères initialement présents sur la RAM, qui n'ont pas été effacés. Nous décidons néanmoins de continuer dans notre gestion de la communication avec la carte fille. | |||
===Communication Picoshield-écran=== | |||
<div style="text-align: justify;"> | |||
Après avoir testé le bon fonctionnement de la RAM nous passons à la communication SPI entre le PicoShield et la carte fille écran. Le Picoshield envoie des caractères par SPI qui sont réçus puis traités via minicom par la carte fille qui les affiche ensuite sur l'écran lcd HD44780. Pour réaliser cette tâche nous programmons d'abord le picoshield en tant que maître pour la gestion de l'envoi des caractères, puis c'est au tour de la carte fille écran d'être programmée en esclave pour recevoir et afficher les caractères envoyés par le picoshield. Vous pouvez voir les codes de programmation des cartes picoshield et écran ci-dessous. | |||
</div> | |||
{| class="wikitable" style="width:100%; border:1px solid black;" | |||
|+ **Aperçu des Codes SPI Esclave (écran) et SPI Maître (picoshield)** | |||
|- | |||
! style="width:50%; background-color:#f0f0f0;" | **Code SPI Esclave (Affichage LCD)** | |||
! style="width:50%; background-color:#f0f0f0;" | **Code SPI Maître** | |||
|- | |||
| | |||
<syntaxhighlight lang="c"> | |||
#include <avr/io.h> | |||
#include <util/delay.h> | |||
#include "lcd.h" | |||
#define F_CPU 16000000UL | |||
void spi_init_slave(void) { | |||
DDRB &= ~((1 << PB3) | (1 << PB5)); // MOSI et SCK en entrée | |||
DDRB |= (1 << PB4); // MISO en sortie | |||
SPCR = (1 << SPE); // Activer le SPI en mode esclave | |||
} | |||
char spi_recevoir_caractere(void) { | |||
while (!(SPSR & (1 << SPIF))); // Attendre la réception complète | |||
return SPDR; // Retourner la donnée reçue | |||
} | |||
void afficher_caractere_sur_LCD(char caractere) { | |||
HD44780_WriteData(caractere); | |||
} | |||
int main(void) { | |||
// Initialisation SPI et LCD | |||
spi_init_slave(); | |||
HD44780_Initialize(); | |||
HD44780_WriteCommand(LCD_ON | CURSOR_BLINK); | |||
HD44780_WriteCommand(LCD_CLEAR); | |||
HD44780_WriteCommand(LCD_HOME); | |||
while (1) { | |||
// Réception d'un caractère via SPI | |||
char caractere = spi_recevoir_caractere(); | |||
// Affichage du caractère reçu sur l'écran LCD | |||
afficher_caractere_sur_LCD(caractere); | |||
} | |||
return 0; | |||
} | |||
</syntaxhighlight> | |||
| | |||
<syntaxhighlight lang="c"> | |||
#include <avr/io.h> | |||
#include <avr/interrupt.h> | |||
#include "SPI.h" | |||
#include "serial.h" | |||
#define F_CPU 16000000UL | |||
#include <util/delay.h> | |||
// Définition des broches SPI | |||
#define SPI_SS_PORT PORTC | |||
#define SPI_SS_PIN PC0 | |||
char valeur; | |||
void spi_envoyer_caractere(char c) { | |||
spi_activer(); | |||
spi_echange(c); | |||
spi_desactiver(); | |||
} | |||
void set_valeur(void) { | |||
while (1) { | |||
valeur = serie_recevoir(); // Lecture du caractère depuis Minicom | |||
serie_envoyer(valeur); // Echo vers le terminal Minicom | |||
spi_envoyer_caractere(valeur); // Transmission du caractère via SPI | |||
} | |||
} | |||
int main(void) { | |||
spi_init(); // Initialisation du SPI | |||
serie_init(9600); // Initialisation du port série | |||
sei(); // Activation des interruptions | |||
while (1) { | |||
set_valeur(); | |||
} | |||
return 0; | |||
} | |||
</syntaxhighlight> | |||
|} | |||
Une fois les cartes programmées nous connectons notre écran à notre Picoshield par le port HE-10 de ce dernier. Nous ouvrons ensuite un terminal minicom avec la commande suivante '''"minicom -D /dev/ttyUSB0 -b 9600"''' et envoyons les caractères qui s'affichent sur l'écran. | |||
Vous pouvez voir cela dans la vidéo ci-dessous. | |||
{| class="wikitable" style="width:50%; border:1px solid black;" | |||
|+ **Vidéo Démonstration** | |||
|- | |||
| [[Fichier:Ecran minicom.mp4|centre|vignette]] | |||
|} | |||
=Bilan & Conclusion= | |||
Nous sommes au terme de notre projet, et les objectifs que nous avons atteints sont les suivants : | |||
*Une carte Shield fonctionnelle et programmable. | |||
*Un ordonnanceur fonctionnel avec différentes tâches (LEDs clignotantes, affichage sept-segments et matrice de LEDs), fonctionnant simultanément et disposant chacune des états "WAKE" et "SLEEPY". | |||
*Une carte fille écran LCD fonctionnelle, avec un écran gérant l'affichage des caractères spéciaux ainsi que quelques commandes VT100 et une mémoire RAM SPI sur laquelle nous pouvons lire et écrire. | |||
Cependant, faute de temps, nous n'avons pas pu aborder les primitives systèmes. En effet, nous avons pris beaucoup plus de temps que prévu pour concevoir une carte fille fonctionnelle (la première carte présentait de nombreux problèmes), un temps précieux qui nous aurait sans doute permis de réaliser les primitives systèmes manquantes. | |||
Ce projet, qui s'est étendu sur tout le semestre, nous a permis d'approfondir nos connaissances en conception de cartes électroniques, en analyse de codes C, ainsi que notre compréhension des microcontrôleurs en général. | |||
Nous tenons à remercier nos encadrants, M. REDON et M. BOE, pour leur accompagnement et les conseils donnés tout au long du projet. |
Version actuelle datée du 30 janvier 2025 à 21:01
GIT
Nos codes et nos conceptions kicad sont disponibles via notre GIT : https://gitea.plil.fr/yyahiani/pico_yahiani_zongo.git
Objectif
Nous avons pour objectif, avec les trois autres binômes de notre groupe, de construire un pico-ordinateur qui intégrera plusieurs éléments essentiels. Voici les composants que nous allons inclure :
- Un processeur de type microcontrôleur : Cela constituera le cœur de notre pico-ordinateur et permettra de gérer toutes les opérations.
- Un clavier : Il permettra l'entrée de données et d'interagir facilement avec notre dispositif.
- Un dispositif d'affichage : Cela nous permettra de visualiser les informations ainsi que les résultats des opérations effectuées par notre pico-ordinateur.
- Un système d'exploitation : Il sera stocké dans la mémoire flash du microcontrôleur, garantissant un fonctionnement fluide de l'appareil.
- Une mémoire de masse : Nous prévoyons d'ajouter une mémoire de masse pour stocker davantage de données.
- Un dispositif de communication externe : Cela permettra d'interagir avec d'autres dispositifs ou réseaux.
Enfin, pour assurer la communication entre ces éléments, nous mettrons en place un bus série. Cela facilitera les échanges de données et garantira une intégration harmonieuse des composants.
Notre binôme se concentrera sur la réalisation d'une carte écran, qui devra remplir les tâches précisées dans l'énoncé du projet.
En premier lieu, nous avons d'abord réalisé un shield qui nous servira, dans le cas où la carte mère ne serait pas fonctionnelle, à tester nos cartes filles à l'aide d'un Arduino Uno pour prototyper le cœur de la carte mère.
Shield
Realisation
Nous nous sommes aidés des indications données par nos encadrants lors de la première séance pour réaliser le schéma de notre shield. Nous y avons ajouté une puce mémoire AT45DB161D, et la datasheet nous a permis de comprendre comment l'intégrer correctement dans notre schéma. Vous pouvez voir ci-dessous la version finale du routage du PCB du bouclier ainsi que ce dernier après la soudure des différents composants.
Suite à une erreur d'orientation de nos broches, connecter l'Arduino et notre bouclier est devenue une tâche bien plus "compliquée" que prévu. Cependant, grâce à une idée ingénieuse proposée par nos encadrants (M. BOE et M. REDON), nous y sommes parvenus. Le bouclier se connecte donc à l'Arduino de la manière suivante :
Tests
LEDs
Après avoir connecté notre bouclier et l'Arduino nous avons effectué les tests et constaté l'allumage des différentes LEDs; attestant du bon fonctionnement de notre bouclier.
Voici le code C (GIT : pico_yahiani_zongo/Software/codes_Shield/clignotement) qui nous permet de faire clignoter les LEDs.
Code C | Vidéo fonctionnement |
---|---|
|
Puce mémoire
Après avoir testé les LEDs, nous cherchons à tester notre puce mémoire pour assurer son bon fonctionnement. Cependant, nous avons rencontré un petit problème.
Problème
En effet, lors du test de notre puce mémoire, nous avons introduit une carte SD pour vérifier, via le logiciel Arduino, si notre puce mémoire détecte la carte SD. Cependant, cela fut sans succès. Nous avons donc vérifié l'horloge de notre Arduino Uno à l'aide d'un oscilloscope en y injectant ce code (GIT : pico_yahiani_zongo/Software/codes_Shield/test_clk_arduino) :
Code arduino | Visualisation |
---|---|
|
Le signal d'horloge de notre Arduino est correct.
Le problème ne venant pas de l'Arduino, nous avons donc dessoudé puis ressoudé notre puce mémoire et le problème à été résolu, comme vous pouvez le voir ci-dessous.
Connecteurs HE10
À l'aide d'un afficheur 7 segments, nous vérifions le bon fonctionnement des connecteurs HE10 en affichant "GrP6" pour Groupe 6. Pour cela, nous configurons la communication SPI avec avr-gcc grâce au code présent dans notre fichier spi.c, grandement inspiré du code disponible sur le site de M. Redon (https://rex.plil.fr/Enseignement/Systeme/Systeme.PSE/systeme.html). Ci-dessous, vous trouverez un aperçu du code source utilisé pour gérer la communication SPI ainsi que celui utilisé pour gérer l'affichage du 7 segments.
Configuration communication SPI | |
---|---|
| |
Code de gestion du sept segments | Image de fonctionnement |
|
Ordonnanceur
Clignotement de deux LEDS
La principale tâche à accomplir en premier lieu est de faire clignoter deux LEDs à des fréquences différentes. Pour cela nous passerons par plusieurs étapes. La première tâche à réaliser est de programmer le minuteur 1 de l'ATMega328p de sorte à ce qu'il génère une interruption toutes les 20ms. Pour cela nous nous sommes inspirés du code donné par l'un de nos encadrants (M. REDON) et avons programmé le minuteur 1 comme vous pouvez le voir dans notre Git : pico_yahiani_zongo/Software/codes_Shield/ISR_nonNue_1tache/timer.c.
Le Timer 1 est configuré en mode CTC, c'est-à-dire qu'il se réinitialise automatiquement à chaque fois qu'il atteint la valeur OCR1A. Le prescaler est configuré à 256, ce qui divise la fréquence de l'horloge système (16000000 Hz) par 256. Nous avons donc une nouvelle fréquence d'horloge à 62500 Hz, ce qui implique que les coups d'horloge (ticks) s'effectuent toutes les 16 µs. Le timer compte jusqu'à 1250 ticks, ce qui génère une interruption toutes les 20 ms.
La seconde étape consistait à faire clignoter la LED sur la broche Arduino 13 (PB5) en utilisant une ISR non nue avec notre timer. Le code suivant nous a permis de réaliser cette tâche.
Code C | Vidéo de fonctionnement |
---|---|
|
Pour passer d'une ISR non nue à une ISR naked il faut écrire les macros de sauvegardes et de restaurations des registres, ces macros sont écrites dans des fichiers présents dans notre GIT :
- pico_yahiani_zongo/Software/codes_Shield/ordo_tourni_2taches/save_registers.h
- pico_yahiani_zongo/Software/codes_Shield/ordo_tourni_2taches/restore_registers.h
Dans la suite nous avons défini nos processus comme des structures que nous avons stockées dans un tableau.
typedef struct {
void (*tache)(void);
int pile;
int etat;//wake ou sleepy (processus actif ou en attente)
}tache;
tache taches[TACHE_MAX];
Notre prochain objectif est de lancer deux tâches en parallèle en incluant un ordonnanceur à torniquet dans l'ISR. Pour cela, nous utilisons, en plus du timer présenté plus haut, une fonction d'initialisation des tâches (init_taches()) et une ISR nue, cette fois, qui gère la succession des tâches. Nous utilisons cette fois une ISR NAKED pour avoir un contrôle total sur la gestion des registres et pour éviter la surcharge des sauvegardes et/ou restaurations automatiques générées par le compilateur.
Code C | Vidéo de fonctionnement |
---|---|
|
Gestion de l'état endormi
Notre ordonnanceur est désormais capable de lancer plusieurs tâches en parallèle, nous pouvons donc passer à la gestion de l'état endormi de nos processus. Jusqu'à présent, pour effectuer des pauses dans l'exécution de nos tâches, nous utilisions la fonction _delay_ms en incluant la bibliothèque <util/delay.h>. Pour améliorer notre ordonnanceur, nous effectuons maintenant les pauses avec une fonction que nous avons écrite (_delay).
Pour l'utilisation de notre fonction, nous avons redéfini la structure des tâches à laquelle nous avons ajouté un champ "temps" représentant le temps pendant lequel la tâche reste endormie. Nous avons également écrit une fonction ordonnanceur qui décrémente le champ "temps" d'une tâche puis la réveille une fois le temps écoulé. Ci-dessous, vous pouvez voir à quoi ressemblent ces fonctions.
Code fonction ordonnanceur | Code fonction _delay |
---|---|
|
|
La nouvelle structure de tâche se presente de la façon suivante :
|
Ci-dessous, une vidéo de fonctionnement de la gestion de l'état endormi des processus. Vous pouvez voir au début de la vidéo que seule la LED verte est active (état WAKE) pendant que la LED orange est endormie. Une fois le temps d'endormissement écoulé, vous pouvez voir la LED orange commencer à clignoter (elle est passée à l'état WAKE) à la même fréquence que la LED verte.
Pour une bonne gestion de l'état endormi, nous ajoutons à notre liste de tâches une tâche _wait qui ne fait rien en bouclant juste indéfiniment. Cette dernière est importante car lorsque toutes les autres tâches de l'ordonnanceur sont inactives ou en attente, une tâche "neutre" garantit qu'il y a toujours une activité en cours, même si elle est minimale. Cela évite que le processeur tombe dans un état imprévisible ou commence à exécuter du code non souhaité.
Liste des tâches | Vidéo de fonctionnement |
---|---|
|
Gestion communication Port serie et SPI
Avec afficheur sept-segments
Notre ordonnanceur est maintenant capable de gérer le clignotement de deux LEDs en parallèle ainsi que les processus endormis. Il s'agira maintenant pour nous de le complexifier en lui permettant de gérer les communications SPI et port série. Pour ce faire, nous utilisons des codes grandement inspirés de ceux présents sur le site de M. REDON (https://rex.plil.fr/Enseignement/Systeme/Systeme.PSE/systeme.html), qui se trouvent dans les fichiers SPI.c (pour la gestion de la communication SPI) et serial.c (pour le port série).
Gestion SPI | Gestion port série |
---|---|
|
|
Les fonctions serie_envoyer et serie_recevoir comportent chacune une phase d'attente de disponibilité avec les lignes loop_until_bit_is_set(UCSR0A, UDRE0); (qui attend que le bit UDRE0 dans le registre UCSR0A soit à 1 ; ce bit indique que le registre de transmission (UDR0) est prêt à recevoir un nouvel octet) et loop_until_bit_is_set(UCSR0A, RXC0); (qui attend que le bit RXC0 dans le registre UCSR0A soit à 1 ; ce bit indique qu'un octet a été reçu via le port série et est disponible dans le registre UDR0).
Il y a ensuite une phase de transmission pour serie_envoyer avec UDR0 = c; : l'octet est chargé dans le registre UDR0. Une fois chargé, l'octet est automatiquement envoyé via le port série. Pour serie_recevoir, la phase suivante est la lecture avec return UDR0; la fonction renvoie directement le contenu du registre UDR0, qui contient l'octet reçu.
Dans le fichier serial.c (image droite ci-dessus), nous avons ajouté la fonction set_valeur qui utilise simultanément les fonctions serie_envoyer et serie_recevoir pour gérer l'écriture et la lecture via le port série dans la même tâche. La valeur mise dans serie_recevoir est la même que celle utilisée dans la tâche de gestion de l'afficheur à sept segments. Nous ajoutons ensuite set_valeur dans notre liste de tâches. Vous pouvez voir le résultat que nous obtenons ci-dessous.
Liste des tâches | Vidéo de fonctionnement |
---|---|
|
Avec Matrice de LEDS
De la même manière qu'avec le sept-segments, nous utilisons notre ordonnanceur pour commander la matrice de LEDs par l'intermédiaire des modes de communication SPI et port série. À la différence du sept-segment, la matrice de LEDs nécessite une configuration des différents caractères qu'elle pourra afficher. Pour cela, nous initialisons une matrice définissant les LEDs qui doivent s'allumer pour afficher les caractères souhaités. Nous affichons l'ensemble des caractères hexadécimaux grâce à cette matrice (contenue dans le fichier matrice.h). Ci-dessous, vous pouvez voir les résultats que nous obtenons ainsi que les codes que nous avons utilisés.
Matrice de configuration des LEDs | |
---|---|
| |
Code de gestion de la matrice de LEDs | Vidéo de fonctionnement |
|
Carte fille écran LCD
Conception de la carte fille
La carte fille écran comporte un ATMega328p et un écran LCD à base de contrôleur HD44780. Nous utilisons, comme precisé dans le wiki, un potentiomètre pour régler la luminosité des cristaux liquides ainsi qu'un connecteur HE10 pour la connexion de cette carte fille à notre carte mère sur lequel nous avons bien prevu une ligne de sélection SPI. Aucune ligne d'interruption n'était initialement nécessaire, mais suite au rajout d'une RAM SPI (FM25W256-G) nous avons dû en rajouter une pour gérer les interruptions.La carte mère écrit dans la RAM et le microcontôleur de la carte fille rafraichit l'écran régulièrement; en outre pour éviter les conflits entre la carte mère qui écrit et la carte fille qui lit il est nécessaire d'effectuer une gestion de priorités entre les différents signaux de MOSI, d'horloge et de selection. Cette gestion se fait à l'aide de circuits intermédaires présents dans notre schématique inspirés des circuits "NMOS highside" que nous avons réalisé avec l'aide de nos Encadrants M. BOE et M. REDON.
Voici les étapes de conceptions de notre carte fille:
Première Version
réception de la carte PCB:
Carte fille avant soudure | Carte fille après soudure |
---|---|
Problèmes rencontrés avec la première version de la carte
Lors de la conception et du routage de la première version de la carte destinée à gérer l'écran LCD HD44780, plusieurs erreurs ont été identifiées. Ces erreurs ont gravement impacté le fonctionnement de la carte, rendant nécessaire la conception d'une nouvelle version. Voici un récapitulatif des problèmes rencontrés et de leur impact :
- Erreur sur l'empreinte du regulateur ISR
Une empreinte SMD a été utilisée pour le régulateur ISR, alors que nous ne disposions que du composant en version traversante. Nous avons dû improviser en soudant des fils pour relier les différentes broches (MOSI, MISO, RST, etc.). Cette approche temporaire a engendré des problèmes majeurs, tels que des connexions instables, des court-circuits et une exposition accrue au bruit électromagnétique. Ces problèmes ont compromis la fiabilité des communications SPI et l'alimentation du circuit, comme nous l'a fait remarquer l'un de nos encadrants, M. REDON. Nous avons donc dans la nouvelle version, remplacé cette empreinte par sa version traversante.
- Connexion incorrecte des broches E et RS
Les broches E (Enable) et RS (Register Select) de l'écran LCD ont été connectées directement au 5V, au lieu d’être reliées aux broches programmables de l’ATMega328p. Ces broches jouent un rôle essentiel dans la communication avec le contrôleur HD44780, en permettant de sélectionner le registre de commande ou de données et de déclencher des opérations d'écriture. En les connectant directement au 5V, il était impossible de les programmer ou de les utiliser correctement. Cela a totalement empêché la configuration et l’envoi de commandes à l’écran LCD. Dans la nouvelle version de la carte, nous avons donc relié les broches E et RS à des broches numériques de l’ATMega328p pour assurer leur contrôle logiciel.
- Erreur sur l'alimentation de l’écran LCD
L'alimentation de l’écran a été liée en série avec un condensateur, au lieu d'être connectée en parallèle. Cette configuration en série empêchait la bonne alimentation de l'écran après les modifications effectuées sur les broches RS et E, rendant ce dernier inopérant. Nous avons donc modifié le schéma pour connecter le condensateur en parallèle avec l’alimentation, ce qui est nécessaire pour stabiliser la tension d’alimentation, comme nous l'a fait remarquer l'un de nos encadrants, M. BOE.
Seconde Version
Une fois la seconde version de la carte reçue nous somme passés à la soudure des différents composants. Ci-dessous vous pouvez voir notre carte fille avant et après la phase de soudure.
Carte fille avant soudure | Carte fille après soudure |
---|---|
Programmation de la carte fille
Test de la carte fille
Avant d'entamer la gestion des différentes fonctionnalités de l'écran LCD, nous vérifions si les composants ont été bien soudés et si la carte est fonctionnelle et programmable, en faisant clignoter une de ses LEDs avec du code Arduino présent dans notre répertoire Git. Vous pouvez voir les résultats de ce test ci-dessous :
Code arduino | Vidéo fonctionnement |
---|---|
|
Test de l'écran LCD HD44780
Maintenant que notre carte fille est fonctionnelle, nous commençons la programmation de notre écran LCD. Encore une fois, nous passons par une étape de test où nous vérifions le bon fonctionnement de l'écran LCD. À l'aide de code Arduino, nous affichons le classique "Hello, World!" sur notre écran. Vous pouvez voir les résultats de ce test ci-dessous.
Code arduino | Image résultat |
---|---|
|
Gestion de l'affichage
Défilement
Notre écran affiche bien les caractères souhaités, nous pouvons donc passer à la réalisation des différentes fonctions de gestion de notre écran LCD HD44780. Pour ce faire, nous avons utilisé le code de gestion du HD44780 que nous a donné l'un de nos encadrants, M. REDON. En utilisant les fonctions présentes dans ce code nous arrivons à faire défiler des caractères sur notre écran.
Code arduino | Image résultat |
---|---|
|
Dans l'exemple ci-dessus nous utilisons les fonctions display_GD et defiler_GD que nous plaçons dans une boucle infinie ce qui nous permet de gérer un défilement continu. Vous pouvez voir le contenu de ces fonctions ci-dessous.
display_GD | defiler_GD |
---|---|
|
|
La fonction display_GD affiche le message sur une seule ligne de l'écran et le fait défiler si sa longueur dépasse 16 caractères, tandis que defiler_GD sauvegarde le premier caractère du message puis décale tous les caractères vers la gauche et enfin replace le premier caractère à la fin du message. Le message est donc modifié pour simuler un défilement.
Caractères spéciaux et quelques codes VT100
Pour gérer les caractères spéciaux nous avons écrit la fonction process_special_characters qui, avec ses différentes conditions, prend en compte les effets attendus pour le retour à la ligne (\n), le retour chariot (\r), et quelques codes VT100 qui déplacent le curseur. L'implémentation de cette fonction est visible ci-dessous.
fonction de gestion des caractères spéciaux |
---|
|
- Retour à la ligne
Le code présenté ci-dessus nous permet de gérer le retour à la ligne. Nous affichons la chaîne de caractères "retour...\n a la ligne". Vous pouvez voir le résultat du test ci-dessous.
section de gestion du retour à la ligne | Vidéo de fonctionnement |
---|---|
|
- Retour chariot
Nous pouvons également gérer le retour chariot. Nous affichons la chaîne de caractères "retour...\r charriot". Vous pouvez voir le résultat du test ci-dessous.
section de gestion du retour à la ligne + retour chariot | Vidéo de fonctionnement |
---|---|
|
- Codes VT100
Nous pouvons également gérer quelques codes VT100. Nous affichons la chaîne de caractères "PICO\033[3Cecran\033[1Bbin6\033[1B \rbye ;)". Vous pouvez voir le résultat du test ci-dessous.
section de gestion du retour à la ligne + retour chariot + VT100 | Vidéo de fonctionnement |
---|---|
|
Test sur la RAM (FM25W256-G)
Afin de vérifier le bon fonctionnement de notre RAM (FM25W256-G), nous tentons d'écrire dessus, puis d'afficher les caractères écrits sur notre écran (HD44780). Pour cela, nous utilisons le code de gestion de l'affichage sur l'écran, auquel nous ajoutons quelques fonctions de notre conception permettant l'écriture sur la RAM.
Pour la configuration de la RAM, nous utilisons le tableau de commandes ci-dessous présent dans la datasheet du FM25W256-G.
Table de commande FM25W256-G |
---|
Ces commandes sont définies dans notre code
// Instructions SPI pour la RAM FM25W256
#define WREN 0x06 // Write Enable
#define WRITE 0x02 // écriture dans la mémoire
#define READ 0x03 // lecture de la mémoire
#define FRAM_SIZE 32768 // Taille de la mémoire FM25W256 en octets (32 Ko)
|
Les principales fonctions que nous utilisons pour la configuration de la RAM SPI sont FRAM_read ,FRAM_write et FRAM_clear qui nous permettent respectivement de lire le contenu de la RAM, d'écrire dans la RAM et enfin d'effacer le contenu de la RAM. Vous pouvez voir ces fonctions ci-dessous.
Lecture de la RAM |
---|
void FRAM_read(uint16_t address, char *buffer, uint8_t length) {
CS_LOW();
SPI_transfer(READ); // Commande de lecture
SPI_transfer((address >> 8) & 0xFF); // Adresse haute
SPI_transfer(address & 0xFF); // Adresse basse
for (uint8_t i = 0; i < length; i++) {
buffer[i] = SPI_transfer(0x00); // Lire les données
}
CS_HIGH();
}
|
Écriture dans la RAM |
void FRAM_write(uint16_t address, char *data) {
FRAM_write_enable();
CS_LOW();
SPI_transfer(WRITE); // Commande d'écriture
SPI_transfer((address >> 8) & 0xFF); // Adresse haute
SPI_transfer(address & 0xFF); // Adresse basse
for (uint8_t i = 0; i < strlen(data); i++) {
SPI_transfer(data[i]); // Écrire chaque caractère
}
CS_HIGH();
}
|
Effaçage de la RAM |
void FRAM_clear() {
FRAM_write_enable(); // Activer l'écriture
CS_LOW();
SPI_transfer(WRITE); // Commande d'écriture
SPI_transfer(0x00); // Adresse haute (MSB)
SPI_transfer(0x00); // Adresse basse (LSB)
for (uint16_t i = 0; i < FRAM_SIZE; i++) {
SPI_transfer(0x00); // Écrire 0x00 à chaque adresse
}
CS_HIGH();
}
|
Grâce à ces fonctions nous écrivons la chaîne de caractères "PICO_Bin6" dans la RAM SPI puis nous affichons le contenu de la RAM avec notre écran HD44780. Vous pouvez voir le résultat ci-dessous.
Utilisation des fonctions dans le main | image résultat |
---|---|
int main(void) {
HD44780_Initialize();
HD44780_WriteCommand(LCD_ON|CURSOR_NONE);
HD44780_WriteCommand(LCD_CLEAR);
HD44780_WriteCommand(LCD_HOME);
HD44780_WriteCommand(LCD_INCR_RIGHT);
_delay_ms(50);
char read_buffer[10] = {0}; // Buffer pour lire les données
SPI_init();
FRAM_clear(); // Effacer toute la mémoire avant d'écrire
// Écriture dans la RAM à l'adresse 0x0000
FRAM_write(0x0000, "PICO_bin6");
_delay_ms(100);
// Lecture des données depuis l'adresse 0x0000
FRAM_read(0x0000, read_buffer, 9);
read_buffer[9] = '\0'; // Ajout d'un caractère de fin de chaîne
while (1) {
// Boucle infinie
display_GD(read_buffer);
_delay_ms(500);
}
return 0;
}
|
Nous n'arrivons cependant pas à expliquer l'origine des caractères présents après ceux que nous avons écrits sur la RAM. Notre principale hypothèse est qu'il s'agit de caractères initialement présents sur la RAM, qui n'ont pas été effacés. Nous décidons néanmoins de continuer dans notre gestion de la communication avec la carte fille.
Communication Picoshield-écran
Après avoir testé le bon fonctionnement de la RAM nous passons à la communication SPI entre le PicoShield et la carte fille écran. Le Picoshield envoie des caractères par SPI qui sont réçus puis traités via minicom par la carte fille qui les affiche ensuite sur l'écran lcd HD44780. Pour réaliser cette tâche nous programmons d'abord le picoshield en tant que maître pour la gestion de l'envoi des caractères, puis c'est au tour de la carte fille écran d'être programmée en esclave pour recevoir et afficher les caractères envoyés par le picoshield. Vous pouvez voir les codes de programmation des cartes picoshield et écran ci-dessous.
**Code SPI Esclave (Affichage LCD)** | **Code SPI Maître** |
---|---|
#include <avr/io.h>
#include <util/delay.h>
#include "lcd.h"
#define F_CPU 16000000UL
void spi_init_slave(void) {
DDRB &= ~((1 << PB3) | (1 << PB5)); // MOSI et SCK en entrée
DDRB |= (1 << PB4); // MISO en sortie
SPCR = (1 << SPE); // Activer le SPI en mode esclave
}
char spi_recevoir_caractere(void) {
while (!(SPSR & (1 << SPIF))); // Attendre la réception complète
return SPDR; // Retourner la donnée reçue
}
void afficher_caractere_sur_LCD(char caractere) {
HD44780_WriteData(caractere);
}
int main(void) {
// Initialisation SPI et LCD
spi_init_slave();
HD44780_Initialize();
HD44780_WriteCommand(LCD_ON | CURSOR_BLINK);
HD44780_WriteCommand(LCD_CLEAR);
HD44780_WriteCommand(LCD_HOME);
while (1) {
// Réception d'un caractère via SPI
char caractere = spi_recevoir_caractere();
// Affichage du caractère reçu sur l'écran LCD
afficher_caractere_sur_LCD(caractere);
}
return 0;
}
|
#include <avr/io.h>
#include <avr/interrupt.h>
#include "SPI.h"
#include "serial.h"
#define F_CPU 16000000UL
#include <util/delay.h>
// Définition des broches SPI
#define SPI_SS_PORT PORTC
#define SPI_SS_PIN PC0
char valeur;
void spi_envoyer_caractere(char c) {
spi_activer();
spi_echange(c);
spi_desactiver();
}
void set_valeur(void) {
while (1) {
valeur = serie_recevoir(); // Lecture du caractère depuis Minicom
serie_envoyer(valeur); // Echo vers le terminal Minicom
spi_envoyer_caractere(valeur); // Transmission du caractère via SPI
}
}
int main(void) {
spi_init(); // Initialisation du SPI
serie_init(9600); // Initialisation du port série
sei(); // Activation des interruptions
while (1) {
set_valeur();
}
return 0;
}
|
Une fois les cartes programmées nous connectons notre écran à notre Picoshield par le port HE-10 de ce dernier. Nous ouvrons ensuite un terminal minicom avec la commande suivante "minicom -D /dev/ttyUSB0 -b 9600" et envoyons les caractères qui s'affichent sur l'écran. Vous pouvez voir cela dans la vidéo ci-dessous.
Bilan & Conclusion
Nous sommes au terme de notre projet, et les objectifs que nous avons atteints sont les suivants :
- Une carte Shield fonctionnelle et programmable.
- Un ordonnanceur fonctionnel avec différentes tâches (LEDs clignotantes, affichage sept-segments et matrice de LEDs), fonctionnant simultanément et disposant chacune des états "WAKE" et "SLEEPY".
- Une carte fille écran LCD fonctionnelle, avec un écran gérant l'affichage des caractères spéciaux ainsi que quelques commandes VT100 et une mémoire RAM SPI sur laquelle nous pouvons lire et écrire.
Cependant, faute de temps, nous n'avons pas pu aborder les primitives systèmes. En effet, nous avons pris beaucoup plus de temps que prévu pour concevoir une carte fille fonctionnelle (la première carte présentait de nombreux problèmes), un temps précieux qui nous aurait sans doute permis de réaliser les primitives systèmes manquantes.
Ce projet, qui s'est étendu sur tout le semestre, nous a permis d'approfondir nos connaissances en conception de cartes électroniques, en analyse de codes C, ainsi que notre compréhension des microcontrôleurs en général.
Nous tenons à remercier nos encadrants, M. REDON et M. BOE, pour leur accompagnement et les conseils donnés tout au long du projet.