Contrôleur de T°, Humidité avec pilotage manuel ou automatique avec log sur SD

Bonjour à toutes et à tous

J'avais déjà posté un code pour montrer comment on peut bricoler un Contrôleur de T°, Humidité avec pilotage manuel ou automatique

Lors des discussions on m'a suggéré plusieurs fois de rajouter la possibilité d'enregistrer les données sur carte SD.

J'ai ressorti alors un autre vieux bout de code qui faisait cela et je vais l'injecter dans l'autre code.

Il faudra sans doute utiliser une MEGA pour avoir toutes les PINs voulues (Sur un UNO sans doute conflit avec LED_BUILTIN (pin D13) que j'utilise et SCK de SPI sur UNO - faudrait changer tout ce qui touche LED_BUILTIN - j'avoue ne pas avoir compté si ça rentre - la flemme et l'objectif c'est de montrer un bout de code qui fait le LOG).

Donc en plus des informations de l'autre post, il vous faudra

COMPOSANTS UTILISES

  • Une LED fonctionnant sous 5V
  • Une Résistance de limitation de courant adaptée à votre LED (220Ω par exemple)
  • Un shield SD fonctionnant en mode SPI
  • Une carte SD de 2Go par exemple (certains lecteurs ne savent pas gérer plus)
  • Des fils de connexion

PINS UTILISEES

  • 48 = pin d'une LED (avec sa résistance de limitation de courant) qui sera allumée pendant le LOG
  • 50 = SD MISO pour la communication SPI (avec la carte SD)
  • 51 = SD MOSI pour la communication SPI (avec la carte SD)
  • 52 = SD SCK pour la communication SPI (avec la carte SD)
  • 53 = SD CS pour la communication SPI (avec la carte SD)

LIBRAIRIES UTILISEES

CABLAGE

  • Carte SD en SPI, utilise les pins standard de la carte MEGA
  • Vcc sous 5V, GND à GND
  • SD MISO <---> Arduino MEGA 50
  • SD MOSI <---> Arduino MEGA 51
  • SD SCK <---> Arduino MEGA 52
  • SD CS <---> Arduino MEGA 53
  • Pin 48 <---> ANODE LED <---> RESISTANCE 200Ω (adaptée à votre LED) <---> GND (led d'activité carte SD)

La LED est optionnelle, elle sert à montrer quand il y a une activité en cours sur la carte SD. Ça vous permet (si d'aventure vous vous mettez à écrire bcp de choses) de savoir si la carte est utilisée, dans ce cas il vaut mieux éviter de couper sauvagement le courant :slight_smile:

J'utilise la librairie SdFat et non pas celle fournie en standard dans l'IDE. La raison est simple, c'est le même auteur pour les deux et la version SdFat est beaucoup plus performante et à jour. Il vous faudra donc l'installer.

ETAPE 1

Après avoir effectué le montage tel que décrit, on va commencer par formatter la carte SD

  1. Choisir dans les exemple de la librairie SdFat le programme SdFormatter.ino

  2. changez la ligne [

Serial.begin(9600);

](SdFat/SdFormatter.ino at d43541f01b7405c4ad260d0c82e4c427e420df54 · greiman/SdFat · GitHub) en Serial.begin(115200); parce qu'il n'y a aucune raison d'aller lentement (ça m'énerve de voir encore du 9600).

  1. compilez le sketch, vérifiez que vous n'avez aucune erreur. chargez le programme sur votre MEGA

  2. ouvrez le moniteur Série et laissez vous guider

```
[color=purple]
Type any character to start

This program can erase and/or format SD/SDHC cards.

Erase uses the card's fast flash erase command.
Flash erase sets all data to 0X00 for most cards
and 0XFF for a few vendor's cards.

Cards larger than 2 GB will be formatted FAT32 and
smaller cards will be formatted FAT16.

Warning, all data on the card will be erased.
Enter 'Y' to continue:
Y + return

Options are:
E - erase the card and skip formatting.
F - erase and then format the card. (recommended)
Q - quick format the card without erase.

Enter option:
F + return

Enter option: F
Card Size: 2008 MB, (MB = 1,000,000 bytes)

Erasing
...............
All data set to 0x00
Erase done

Formatting
Blocks/Cluster: 64
FAT16
..
Format done
[/color]
```

Voilà, maintenant nous avons une carte bien formatée en FAT16

ETAPE 2
Je mets en pièce jointe le nouveau programme LCD_RTC_SD_TEMP_HUM_VENT.ino à télécharger directement. C'est la nouvelle version qui gère la carte SD.

Notez les bouts de code suivants:

const char * extensionFichierLog = ".LOG"; // L'extension utilisée pour tous nos fichiers (format .XXX -> un point et 3 caracteres max)

C'est ici que vous pouvez changez l'extension du fichier, par exemple en ".TXT" ou ".DAT" ou ".CSV" à votre convenance (un point et 3 caracteres max)

char * nomDuFichierLog()
{
   ..
}

Cette fonction doit retourner le nom du fichier à ouvrir (ou créer s'il n'existe pas) pour rajouter à la fin les données que vous voulez sauver. c'est appelé à chaque sauvegarde, donc le nom du fichier peut changer au fil du temps

Le nom de fichier (pour être tranquille dans tous les formats) doit être au max en 8.3 (8 caractères, un point, 3 caractères de suffixe).

Ici je construit un nom de fichier qui va changer tous les mois sous la forme de AAAAMM.LOG ou AAAA est l'année en cours (2018) et MM le mois en cours sur 2 digits donc entre 01 et 12 (lus sur la RTC).

--> Donc si vous exécutez le code en ce moment votre fichier va s'appeler 201807.LOG et en août il sera 201808.LOG etc...

const uint32_t LOG_INTERVAL_MS = 5000; // (5 * 60 * 1000UL); // 5 minutes (attention de bien mettre le UL pour ne pas faire les calculs en int mais bien en unsigned long)

Au début du code vous avez la fréquence de sauvegarde que vous souhaitez. Ici j'ai mis 5 secondes à titre de démo. Si vous voulez sauvegarder toutes les 5 minutes, mettez 5 * 60 * 1000[color=red]UL[/color] (5 x soixante secondes dans une minute x 1000ms dans 1 seconde = 5 minutes).
Le UL est super important pour que le calcul se fasse en unsigned long et pas en int.

Une sauvegarde toutes les 15 minutes pour un processus lent est sans doute approprié (96 lignes par jour - ça fait environ 3000 lignes par mois), n'oubliez pas que l'écriture sur SD est un processus pas super rapide, donc il ralentit la loop et la gestion des boutons - donc n'essayez pas de sauver toutes les 5 ms... :slight_smile:

NB: Je n'ai pas testé si ça marchait sur plusieurs mois, je viens de fusionner les codes... Donc à vous de tester et de me dire si tout se passe bien dans la durée :slight_smile:

Vous trouverez ensuite la fonction

void logToutesDonnees()
{
   ...
}

c'est dans cette fonction que l'on fait la sauvegarde sur la carte SD. la fonction commence simplement par ouvrir (et créer s'il n'existe pas) un fichier avec le nom tel que défini dans la fonction décrite ci dessus puis j'ai mis une série de print pour faire un dump de tous mes paramètres.

LCD_RTC_SD_TEMP_HUM_VENT.ino (33.5 KB)

SUITE DU POST

Les éléments suivants sont disponibles pour générer votre log (et datent de la lecture la plus récente des capteurs)

temperaturesDS18820[]       => tableau des T° lues sur les DS18820. On en a nbCapteursDS18820 (ce sont des float)
temperatureSi7021           => la T° lue sur le Si7021 (float)
humiditeSi7021              => l'humidité relative lue sur le Si7021 (float)
lesParametres.tempConsigne  => la  consigne de température telle que fournie par l'utilisateur
lesParametres.humConsigne  => la consigne d'humidité telle que fournie par l'utilisateur
lesParametres.ventConsigne  => la consigne de ventilation telle que fournie par l'utilisateur
lesParametres.vitesseVentilateur  =>le PWM associé à la consigne pour le ventilateur
tempTropChaudDelta          => une constante du programme
humiditeMin                 => une constante du programme (seuil min d'humidité)
humiditeMax                 => une constante du programme (seuil max d'humidité)
chauffageOn                 => vrai si allumé
humidificateurOn            => vrai si allumé
ventilateurOn               => vrai si allumé
climatiseurOn               => vrai si allumé
mode                        => une valeur de l'enum {automatique, manuel, param, eteint}

dans mon cas j'écris tout ce que je peux, donc mon fichier /201807.LOG contient

1532398552 2018/07/24 02:15:52 DS18820={28.2} Si7021={28.9, 45.7} Consignes={18, 55, 2, 93} Etats={0, 1, 1, 1} Mode={AUTO}
1532398557 2018/07/24 02:15:57 DS18820={28.2} Si7021={29.0, 45.7} Consignes={18, 55, 2, 93} Etats={0, 1, 1, 1} Mode={AUTO}
1532398562 2018/07/24 02:16:02 DS18820={28.2} Si7021={28.9, 45.7} Consignes={18, 55, 2, 93} Etats={0, 1, 1, 1} Mode={AUTO}
1532398567 2018/07/24 02:16:07 DS18820={28.2} Si7021={28.9, 45.6} Consignes={18, 55, 2, 93} Etats={0, 1, 1, 1} Mode={AUTO}
1532398572 2018/07/24 02:16:12 DS18820={28.2} Si7021={28.9, 45.6} Consignes={18, 55, 2, 93} Etats={0, 1, 1, 1} Mode={AUTO}
1532398577 2018/07/24 02:16:17 DS18820={28.2} Si7021={28.9, 45.6} Consignes={18, 55, 2, 93} Etats={0, 1, 1, 1} Mode={AUTO}
....

Les champs sont séparés par des tabulations, une ligne commence par l'heure unix, suivie de la date et l'heure en format standard, suivies d'un dump des différents capteurs et sous forme de tableaux entre accolades, avec les valeurs séparées par des virgules, puis les consignes, états et mode. J'ai fait ça parce que c'est joli, mais pas forcément simple à parser (si vous voulez l'analyser par informatique, écrivez en JSON par exemple ou un simple CSV).... --> à vous de jouer pour voir ce que vous voulez écrire et comment le formater. (si vous voulez faire de beaux graphes sous excel, imprimez juste les valeurs qui vous intéressent et le timestamp unix ou l'heure en début, séparées par des tabulation. c'est facile à lire ensuite en CSV sous excel.

ETAPE 3

En prime pour ceux qui n'ont pas envie d'éjecter la carte SD et la mettre dans leur ordinateur, j'ai aussi retrouvé un bout de code qui parcourt (avec la librairie SdFat) l'arborescence des répertoires et fichiers sur votre carte SD depuis la racine (dans l'ordre des dernières écritures) et qui cherche tous les fichiers .LOG et vous affichent leur nom, taille et contenu directement dans le moniteur Série (à 115200 bauds)

Voici le code:

/* ********************************************************
     AUTEUR J-M-L pour forum Arduino
     Dump des fichiers LOGS
     http://forum.arduino.cc/index.php?topic=559714.0
     Pour Arduino Arduino MEGA (ou UNO avec les bonnes pins SPI)
     Code libre de droits, mention de l'origine appréciée
     Librairies propriété de leurs auteurs respectifs
     Sans garantie aucune de bon fonctionnement :)
   ********************************************************

   PINS UTILISEES
   --------------
  Carte SD en SPI, utilise les pins standard de la carte MEGA
    Vcc sous 5V, GND à GND
    SD MISO <---> Arduino MEGA 50
    SD MOSI <---> Arduino MEGA 51
    SD SCK  <---> Arduino MEGA 52
    SD CS   <---> Arduino MEGA 53

*/

// **********************************************************
// les définitions
// **********************************************************

//
const uint8_t MAXNBDIR = 10;
const uint8_t MAXFILENAME = 13; // format 8.3 --> 12 + 1 NULL à la fin
char sousRepertoires[MAXNBDIR][MAXFILENAME];
const char * extensionFichierLog = ".LOG";

// Gestion de la carte SD

#include <SPI.h>
#include "SdFat.h"
const uint8_t chipSelect = SS; // SD chip select pin
SdFat sd;


// **********************************************************

void dumpLOGForDirectory(FatFile depart, int8_t repertoireNb = 0)
{
  FatFile fichier;
  char nom[MAXFILENAME];  //  format de nom en 8.3

  while (true) {
    if (!fichier.openNext(&depart)) break;  // plus de fichiers, on termine
    bool testRepertoire = fichier.isDir();
    uint32_t fsize = fichier.fileSize();       // taille du fichier en octets

    fichier.getName(nom, MAXFILENAME); // nom du répertoire ou fichier

    if (testRepertoire) { // c'est un répertoire, on ajoute le sous répertoire et on fait une récursion
      FatFile fichierSuivant;
      uint16_t index = depart.curPosition() / 32 - 1; //index du répertoire courant

      memcpy(sousRepertoires[repertoireNb], nom, strlen(nom) + 1); // le +1 pour prendre le '\0'

      fichier.close(); // on ferme avant d'ouvrir le suivant
      fichierSuivant.open(&depart, index, O_RDONLY); // ouvre ce répertoire
      dumpLOGForDirectory(fichierSuivant, repertoireNb + 1); // et dump du contenu en mode récursion

    } else { // c'est un fichier
      // test si c'est un fichier avec le bon suffixe
      if (strlen(nom) >= strlen(extensionFichierLog)) {
        char * ptrSuffixe;
        ptrSuffixe = nom + strlen(nom) - strlen(extensionFichierLog); // un pointeur sur le suffixe
        if (!strcmp(ptrSuffixe, extensionFichierLog)) { // si le suffixe est le bon
          // on imprime le chemin et le Nom de fichier
          Serial.write('/');
          for (int8_t i = 0; i < repertoireNb; i++) { 
            Serial.print(sousRepertoires[i]);
            Serial.write('/');
          }

          // suivi du nom
          Serial.print(nom); 

          // et la taille du fichier
          Serial.print(" (taille: ");
          Serial.print(fsize);
          Serial.println(F(")\n"));

          // et on affiche le contenu du fichier
          fichier.rewind(); // on se met au début
          while (fichier.available() > 0) { // et on parcourt le fichier
            int c = fichier.read();
            if (c != -1) Serial.write((char) c);
          }
          Serial.println();
        }
      }
      fichier.close();
    }
  } // fin du while (sortie par break)
}

void setup() {
  FatFile root;

  Serial.begin(115200);

  // Initialise a la plus haute vitesse possible sous 50MHz
  // Essayez une vitesse plus basse si vous avez des "SPI errors"
  if (!sd.begin(chipSelect, SD_SCK_MHZ(50))) {
    sd.initErrorHalt();
  }

  if (root.openRoot(sd.vol())) {    // on commence à la racine
    dumpLOGForDirectory(root); // cette fonction est récursive
  } else {
    Serial.println(F("ERREUR OUVERTURE RACINE"));
  }
}

void loop() {}

Tout se fait dans le setup().

Le câblage peut rester identique à celui de l'autre montage et si vous exécutez le programme après avoir laissé tourner un peu le code de log (qui écrit toutes les 5 secondes) vous allez voir un truc comme cela:

Le code est rigolo à lire, il utilise une Fonction récursive pour traverser les répertoires et fichiers (donc on ne peut pas trop descendre bas dans des arborescences car on n'aura pas assez de mémoire)

Voilà - amusez vous bien - j'espère que ça en aidera certains.

Jean-Marc

Bonjour et merci pour votre réactivité.

Un Grand Bravo pour ce tutoriel très clair.

Encore merci pour le partage.

De rien :slight_smile:

Cool merci, les usages sont multiples,

Beau travail.

Dododu01:
Cool merci, les usages sont multiples,

Beau travail.

merci :slight_smile: