SE4Binome2025-5

De projets-se.plil.fr
Aller à la navigation Aller à la recherche

Objectif

L'objectif de notre groupe est de réaliser une carte fille compatible avec la carte mère réalisée par le binôme 2. Cette carte que nous réalisons doit pouvoir permettre au pico-ordinateur d'afficher des informations sur un écran. Pour complexifier la tâche, nous utiliserons un affichage LCD graphique, qui nous permettra d'effectuer un affichage simple initialement, mais aussi de créer des primitives graphiques dans un second temps.

En plus de cette carte, nous devons réaliser un "OS primitif" qui fonctionnera sur une carte Arduino, à la fois pour pouvoir comprendre le fonctionnement d'un ordonnanceur, mais aussi pour pouvoir tester notre carte de notre côté avant de l'intégrer au pico-ordinateur. L'Arduino sera complété d'un shield afin d'avoir une interface similaire à celle qui sera disponible sur la carte mère, que nous devons réaliser également. Aussi, ce shield sera accompagné d'un module de carte SD pour pouvoir charger des programmes "à la volée" depuis la carte SD.

Dépôt GIT

le dépôt git de ce projet est le suivant: https://gitea.plil.fr/avanghel/SE4-Pico-B5

Partie Hardware

Bouclier Arduino & carte SD

Le bouclier Arduino que nous réalisons servira d'interface entre l'Arduino et la carte fille, de façon à ce qu'un arduino avec le programme de la carte mère et le shield d'installé ait le même comportement que la carte mère. Le module SD pourra se brancher sur un connecteur de la carte mère et donc du bouclier aussi.

Composants utilisés

Composants du bouclier arduino

La liste des composants utilisés pour le bouclier est la suivante:

-5x LEDS rouges

-5x Résistances 1kohm

Connecteurs utilisés (tous en 2.54mm):

-Connecteur arduino R3(disponible dans la librairie d'empreintes KiCad)

-6x 1x8 mâle vertical

Composants du module SD

La liste des circuits intégrés utilisés pour le module SD est la suivante:

-1x LTC3531

-1x74LVC125

les composants hors circuits intégrés utilisés sont les suivants:

-1x Capacité 2.2µF

-1x Capacité 10µF

-1x Inductance 10µH

-3x Résistances 3.3kohms

Les connecteurs utilisés sont les suivants:

-1x 1x8 mâle horizontal

-1x Connecteur Molex microSD

Schématique des deux cartes

Ci dessous la schématique pour les deux cartes:

Schématique du bouclier arduino et du module SD
Schématique du bouclier arduino et du module SD

PCB du bouclier

Ci dessous le PCB du bouclier arduino:

PCB de la carte bouclier arduino.
PCB de la carte bouclier arduino.

PCB du module SD

Ci dessous le PCB du module SD. Il est à noter que le connecteur SD est à l'envers, et que l'insertion d'une carte SD est difficile de ce fait. Il est recommandé de changer le routage du module SD si le projet est réutilisé.

PCB du module SD
PCB du module SD

Vue 3D des deux cartes

Ci-dessous la vue 3D de la carte du bouclier et du module SD:

Vue 3D du bouclier arduino et du module SD
Vue 3D du bouclier arduino et du module SD

Carte Fille

Le pilotage d'un écran graphique est plus complexe que pour un écran à caractère, puisque la création d'une image se fait pixel par pixel (plutôt que par caractère pour l'écran à caractère). De plus, le microcontrôleur de la carte mère(que l'on appelera CPU ici) doit gérer d'autres cartes filles, il n'est donc pas souhaitable qu'il traite par lui-même une tâche aussi lourde.

Notre carte fille devra donc être composée elle aussi d'un microcontrôleur(que l'on appelera GPU) qui interprètera des données reçues par le CPU (par exemple, un caractère à une certaine position) et transmettra les données à afficher à l'écran LCD graphique. Par ailleurs, nous fixerons l'écran graphique sur la carte fille.

Composants utilisés

La liste des circuits intégrés est la suivante:

-Atmega328P(Microcontrôleur)

-FM256WG (mémoire SPI)

-TMUX1574 (Multiplexeur bidirectionnel 4 bits) (à commander)

La liste des composants hors CI est la suivante:

-1 oscillateur à quartz 8MHz

-2 Capacités de 22pF

-5 Capacités de 100nF

-1 Capacité de 10µF

-1 Résistance de 1 Mohm

-2 Résistances de 1 Kohm

-2 LEDs rouges

Les connecteurs sont tous des connecteurs traversants 2.54 mm, les voici:

- 2x3 mâle vertical (ISP)

- 1x2 mâle vertical (debug, connection série)

- 1x8 mâle horizontal (Interface mère-fille)

- 1x9 mâle vertical (Interface avec écran)

Schématique de la carte fille

Schématique de la carte fille
Schématique de la carte fille

PCB de la carte

Ci-dessous le PCB de la carte imprimée:

PCB de la carte fille
PCB de la carte fille

et ci dessous un zoom sur la partie logique:

zoom sur la partie logique de la carte fille
zoom sur la partie logique de la carte fille

Vue 3D de la carte

Ci-dessous une vue 3D de la carte fille:

Vue 3D du pcb de la carte fille
Vue 3D du pcb de la carte fille

A noter que le connecteur en bas de la carte(connecteur mère-fille) sera bien à l'horizontale.

Composants à commander

TMUX1574DYYR (SOT-23-16 THIN)

Software

Ordonnanceur

Initialisation de la pile

Interruption nue

Structure d'un processus

Code de l'ordonnanceur

Fonction wait

Exemple de tâches

Exemple d'utilisation

Carte SD

Ecran LCD

Pour notre carte fille, nous utiliserons l'écran Fermion: 3.5” 480x320 TFT LCD Capacitive. Voyons le code pour communiquer avec l'écran.

Initialisation

Tout d'abord, la carte utilisant le driver ILI9488 pour communiquer avec l'écran, nous devons initialiser la communication.

Il faut savoir que ce driver reçoit soit des commandes, soit des données, et donc nous allons souvent utiliser ces 2 fonctions :

void tft_command(uint8_t cmd) {
    active_o(TFT_DC, LOW, 'd');   // DC low = command
    enable_spi();
    spi_echange(cmd);
    disable_spi();
}

void tft_data(uint8_t data) {
    active_o(TFT_DC, HIGH, 'd');   // DC high = data
    enable_spi();
    spi_echange(data);
    disable_spi();
}

Maintenant que nous avons ces fonctions, nous pouvons initialiser notre écran.

void tft_init() {
    // RESET
    tft_reset();

    // software reset
    tft_command(0x01);
    _delay_ms(100);

    // sleep out
    tft_command(0x11);
    _delay_ms(100);

    // memory acces control
    tft_command(0x36);
    tft_data(0xE8);
    _delay_ms(100);

    // set dbi
    tft_command(0x3A);
    tft_data(0x06);
    _delay_ms(100);

    // partial mode on
    tft_command(0x12);
    _delay_ms(100);

    // display on
    tft_command(0x29);
    _delay_ms(100);

    // set cursor
    tft_command(0x2A);
    // set start x
    tft_data(0x00);
    tft_data(0x00);
    // set end x
    tft_data(0x01);
    tft_data(0xDF);
    tft_command(0x00);
    tft_command(0x2B);
    // set start y
    tft_data(0x00);
    tft_data(0x00);
    // set end y
    tft_data(0x01);
    tft_data(0x3F);
    tft_command(0x00);
    _delay_ms(100);

    // set brightness
    tft_command(0x51);
    tft_data(0x0F);
    _delay_ms(100);

    // set brightness control
    tft_command(0x53);
    tft_data(0x2C);
    _delay_ms(100);

    // set framerate
    tft_command(0xB1);
    tft_data(0xB0);
    tft_data(0x11);
    _delay_ms(50);

    active_o(CS, HIGH, 'b');
    _delay_ms(100);
}

Par ailleurs, il faut initialiser notre SPI pour l'activer en mode maître et choisir la vitesse voulue, dans notre cas 8MHz.

void spi_init(void){
    SPCR = (1 << SPE) | (1 << MSTR);    //Active le SPI en mode Maitre
    SPSR = (1 << SPI2X);                // SPI2X = 1 → diviseur final = 2
}

Afficher un rectangle de couleur

Pour afficher un rectangle de couleur, il faut premièrement saisir les coordonnées concernées.

void tft_set_window(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1) {
    //tft_command(0x2C); // Memory Write
    tft_command(0x2A); // Column Address Set
    tft_data(x0 >> 8);
    tft_data(x0 & 0xFF);
    tft_data(x1 >> 8);
    tft_data(x1 & 0xFF);

    tft_command(0x00);
    tft_command(0x2B); // Page Address Set
    tft_data(y0 >> 8);
    tft_data(y0 & 0xFF);
    tft_data(y1 >> 8);
    tft_data(y1 & 0xFF);
    tft_command(0x00);
    tft_command(0x2C); // Memory Write
}

Ensuite, il faut parcourir chaque coordonnée pour venir lui associer une couleur.

void tft_fill_rect(uint16_t color, uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1) {
    tft_set_window(x0, y0, x1, y1);

    uint8_t r = ((color >> 11) & 0x1F) << 3;
    uint8_t g = ((color >> 5) & 0x3F) << 2;
    uint8_t b = (color & 0x1F) << 3;

    active_o(TFT_DC, HIGH, 'd');
    enable_spi();
    for (uint32_t i = 0; i < (uint32_t)(x1 - x0 + 1) * (uint32_t)(y1 - y0 + 1); i++) {
        spi_echange(r);
        spi_echange(g);
        spi_echange(b);
    }
    disable_spi();
}

Nous avons désormais un rectangle qui peut s'afficher de toutes les couleurs et de toutes les dimensions !

Afficher des caractères

L'affichage de caractères est un enjeu crucial dans notre projet de PicoOrdinateur car il permet de voir les commandes saisies par un utilisateur. Cependant, on ne peut pas écrire de caractère si facilement que ça, c'est pour ça que nous allons devoir utiliser une Bitmap qui nous fournit comment écrire une lettre en 5x7 pixels. Voici le code utilisé.

struct Font {
    unsigned char letter;
    unsigned char code[7][6];  // 5 caractères + '\0'
};

static const struct Font font[] = {
    { ' ', {
        "     ",
        "     ",
        "     ",
        "     ",
        "     ",
        "     ",
        "     " }},
    { 'A', {
        " ### ",
        "#   #",
        "#   #",
        "#   #",
        "#####",
        "#   #",
        "#   #" }},
    { 'B', {
        "#### ",
        "#   #",
        "#   #",
        "#### ",
        "#   #",
        "#   #",
        "#### " }},
......

Le code ci-dessus est pratique car visuel mais peu optimisé d'un point de vue mémoire car chaque information est codé par un unsigned char alors même qu'il contient seulement une information binaire. Ainsi, nous pourrions diviser par 128 la place mémoire occupée par ce code en revoyant sa structure mais pour nos essais, c'est acceptable de laisser cela ainsi. Pour passer de cette bitmap à l'affichage, voilà la fonction que nous utilisons :

int print_char_on_screen(uint16_t color, unsigned char c, uint16_t x, uint16_t y, uint16_t scale){
    int char_idx = find_idx(c);
    if (char_idx == -1 ){
        return 1;
    }
    for (uint16_t py = 0; py < 7 * scale; py++) {
        for (uint16_t px = 0; px < 5 * scale; px++) {
            if (font[char_idx].code[py/scale][px/scale] == '#') {
                tft_draw_pixel(x + px, y + py, color);
            }else{
                tft_draw_pixel(x + px, y + py, BACKGROUND);
            }
        }
    }
    return 0;
}