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

De projets-se.plil.fr
Aller à la navigation Aller à la recherche
Ligne 747 : Ligne 747 :
|}
|}


Maintenant que notre carte fille est fonctionnelle nous commencons la programmation de notre Ecran LCD. Encore une fois nous passons par une étape de test où nous vérifions le bon fonctionnement de l'écran LCD. Avec du code arduino nous affichons le classique '''hello world''' sur notre écran. Vous pouvez voir les résultats de ce test ci-dessous.  
Maintenant que notre carte fille est fonctionnelle nous commençons la programmation de notre Ecran LCD. Encore une fois nous passons par une étape de test où nous vérifions le bon fonctionnement de l'écran LCD. Avec du code arduino nous affichons le classique '''hello world''' sur notre écran. Vous pouvez voir les résultats de ce test ci-dessous.  


{|class = "wikitable"
{|class = "wikitable"
Ligne 762 : Ligne 762 :
|}
|}


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ésentent dans ce code nous arrivons à faire défiler des caractères sur notre écran.
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"
{|class = "wikitable"
Ligne 792 : Ligne 792 :
||
||
[[Fichier:Defiler.mp4|vignette]]
[[Fichier:Defiler.mp4|vignette]]
|}
Dans cet exemple nous utilisons les fonctions '''display_GD''' et '''defiler_GD''' que nous plaçons dans une boucle infini ce qui nous permet de gérer un defilement 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>
|}
|}

Version du 11 janvier 2025 à 19:35

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 3 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, permettant de gérer toutes les opérations.
  • Un clavier : Pour l'entrée de données, il nous permettra d'interagir facilement avec notre dispositif.
  • Un dispositif d'affichage : Cela nous permettra de visualiser les informations et les résultats des opérations effectuées par notre pico-ordinateur.
  • Un système d'exploitation : Celui-ci sera stocké dans la mémoire flash du microcontrôleur, garantissant un fonctionnement fluide de notre appareil.
  • Une mémoire de masse : Nous prévoyons d'ajouter une mémoire qui ira au-delà de la mémoire flash, pour stocker davantage de données.
  • Un dispositif de communication externe : Cela nous permettra d'interagir avec d'autres dispositifs ou réseaux.

Enfin, pour assurer la communication entre tous ces éléments, nous allons mettre en place un bus série. Cela facilitera les échanges de données et garantira une intégration harmonieuse de chaque composant. Pour ce qui concerne notre binome notre travail portera sur la realisation d'une carte ecran qui devra remplir les taches precisé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 n'abouti pas, à pouvoir tester nos cartes filles à l'aide d'un Arduino uno pour prototyper le coeur d'une carte mère.

Shield

Realisation

Nous nous sommes aidés des indications du professeur en première séance pour réaliser le schéma de notre shield, nous y avons rajouté une puce mémoire AT45DB161D, la datasheet nous a permis de savoir comment intégrer notre puce memoire dans notre schéma. Vous pouvez voir ci-dessous la version finale du routage concernant le PCB du Bouclier ainsi que ce dernier après soudures des différents composants.

schematique du Bouclier
PCB routé
Carte après soudure


Suite à une erreur d'orientation de nos broches; connecter l'arduino et notre Bouclier devenait une tâche beaucoup plus "compliquée" que prévu, mais grâce à une idée ingénieuse proposée par nos encadrants (Mr BOE et Mr REDON) nous y sommes parvenus. Le bouclier se connecte donc à l'arduino de la façon suivante:

connexion Bouclier-Arduino
connexion Bouclier-Arduino

Tests

LEDS

Après avoir connecté notre bouclier et l'arduino nous avons effectué les tests et constaté l'allumage des differentes LED; attestant du bon fonctionnement de notre bouclier.

Voici le code C (GIT : pico_yahiani_zongo/Software/clignotement) qui nous permet de faire clignoter les leds.

Code C Vidéo fonctionnement

#define BROCHE_LED_BLEU 4
#define BROCHE_LED_ORANGE 7
#define BROCHE_LED_ROUGE 14
#define BROCHE_LED_JAUNE 17
#define BROCHE_D3 0

void setup()
{
  pinMode(BROCHE_LED_BLEU, OUTPUT);
  pinMode(BROCHE_LED_JAUNE, OUTPUT);
  pinMode(BROCHE_LED_ROUGE, OUTPUT);
  pinMode(BROCHE_LED_ORANGE, OUTPUT);
  pinMode(BROCHE_D3, OUTPUT) ;
}

void loop()
{
  digitalWrite(BROCHE_D3, HIGH);
  digitalWrite(BROCHE_LED_ORANGE, HIGH);
  delay (1000);
  digitalWrite(BROCHE_LED_ROUGE, HIGH);
  delay(1000);
  digitalWrite (BROCHE_LED_BLEU, HIGH);
  delay (1000);
  digitalWrite(BROCHE_LED_JAUNE, HIGH);
  delay (1000) ;
  digitalWrite(BROCHE_LED_ROUGE, LOW);
  digitalWrite(BROCHE_LED_BLEU, LOW);
  digitalWrite(BROCHE_LED_JAUNE, LOW);
  digitalWrite(BROCHE_LED_ORANGE, LOW);
  delay(1000);
}

fonctionnement effectif du bouclier (LED allumées)
fonctionnement effectif du bouclier (LED allumées)




Puce mémoire

Apres avoir tester 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, ce fut sans succès. Nous avons donc vérifié l'horloge de notre Arduino Uno sur un oscilloscope en y injectant ce code (GIT : pico_yahiani_zongo/Software/test_clk_arduino):

Code arduino Visualisation

#include <SPI.h>

// set pin 10 as the slave select for the digital pot:
const int slaveSelectPin = 10;

void setup()
{
  // set the slaveSelectPin as an output:
  Serial.begin (9600);
  pinMode(slaveSelectPin, OUTPUT),
  // initialize SPI:
  SPI.begin();
}

void loop()
{
  
  digitalWrite(slaveSelectPin, LOW);
  SPI.transfer(0xaa);
  Serial.write("ok");
  delay(100);
}

Signal d'horloge

Le signal d'horloge de notre Arduino est correct.


Le problème ne venant pas de l'Arduino, nous avons donc dessoudé et ressoudé notre puce mémoire et le problème à été résolu:

Carte SD reconnue

Connecteurs HE10

À l'aide d'un afficheur 7-segments nous vérifions le bon fonctionnement des connecteurs HE10 en affichant "GrP6" pour Groupe6. 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 présent sur le site de Mr Redon (https://rex.plil.fr/Enseignement/Systeme/Systeme.PSE/systeme.html). Ci-dessous un aperçu du code source utilisé pour gerer la communication SPI ainsi que celui utilisé pour gérer l'affichage du sept segments.

Configuration communication SPI

#define SPI_DDR         DDRB
#define SS_DDR          DDRC
#define SPI_PORT        PORTC

#define SPI_SS_M        2 // master ?
#define SPI_MOSI        3
#define SPI_MISO        4
#define SPI_SCK         5

void spi_activer(void){                              // Activer le périphérique
SPI_PORT &= ~(1<<0);                            // Ligne SS à l'état bas
}

void spi_desactiver(void){                           // Désactiver le périphérique
SPI_PORT |= (1<<0);                             // Ligne SS à l'état haut
}

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

void spi_init(void){                                 // Initialisation du bus SPI
SPI_DDR |= (1<<SPI_MOSI)|(1<<SPI_SCK)|(1<<SPI_SS_M);   // Définition des sorties
SPI_DDR &= ~(1<<SPI_MISO);                           // Définition de l'entrée
                             // Désactivation du périphérique
SS_DDR |= (1<<0); // afficheur sur pc0
SPCR = (1<<SPE)|(1<<MSTR)|(1<<SPR1);                 // Activation SPI (SPE) en état maître (MSTR)
SPI_PORT |= (1<<0);                                                   // horloge F_CPU/64 (SPR1=1,SPR0=0)
}

Code de gestion du sept segments Image de fonctionnement
#include <avr/io.h>
#include <avr/interrupt.h>  
#include <util/delay.h>
#include "SPI.h"

int main(void){
spi_init();
spi_activer();
    spi_echange(0x76); // adresse de l'afficheur du 7 segment
    spi_desactiver();

while(1){
         spi_activer(); // active 
         spi_echange('G');
         _delay_ms(5);
         spi_echange('R');
         _delay_ms(5);
         spi_echange('p');
         _delay_ms(5);
         spi_echange('6');

         spi_desactiver(); //désactive
         _delay_ms(5);            
       }
return 0;
}

connecteur HE10 fonctionnel

Ordonnanceur

Clignotement de deux LEDS

La principale tâche à accomplir en premier lieu est de faire cligonter deux LED à des fréquences différentes. Pour cela nous passerons par plusieurs étapes. La première tâche à realiser 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 (Mr REDON) et avons le minuteur 1 programmé comme vous pouvez le voir dans notre git : pico_yahiani_zongo/Software/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(16000000hz)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 16us. 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.

Code C Vidéo de fonctionnement

#include <avr/io.h>
#include <avr/interrupt.h>  

#define PRESCALER 256
#define NB_TICK  1250
#define CTC1  WGM12  // Better name


void init_timer(void){
TCCR1A = 0;  
TCCR1B = 1<<CTC1; 
#if (PRESCALER==8)
 TCCR1B |= (1<<CS11);
#elif (PRESCALER==64)
 TCCR1B |= (1<<CS11 | 11<<CS10);
#elif (PRESCALER==256)
 TCCR1B |= (1<<CS12);
#elif (PRESCALER==1024)
 TCCR1B |= (1<<CS12 | 1<<CS10);
#endif
 
OCR1A = NB_TICK;
TCNT1 = 0;
TIMSK1 = (1<<OCIE1A);
}

ISR(TIMER1_COMPA_vect, ISR_NAKED){    // Procédure d'interruption
  PORTB ^= (1<<PB5);
}

int main(void)
{
  DDRB |= (1<<PB5);
  init_timer();
  sei();
  while(1);
}

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/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

void init_taches()
{
    for(uint8_t cpt=1; cpt<TACHE_MAX; cpt++)
    {
        uint16_t sauv = SP;
        SP = taches[cpt].pile;
        uint16_t addresse=(uint16_t) taches[cpt].tache;
        asm volatile("push %0" : : "r" (addresse & 0x00ff));
        asm volatile("push %0" : : "r" ((addresse & 0xff00)>>8));
        SAVE_REGISTERS();
        taches[cpt].pile = SP;
        SP = sauv;
    }
 }

ISR(TIMER1_COMPA_vect,ISR_NAKED)
{
    // Sauvegarde du contexte de la tâche interrompue
	SAVE_REGISTERS();
  	taches[tache_courante].pile = SP; 
	tache_courante ++;
    if (tache_courante >= TACHE_MAX)
    {
      tache_courante = 0;
    }
	// Récupération du contexte de la tâche ré-activée 
	SP = taches[tache_courante].pile;
  	RESTORE_REGISTERS();
	
    asm volatile ( "reti" );
    TCNT1=0;
}

Les 2 LEDS clignotent à des fréquences differentes




Gestion de l'état endormi

Notre ordonnanceur est désormais capable de lancer plusiuers 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 nos effectuons maintenant les pauses avec une fonction que nous avons écrite (_delay). Pour l'utilisation de notre fonction nous avons redefini 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écremente le champ temps d'une tâche puis la reveille une fois que le temps écoulé. Ci-dessous vous pouvez voir à quoi ressemblent ces fonctions.

Code fonction ordonnanceur Code fonction _delay

void ordonnanceur()
{
  int cpt_ord = 0;
  uint8_t tache_suivante = tache_courante;
  for(uint8_t cpt=1; cpt<TACHE_MAX; cpt++)
   {
    if (taches[cpt].etat == SLEEPY)
    {
            taches[cpt].temps--;

            if(taches[cpt].temps <= 0)
            {

                taches[cpt].etat = WAKE;
            }

            ++cpt_ord;
    }
   }
        if (cpt_ord == (TACHE_MAX - 1))
        {
          tache_suivante = 0;
        }
        else
        {
          do
          {
              tache_suivante = (tache_suivante + 1) % TACHE_MAX;
          }  while (taches[tache_suivante].etat != WAKE);
        }
  tache_courante = tache_suivante;
}

void _delay(uint16_t temps){
cli();
taches[tache_courante].temps = temps;//
taches[tache_courante].etat = SLEEPY;
sei();
TIMER1_COMPA_vect();
}

La nouvelle structure de tâche se presente de la façon suivante :

Code de la structure finale representant les tâches
typedef struct {
  void (*tache)(void);
  uint16_t pile;
  uint16_t temps;
  uint16_t etat;
}tache

Ci-dessous, une vidéo de fonctionnement de la gestion de l'état endormi des processus. Vous pouvez voir au debut 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éfinimment. 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

tache taches[TACHE_MAX] =
{{_wait,0x0800,10,WAKE},
{gestion_LED4,0x0400,1000,SLEEPY},
{gestion_LED2,0x0600,10,WAKE},
};



Gestion communication Port serie et SPI

Avec afficheur sept-segments

Notre ordonnanceur est maintenant capable de gérer le cligottement 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 serie. 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 sont dans les fichiers SPI.c (pour la gestion de la communication SPI) et serial.c (pour le Port serie).

Gestion SPI Gestion Port Série

#include <avr/io.h>
#include <avr/interrupt.h>
#include "SPI.h"

#define SPI_DDR         DDRB
#define SS_DDR          DDRC
#define SPI_PORT        PORTC


#define SPI_SS_M        2 // master ?
#define SPI_MOSI        3
#define SPI_MISO        4
#define SPI_SCK         5




void spi_activer(void){                              // Activer le périphérique
SPI_PORT &= ~(1<<0);                            // Ligne SS à l'état bas
}

void spi_desactiver(void){                           // Désactiver le périphérique
SPI_PORT |= (1<<0);                             // Ligne SS à l'état haut
}

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

void spi_init(void){                                 // Initialisation du bus SPI
SPI_DDR |= (1<<SPI_MOSI)|(1<<SPI_SCK)|(1<<SPI_SS_M);   // Définition des sorties
SPI_DDR &= ~(1<<SPI_MISO);                           // Définition de l'entrée
                             // Désactivation du périphérique
SS_DDR |= (1<<0); // afficheur sur pc0
SPCR = (1<<SPE)|(1<<MSTR)|(1<<SPR1);                 // Activation SPI (SPE) en état maître (MSTR)
SPI_PORT |= (1<<0);                                                   // horloge F_CPU/64 (SPR1=1,SPR0=0)
}

#include <avr/io.h>

#include <avr/interrupt.h>
#include <stdio.h>

char valeur;

void serie_init(long int vitesse){
UBRR0=F_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
}

void serie_envoyer(unsigned char c){
loop_until_bit_is_set(UCSR0A,UDRE0);
UDR0=c;// charge l'octet à envoyer dans le registre de transmission
}

unsigned char serie_recevoir(void){
loop_until_bit_is_set(UCSR0A, RXC0);
return UDR0; //retourne le caractère reçu 
}

void set_valeur(void){
    while(1){
        valeur = serie_recevoir();
         serie_envoyer(valeur);
    }
}

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 avec la même tâche. La valeur mise dans serie_recevoir est la même que nous utilisons 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 optenons ci-dessous.

Liste des tâches Vidéo de fonctionnement

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}
};

Avec Matrice de LEDS

De la même manière qu'avec le sept-segments, nous utilisons notre ordonnnceur pour commander la matrice de LEDS par l'intermediaire des modes de communication SPI et port serie. À la difference du sept-segment; la matrice de LEDS nécessite une configuration des differents 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 (contenu dans le fichier matrice.h). Ci-dessous vous pouvez voir les résultats que nous optenons ainsi que les codes que nous avons utilisés.

Matrice de configurtion des LEDS

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
};

Code de gestion de la matrice de LEDS Vidéo de fonctionnement
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); //command char to reset frame index uniquement sur les nouvelles versions de matrices
    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);//send what is in buffer to the matrices
            }
        }
        _delay(10);
        spi_desactiver();
        _delay(25);

    }
    sei();
}

Matrice de LEDS programmée





Carte file é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 nous avons dû en rajouter une pour gérer les interruptions.La carte mère écrit dans la RAM et le uC 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 differents 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

Schématique de notre carte fille écran
Routage de notre carte fille écran
Visualisation 3D de notre carte PCB (face avant)
Visualisation 3D de notre carte PCB (face arrière)

réception de la carte PCB:

Carte.pdf.pdf

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 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 remarqué l'un de nos encadrants, M. Redon. Nous avons donc dans la nouvelle version, remplacé cette empreinte par sa version transversante.

  • 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 relier 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'ecran après les modification 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 remarqué l'un de nos encadrants M. BOE.

Seconde Version

schematique carte fille 2nde version
routage carte fille 2nde version
visualisation 3D (face avant)
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.

Carte fille avant soudure Carte fille après soudure
Carte non soudée.jpg
Carte .jpg

Programmation de la carte fille

Avant d'entamer la gestion des différentes fonctionalités de l'écran LCD nous verifions si les composants ont été bien soudée et que la carte est fonctionnelle et programmable en faisant clignoter une de ses Leds avec du code arduino présent dans notre repertoire Git. Vous pouvez voir les resultats de ce test ci-dessous:

Code arduino Vidéo fonctionnement
#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);
}

Led d'alimantation allumée et Led programmable clignotante

Maintenant que notre carte fille est fonctionnelle nous commençons la programmation de notre Ecran LCD. Encore une fois nous passons par une étape de test où nous vérifions le bon fonctionnement de l'écran LCD. Avec du 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

Hello ecr.jpg

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
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
    }
}

Dans cet exemple nous utilisons les fonctions display_GD et defiler_GD que nous plaçons dans une boucle infini ce qui nous permet de gérer un defilement continu. vous pouvez voir le contenu de ces fonctions ci-dessous.

display_GD defiler_GD
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);
    }
}

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;
}