Savoir comment lire les informations d’une carte mémoire
Savoir comment lire un fichier dans la carte SD
Savoir comment créer un nouveau fichier
Savoir comment mettre à jour un fichier de données
Savoir comment formater les données dans un fichier
Savoir dater, et stocker les données dans la carte mémoire
Savoir utiliser une carte mémoire avec Arduino
Savoir développer un Data logger avec Arduino
Etc.
Fonctionnement du projet
L’objectif projet et l’acquisition et stockage des données dans une carte mémoire. On fait la lecture du pin analogique A0 toute les secondes. On utilise un buffer (tableau) pour le stockage local dans la carte Arduino. Après le remplissage du tableau, on transfert ce dernier à la carte mémoire SD. On peut varier la taille du tableau via le paramètre N. Chaque ligne du fichier contient la donnée en format double séparé par des virgules ainsi la date et l’heure d’acquisition de ce dernier à la fin de la ligne. Ci-dessous le format de stockage des données dans les lignes du fichier:
DATA1,…, DATAN, Année, …, Secondes,
DATA1,…, DATAN, Année, …, Secondes,
DATA1,…, DATAN, Année, …, Secondes,
DATA1,…, DATAN, Année, …, Secondes,
….
Capteur et module RTC
Avant d’abordé le projet, je vous recommande de consulter la partie 1 et 2 du projet concernant la partie capteur et le module RTC pour la sauvegarde du temps.
Le lecteur de la carte MicroSD utilise l’interface SPI pour interfacer avec la carte Arduino via les pins SCK (Horloge), MOSI (Master Out Slave In) pour l’envoie des données vers de la carte Arduino vers le module et MISO (Master In Slave Out) pour la récupération des données de la carte. L’interface SPI est une interface synchrone, il utilise un signal d’horloge indépendant. Le pin CS est utilisé pour l’activation du module de la carte SD. Il est activé niveau : Il faut positionner le pin à 5V afin d’activer le lecteur. Le pin CS est très important lorsqu’on a besoin de contrôler plusieurs lecteurs avec une seule interface. Par exemple, si on dispose de 3 cartes SD de 34 Go, alors on peut sélectionner une carte à la fois grâce aux pins CS1, CS2 et CS1 !
Le lecteur est alimente par une alimentation de 5V s’il dispose d’un régulateur interne comme le modèle illustré ci-dessous. Dans le cas échéant, la tension d’alimentation est souvent 3.3V ! Il faut bien s’assurer des caractéristiques de son lecteur avant d’effectuer le câblage au risque de détériorer le lecteur ou le sous alimentation de ce dernier. Un lecteur de 5V alimenté par 3.3V risque de ne pas fonctionner!
La module ci-dessous est testé et fonctionnel avec une carte Micro SD de 4 Go et 64 Go (voir la vidéo pour plus des détails). Avant d’incruster la puce dans le lecteur, assurez-vous bien qu’elle est bien formater avec le système FAT16 ou FAT32. Il est recommandé aussi d’avoir une carte dédiée pour son stockage des données. Ci-dessous un lien vers un mini logiciel pour le formatage en format FAT32. Vous pouvez trouver d’autres logiciels dans le web en cas du besoin.
FAT16 (File Allocation Table), soit « table d’allocation de fichiers, ce de divise l’espace disponible sur un disque dur ou mémoire SD en 65536 blocs (ou clusters) portant chacun un numéro propre. Si le nombre de blocs reste constant, leur taille varie en fonction de la taille du périphérique de stockage. À partir de Windows 95 OSR2, l’utilisateur a le choix entre FAT16 et FAT32. Ce système de fichiers est également utilisé sur les cartes SD. FAT16 permet d’avoir des partitions, selon la taille des clusters, occupant jusqu’à 2 Go, et exceptionnellement 4 Go. Concrètement, les spécifications de FAT16 sont les suivantes :
Taille maximale d’un fichier : 4 Go – 1 octet (soit exactement 4 294 967 295 octets) ;
Nombre maximal de fichiers par partition : 65 524 ;
La racine du disque est de plus limitée à 512 entrées ; une entrée est un fichier ou un dossier, lire la suite…
Taille maximale de la mémoire 2 To !
A retenir, au moment de stockage des donnés dans la carte SD, la taille du fichier ne doit pas dépasse au maximum 4Go. Il faut mettre en place un système de création des fichiers dans la carte SD avec une taille modérée (voir la vidéo et la suite de l’article). En cas de dépassement de la capacité, il peut la remise à zéro du pointeur des lignes du fichier (écraser les données anciennes).
Le lecteur de carte SD utilise l’interface SPI matériel. Il faut utiliser les mêmes pins en fonction de votre carte Arduino. Le câblage est différent pour la carte Arduino Méga. Voir le tableau et les figures ci-dessous pour plus des détails.
La fonction setup()
void setup() { // Init INT4 InitInt4(PinINT4);
// Initialisation du port série: Affichage locale des données Serial.begin(9600);
// Initialisation du Module RTC (Heure de l'ordinateur) InitRTC();
// Initialisation de la carte SD InitSD(CS);
// Stockage des données dans le fichier (Fichier par défaut) WriteSD(Data, DateHeure, N, NomFichier);
// Réouverture et Affichage du contenu du fichier (Fichier par défaut) ReadDisFile(NomFichier);
}
La fonction loop():
Lecture de l’horloge RTC
void loop() { //Lecture de l'horloge GetDateHeure(DateHeure); }
La fonction ISR():
Acquisition et stockage des données
ISR(INT4_vect) //ISR(_VECTOR(5)) { // Compteur des échantillons static long count=0;
// Désactivation de l'interruption globale cli();
// Lecture du capteur de luminosité R_l= GetData(A0);
// Mise à jour du buffer des échantillons Data[count]=R_l;
// Mise à jour du compteur count++; count=count%N;
// Mise à jour du nom du fichier NomFichier= UpdateNomFile(TFile, DateHeure, NomFichier);
// Stockage des données dans la carte Mémoire if(!count) //count=N: stockage lorsque le buffer est plein { // Stockage des données dans la carte SD WriteSD(Data, DateHeure, N, NomFichier);
// Réouverture et Affichage du contenu du fichier (Pour la vérification) Serial.println(NomFichier); ReadDisFile(NomFichier); }
// Réactivation de l'interruption globale sei(); }
La fonction ReadDisFile()
Lecture et affichage du contenu d’un fichier dans la carte SD
if (myFile) { // 2. Lecture & Affichage du contenu du fichier while (myFile.available()) { Serial.write(myFile.read()); }
// 3. Fermeture du fichier myFile.close(); } else { Serial.println("Erreur d'ouverture du fichier "+ NomFich); } Serial.println("\n\n"); }
La fonction UpdateNomFile()
Génération périodique d’un nom du fichier en fonction de la date, l’heure et la période de création d’un nouveau fichier
String UpdateNomFile(byte TempsFile, word *Horloge, String OldName) { String NewNom=""; unsigned long T; T=Horloge[0]+60*Horloge[1]+60*60*Horloge[2];
if(!(T%TempsFile)) // Changement du nom du fichier toutes les TempsFile { for(int i=3;i>=0;i--) { NewNom=NewNom+String(Horloge[i]); } NewNom="F"+NewNom+".txt"; } else NewNom=OldName;
return NewNom; }
La fonction WriteSD()
Stockage d’un buffer de taille N dans le fichier courant avec la date et l’heure
void WriteSD(double *DataIn, word *Horloge, int Taille, String NomFich) { String StrDATA="";
// 1. Ouverture du fichier en écriture File myFile = SD.open(NomFich, FILE_WRITE);
// 2-3. Conversion et Stockage des données dans la carte mémoire if (myFile) { // Données for(int i=0;i<Taille; i++) { // 2. Conversion des données en chaine de caractaire StrDATA=String(DataIn[i])+",";
// 3. Stockage dans la carte SD myFile.print(StrDATA); }
// Date & Heure for(int i=0;i<6; i++) { // 2. Conversion des données en chaîne de caractère StrDATA=String(Horloge[i])+",";
// 3. Stockage dans la carte SD myFile.print(StrDATA); }
myFile.print("\n"); // Retour à la ligne
// 4. Fermeture du fichier myFile.close(); } else { Serial.println("Erreur d'ouverture du fichier "+NomFichier); } }
La fonction GetDateHeure()
Récupération de la date et l’heure dans le module RTC
void GetDateHeure(word *DateHeure) { // Lecture du module RTC DateTime now = rtc.now();
// Récupération de la date DateHeure[5]=now.year(); // Années DateHeure[4]=now.month(); // Mois DateHeure[3]=now.day(); // Jours
// Récupération de l'heure DateHeure[2]=now.hour(); DateHeure[1]=now.minute(); DateHeure[0]=now.second(); }
La fonction InitRTC()
Initialisation du module RTC avec la génération d’un signal carré de 1Hz. Il sera utilisé pour trigger les acquisitions et le stockage des données
void InitRTC(void) { if (! rtc.begin()) { Serial.println("Le module RTC non disponible"); while (1); // Attente RESET } else { Serial.println("Le module RTC est OK"); rtc.writeSqwPinMode(DS1307_SquareWave1HZ); //DS1307_SquareWave1HZ = 0x10, // 1Hz square wave //DS1307_SquareWave4kHz = 0x11, // 4kHz square wave //DS1307_SquareWave8kHz = 0x12, // 8kHz square wave //DS1307_SquareWave32kHz = 0x13 // 32kHz square wave rtc.adjust(DateTime(F(__DATE__), F(__TIME__))); // Date du PC
//rtc.adjust(DateTime(2020,1,24,10,10,10)); // Ajustement manuel // Ex: 10 Janvier 2020 à 10:00:00: // rtc.adjust(DateTime(2020, 1, 10, 10, 0, 0)); } }
La fonction InitInt4()
Initialisation de l’interruption externe INT4 (Arduino Mega)
// Activation de l'interruption globale (registre SREG) SREG|=0x80;
// Validation de l'interruption INT4 (Registre EIMSK) EIMSK|=0x10; // INT4
// Choix du mode de détection: Front montant dans INT4 EICRB|=0x03; }
La fonction InitSD()
Initialisation de la carte SD: Vérification de la connexion, affichage des informations de la carte, les fichiers disponibles dans la carte, test lecture et écriture dans la mémoire, etc.
void InitSD(byte ShipSel) { // Positionner à "1" le CS: Activation du module pinMode(ShipSel, OUTPUT); digitalWrite(ShipSel, HIGH);
// Initialisation Serial.print("\n Initialisation de la carte SD...");
if (!card.init(SPI_HALF_SPEED, CS)) { Serial.println("Initialisation échouée"); //return; } else { Serial.println("Le câblage est OK et la carte est disponible."); }
Serial.print("\nType de la carte: "); switch (card.type()) { case SD_CARD_TYPE_SD1: Serial.println("SD1"); break; case SD_CARD_TYPE_SD2: Serial.println("SD2"); break; case SD_CARD_TYPE_SDHC: Serial.println("SDHC"); break; default: Serial.println("Non reconnue!"); }
if (!volume.init(card)) { Serial.println("La partition FAT16/FAT32 non dispo.\n Assurez-vous bien de bien formater la carte"); return; }
uint32_t volumesize; Serial.print("\nVolume type is FAT"); Serial.println(volume.fatType(), DEC); Serial.println();
volumesize = volume.blocksPerCluster(); // clusters = somme des blocks volumesize *= volume.clusterCount(); volumesize *= 512; // les blocs ont souvent la taille de 512 bytes Serial.print("Volume size (bytes): "); Serial.println(volumesize); Serial.print("Volume size (Kbytes): "); volumesize /= 1024; Serial.println(volumesize); Serial.print("Volume size (Mbytes): "); volumesize /= 1024; Serial.println(volumesize);
Serial.println("\nFiles found on the card (name, date and size in bytes): "); root.openRoot(volume);
root.ls(LS_R | LS_DATE | LS_SIZE);
if (!SD.begin(CS)) { Serial.println("Initialisation échouée!"); while (1); } Serial.println("Initialisation réussie."); }
La fonction GetData()
Lecture du flux de lumière (la résistance en fonction de la luminosité)
double GetData(byte Analog_pin) { word A0_val; double V0, R_ll;
// Lecture du capteur A0_val=analogRead(Analog_pin);
// Conversion en volt [0,1023]=>[0, 5V] V0=(double)A0_val*V/1023.00;
// Calcul de la résistance en Ohm R_ll=((V/V0)-1.0)*R;
// Renvoie de la valeur return R_ll; }
Le programme complet
/*
* Projet Data logger: Stockage des données dans la carte SD 3/4 * wwww.Electronique-Mixte.fr
Objectifs 1. Savoir utiliser le lecteur de la carte SD 2. Savoir lire / écrire les données dans la carte SD 3. Savoir ajuster la période de mise à jour du fichier 4. Savoir ajuster la période de création d’un nouveau fichier 5. Les erreurs fatales à éviter (Gagnez quelques heures ou jours du travail!) 6. Etc. */
#include <Wire.h> #include "RTClib.h" #include <SPI.h> #include <SD.h> #define CS 10 #define V 5.00 #define R 10000.0 #define N 36 // 60*20=1200 - Taille SRAM #define PinINT4 2 // PE4 - PIN 2 #define TFile 3600 // Création d'un nouveau fichier toutes
RTC_DS1307 rtc; word DateHeure[6]={0,0,0,0,0,0}; double R_l=0.0, Data[N];
Sd2Card card; SdVolume volume; SdFile root;
File myFile; String StrDATA=""; String NomFichier="F1234"; // Fichier Initial (A ne pas tenir en compte)
void setup() { // Init INT4 InitInt4(PinINT4);
// Initialisation du port série: Affichage locale des données Serial.begin(9600);
// Initialisation du Module RTC (Heure de l'ordinateur) InitRTC();
// Initialisation de la carte SD InitSD(CS);
// Stockage des données dans le fichier (Fichier par défaut) WriteSD(Data, DateHeure, N, NomFichier);
// Réouverture et Affichage du contenu du fichier (Fichier par défaut) ReadDisFile(NomFichier);
}
void loop() { //Lecture de l'horloge GetDateHeure(DateHeure); }
ISR(INT4_vect) //ISR(_VECTOR(5)) { // Compteur des échantillons static long count=0;
// Désactivation de l'interruption globale cli();
// Lecture du capteur de luminosité R_l= GetData(A0);
// Mise à jour du buffer des échantillons Data[count]=R_l;
// Mise à jour du compteur count++; count=count%N;
// Mise à jour du nom du fichier NomFichier= UpdateNomFile(TFile, DateHeure, NomFichier);
// Stockage des données dans la carte Mémoire if(!count) //count=N: stockage lorsque le buffer est plein { // Stockage des données dans la carte SD WriteSD(Data, DateHeure, N, NomFichier);
// Réouverture et Affichage du contenu du fichier (Pour la vérification) Serial.println(NomFichier); ReadDisFile(NomFichier); }
// Réactivation de l'interruption globale sei(); }
if (myFile) { // 2. Lecture & Affichage du contenu du fichier while (myFile.available()) { Serial.write(myFile.read()); }
// 3. Fermeture du fichier myFile.close(); } else { Serial.println("Erreur d'ouverture du fichier "+ NomFich); } Serial.println("\n\n"); }
String UpdateNomFile(byte TempsFile, word *Horloge, String OldName) { String NewNom=""; unsigned long T; T=Horloge[0]+60*Horloge[1]+60*60*Horloge[2];
if(!(T%TempsFile)) // Changement du nom du fichier toutes les TempsFile { for(int i=3;i>=0;i--) { NewNom=NewNom+String(Horloge[i]); } NewNom="F"+NewNom+".txt"; } else NewNom=OldName;
return NewNom; } void WriteSD(double *DataIn, word *Horloge, int Taille, String NomFich) { String StrDATA="";
// 1. Ouverture du fichier en écriture File myFile = SD.open(NomFich, FILE_WRITE);
// 2-3. Conversion et Stockage des données dans la carte mémoire if (myFile) { // Données for(int i=0;i<Taille; i++) { // 2. Conversion des données en chaine de caractaire StrDATA=String(DataIn[i])+",";
// 3. Stockage dans la carte SD myFile.print(StrDATA); }
// Date & Heure for(int i=0;i<6; i++) { // 2. Conversion des données en chaîne de caractère StrDATA=String(Horloge[i])+",";
// 3. Stockage dans la carte SD myFile.print(StrDATA); }
myFile.print("\n"); // Retour à la ligne
// 4. Fermeture du fichier myFile.close(); } else { Serial.println("Erreur d'ouverture du fichier "+NomFichier); } }
void GetDateHeure(word *DateHeure) { // Lecture du module RTC DateTime now = rtc.now();
// Récupération de la date DateHeure[5]=now.year(); // Années DateHeure[4]=now.month(); // Mois DateHeure[3]=now.day(); // Jours
// Récupération de l'heure DateHeure[2]=now.hour(); DateHeure[1]=now.minute(); DateHeure[0]=now.second(); }
void InitRTC(void) { if (! rtc.begin()) { Serial.println("Le module RTC non disponible"); while (1); // Attente RESET } else { Serial.println("Le module RTC est OK"); rtc.writeSqwPinMode(DS1307_SquareWave1HZ); //DS1307_SquareWave1HZ = 0x10, // 1Hz square wave //DS1307_SquareWave4kHz = 0x11, // 4kHz square wave //DS1307_SquareWave8kHz = 0x12, // 8kHz square wave //DS1307_SquareWave32kHz = 0x13 // 32kHz square wave rtc.adjust(DateTime(F(__DATE__), F(__TIME__))); // Date du PC
//rtc.adjust(DateTime(2020,1,24,10,10,10)); // Ajustement manuel // Ex: 10 Janvier 2020 à 10:00:00: // rtc.adjust(DateTime(2020, 1, 10, 10, 0, 0)); } }
// Activation de l'interruption globale (registre SREG) SREG|=0x80;
// Validation de l'interruption INT4 (Registre EIMSK) EIMSK|=0x10; // INT4
// Choix du mode de détection: Front montant dans INT4 EICRB|=0x03; }
void InitSD(byte ShipSel) { // Positionner à "1" le CS: Activation du module pinMode(ShipSel, OUTPUT); digitalWrite(ShipSel, HIGH);
// Initialisation Serial.print("\n Initialisation de la carte SD...");
if (!card.init(SPI_HALF_SPEED, CS)) { Serial.println("Initialisation échouée"); //return; } else { Serial.println("Le câblage est OK et la carte est disponible."); }
Serial.print("\nType de la carte: "); switch (card.type()) { case SD_CARD_TYPE_SD1: Serial.println("SD1"); break; case SD_CARD_TYPE_SD2: Serial.println("SD2"); break; case SD_CARD_TYPE_SDHC: Serial.println("SDHC"); break; default: Serial.println("Non reconnue!"); }
if (!volume.init(card)) { Serial.println("La partition FAT16/FAT32 non dispo.\n Assurez-vous bien de bien formater la carte"); return; }
uint32_t volumesize; Serial.print("\nVolume type is FAT"); Serial.println(volume.fatType(), DEC); Serial.println();
volumesize = volume.blocksPerCluster(); // clusters = somme des blocks volumesize *= volume.clusterCount(); volumesize *= 512; // les blocs ont souvent la taille de 512 bytes Serial.print("Volume size (bytes): "); Serial.println(volumesize); Serial.print("Volume size (Kbytes): "); volumesize /= 1024; Serial.println(volumesize); Serial.print("Volume size (Mbytes): "); volumesize /= 1024; Serial.println(volumesize);
Serial.println("\nFiles found on the card (name, date and size in bytes): "); root.openRoot(volume);
root.ls(LS_R | LS_DATE | LS_SIZE);
if (!SD.begin(CS)) { Serial.println("Initialisation échouée!"); while (1); } Serial.println("Initialisation réussie."); }
double GetData(byte Analog_pin) { word A0_val; double V0, R_ll;
// Lecture du capteur A0_val=analogRead(Analog_pin);
// Conversion en volt [0,1023]=>[0, 5V] V0=(double)A0_val*V/1023.00;
// Calcul de la résistance en Ohm R_ll=((V/V0)-1.0)*R;