Comment stocker des données sur une carte SD en SPI ?

NIVEAU 3

Objectifs

  • Ajouter de l’espace mémoire à l’aide d’une carte SD en SPI
  • Créer et lire des fichiers ASCII sur une carte SD (système de fichier FAT)

Pré-requis

Carte SD

Une carte SD (en anglais Secure Digital) est un format de carte mémoire utilisée comme stockage de masse pour divers appareils portables. Elle permet de gérer un système de fichier standard (type FAT16 ou FAT32 dans la cas des microcontroleurs) et ainsi pouvoir créer et lire des fichiers ASCII (type fichiers texte).

L’accès aux informations se fait selon deux niveaux :

  • la couche physique : basée sur une liaison SPI (voir le tutoriel Configurer un réseau point à point SPI )
  • la couche applicative : basée sur un gestionnaire de type FAT (pour file allocation table, ou table d’allocation de fichiers)

Le protocole FAT32 utilise un adressage sur 28 bits pour réaliser la table d’allocation de données sur 32 bits.

Connexion SPI

La première étape est donc de relier le support de carte SD par l’intermédiaire d’une liaison SPI. Pour rappel, le protocole de communication SPI (Serial Peripheral Interface) permet d’établir une liaison de transmission de données série synchrone entre un maître et plusieurs esclaves. La liaison se fait à l’aide de 4 fils :

  • SCK : signal d’horloge, imposé par le maître
  • MOSI / SDO : données sortant du maître
  • MISO / SDI : données reçues par le maître
  • SS / CS : signal de sélection de l’esclave – cette ligne est spécifique à chaque esclave

L’alimentation des cartes SD se fait en 3.3V. Cependant, certains supports de carte SD intègrent un régulateur de tension et doivent donc être alimentés en 5V. Pensez à regarder la documentation technique du connecteur.

Par la suite, nous utiliserons la bibliothèque SDBlockDevice de MBED, qui permet d’utiliser plus facilement les cartes SD en s’affranchissant des signaux de commandes à transmettre pour accéder aux espaces mémoires. Mais pour pouvoir l’utiliser avec MBED5, il est nécessaire de modifier la configuration du compilateur.

Configuration de la compilation

Avant de pouvoir utiliser les bibliothèques liées aux cartes SD, il est important de créer (ou modifier) le fichier mbed_app.json à la racine du projet. Il faut utiliser les lignes suivantes :

{
    "requires": [ "bare-metal", "rtos-api", "sd","filesystem","fat_chan"],
    "target_overrides": {
        "*": { "target.components_add": [ "SD" ] }
    }
}

L’option bare-metal permet de s’affranchir de la partie temps réel de MBED 5. Les autres options permettent d’utiliser les bibliothèques liées aux cartes SD et aux gestionnaires de systèmes de fichiers (sd, filesystem et fat_chan).

Bibliothèques SDBlockDevice et FATFileSystem

Pour pouvoir utiliser l’espace mémoire de la carte SD comme un lieu de stockage de données (dans des fichiers texte), il faut utiliser deux librairies différentes : SDBlockDevice et FATFileSystem.

Deux autres bibliothèques seront utiles pour la gestion des fichiers et des erreurs : stdio.h et errno.h.

SDBlockDevice

La première permet de mettre en oeuvre le protocole de bas niveau de discussion (et donc d’accès) avec la carte SD, via le protocole SPI. Cette bibliothèque se nomme SDBlockDevice.

Cette bibliothèque sera surtout utilisée par la suivante afin de gérer les commandes à envoyer à la carte SD pour qu’elles renvoient des données (ou en écrivent) depuis (ou vers) les bonnes cases mémoires.

Pour initialiser un bloc de carte SD (après l’avoir bien relié selon la norme SPI), il faut ajouter la ligne suivante :

SDBlockDevice   blockDevice(D11, D12, D13, D7);  // mosi, miso, sck, cs

blockDevice sera alors le nom de l’objet à spécifier pour accéder à la carte SD. Les paramètres suivants correspondent aux broches de la liaison SPI utilisée par la carte SD, dans l’ordre suivant : MOSI, MISO, SCK et CS.

FATFileSystem

Une seconde bibliothèque (probablement la plus utile pour la gestion des ressources sur la carte SD) est la bibliothèque FATFileSystem.

Elle permet de créer une table d’allocation de fichiers sur l’espace mémoire de la carte SD. Pour cela, il faut également l’initialiser par la ligne suivante :

FATFileSystem   fileSystem("fs");

fileSystem sera alors le nom de l’objet permettant la gestion de fichier. Le paramètre est alors le nom du système de fichiers. Il correspond au nom du répertoire de base, appelé encore racine du système de fichiers.

Cette bibliothèque fournit un ensemble de fonctions permettant d’accéder aux données du système de fichiers mais également de créer les liens physiques entre les espaces mémoires et les fichiers (transfert des données sur la carte SD). Parmi les fonctions, on pourra citer les suivantes :

  • fileSystem.mount(&blockDevice) : permettant de monter un système de fichier sur un espace mémoire physique (créer la table d’allocation) ;
  • fileSystem.reformat(&blockDevice) : permettant de formater l’espace mémoire (effacer l’ensemble des données et reconstruire la table d’allocation) ;
  • fileSystem.unmount() : permettant de démonter le système de fichier (indispensable pour ne pas dégrader les données sur l’espace mémoire).

Programme complet de test

L’exemple suivant est basé sur celui fourni par MBED (CookBook).

Dans cet exemple, le système de fichier est monté (mount). Puis on accède à un fichier texte à la racine du système de fichier. Ce fichier est nommé numbers.txt et contient 10 nombres réels, un par ligne. Le fichier est ouvert en lecture (r+) à l’aide de la fonction fopen. Puis chacun des nombres est récupéré dans un tableau à l’aide de la fonction fscanf.

Ensuite, le fichier est refermé (fclose).

Dans une seconde partie, l’inventaire du répertoire racine est réalisé (opendir puis readdir).

Enfin, le système de fichiers est démonté (unmount).

#include "mbed.h"
#include <stdio.h>
#include <errno.h>

#include "SDBlockDevice.h"
#include "FATFileSystem.h"

SDBlockDevice   blockDevice(D11, D12, D13, D7);  // mosi, miso, sck, cs
FATFileSystem   fileSystem("fs");

Serial pc(USBTX, USBRX);
float tab[10];

int main()
{
    // Try to mount the filesystem
    int err = fileSystem.mount(&blockDevice);
    pc.printf("%s\n", (err ? "Fail :(" : "OK"));
    if (!err) {
        // Open the numbers file in /fs/numbers.txt
        // This file includes 10 float numbers - 1 per line
        FILE*   f = fopen("/fs/numbers.txt", "r+");
        pc.printf("%s\n", (!f ? "Fail :(" : "OK"));
        if (f) {
            // Go through and increment the numbers
            for (int i = 0; i < 10; i++) {
                fscanf(f, "%f", &tab[i]);
                pc.printf("%f \r\n", tab[i]);
            }
        }
    }

    // Close the file 
    err = fclose(f);
    printf("%s\n", (err < 0 ? "Fail :(" : "OK"));

    // Display the root directory
    DIR*    d = opendir("/fs/");
    printf("%s\n", (!d ? "Fail :(" : "OK"));
    if (d) {
        while (true) {
            struct dirent*  e = readdir(d);
            if (!e) {
                break;
            }
            printf("    %s\n", e->d_name);
        }
        err = closedir(d);
        printf("%s\n", (err < 0 ? "Fail :(" : "OK"));
    }

    // Unmounting... 
    err = fileSystem.unmount();
    printf("%s\n", (err < 0 ? "Fail :(" : "OK"));
}
Nucléo – Stocker des données sur une carte SD en SPI