Skyduino:~#
Articles
arduino, programmation

[Arduino] Charger un fichier de configuration depuis une carte SD

Bonjour tout le monde !

Aujourd’hui j’ai à vous proposer un morceaux de code arduino qui devrait en intérésser plus d’un !

Pour la petite histoire :
Un membre du forum arduino.cc cherchait un moyen de charger des valeurs depuis un fichier de configuration texte un peu à la manière des fichiers .ini, le tout stocké sur une carte SD.
J’ai trouvé l’idée très intéréssante et je me suis immédiatement lancé dans le codage de la chose.

Il y a 3 règles à respecter pour que le code ci dessous fonctionne correctement :
– les fins de lignes doivent être au format UNIX (\n) (amis windowsiens -> notepad++ ;))
– les lignes de commentaires ne doivent pas avoir d’espaces ou de tabulations avant le #
– il faut impérative ajouter une ligne à la fin du fichier

Voici un exemple bête et méchant de fichier de configuration permettant de tester le code ci dessous :

# Ceci est un commentaire

# Toto
toto = 42

   titi   =  1337
      	
tata=7

Et le résultat dans le serial monitor :

Exemple de chargement d'un fichier de config
Ligne mal forme a la ligne 7
toto = 42
tata = 7
titi = 1337

L’erreur à la ligne 7 est normal, j’ai volontairement ajouté une tabulation pour montrer que le programme continué en cas de ligne mal formé 😉

Bon après avoir vu le résultat vous devez voir dire « LE CODE !!!! », et bien le voici :

/* Includes */
#include <SPI.h>
#include <SD.h>

/* Taille du buffer */
const byte BUFFER_SIZE = 32;

/* Variables d'exemple qui seront chargé depuis le fichier de configuration */
int toto = 0, tata = 0, titi = 0;

/* setup() */
void setup() {

  /* Déclare le buffer qui stockera une ligne du fichier, ainsi que les deux pointeurs key et value */
  char buffer[BUFFER_SIZE], *key, *value;

  /* Déclare l'itérateur et le compteur de lignes */
  byte i, buffer_lenght, line_counter = 0;

  /* Initialise le port série */
  Serial.begin(9600);
  Serial.println("Exemple de chargement d'un fichier de config");

  /* Initialise la carte SD */
  pinMode(10, OUTPUT);
  if (!SD.begin(4)) { // Gère les erreurs
    Serial.println("Erreur de carte SD !");
    for(;;);
  }

  /* Ouvre le  fichier de configuration */
  File config_file = SD.open("config.txt", FILE_READ);
  if(!config_file) { // Gère les erreurs
    Serial.println("Erreur d'ouverture du fichier !");
    for(;;);
  }

  /* Tant que non fin de fichier */
  while(config_file.available() > 0 ){

    /* Récupère une ligne entière dans le buffer */
    i = 0;
    while((buffer[i++] = config_file.read()) != '\n') {

      /* Si la ligne dépasse la taille du buffer */
      if(i == BUFFER_SIZE) {

        /* On finit de lire la ligne mais sans stocker les données */
        while(config_file.read() != '\n');
        break; // Et on arrête la lecture de cette ligne
      }
    }

    /* On garde de côté le nombre de char stocké dans le buffer */
    buffer_lenght = i;

    /* Gestion des lignes trop grande */
    if(i == BUFFER_SIZE) {
      Serial.print("Ligne trop longue à la ligne ");
      Serial.println(line_counter, DEC);
    }

    /* Finalise la chaine de caractéres ASCIIZ en supprimant le \n au passage */
    buffer[--i] = '\0';

    /* Incrémente le compteur de lignes */
    ++line_counter;

    /* Ignore les lignes vides ou les lignes de commentaires */
    if(buffer[0] == '\0' || buffer[0] == '#') continue;
      
    /* Cherche l'emplacement de la clef en ignorant les espaces et les tabulations en début de ligne */
    i = 0;
    while(buffer[i] == ' ' || buffer[i] == '\t') {
      if(++i == buffer_lenght) break; // Ignore les lignes contenant uniquement des espaces et/ou des tabulations
    }
    if(i == buffer_lenght) continue; // Gère les lignes contenant uniquement des espaces et/ou des tabulations
    key = &buffer[i];

    /* Cherche l'emplacement du séparateur = en ignorant les espaces et les tabulations âpres la clef */
    while(buffer[i] != '=') {

      /* Ignore les espaces et les tabulations */
      if(buffer[i] == ' ' || buffer[i] == '\t') buffer[i] = '\0';
        
      if(++i == buffer_lenght) {
        Serial.print("Ligne mal forme a la ligne ");
        Serial.println(line_counter, DEC);
        break; // Ignore les lignes mal formé
      }
    }
    if(i == buffer_lenght) continue; // Gère les lignes mal formé

    /* Transforme le séparateur = en \0 et continue */
    buffer[i++] = '\0';

    /* Cherche l'emplacement de la valeur en ignorant les espaces et les tabulations âpres le séparateur */
    while(buffer[i] == ' ' || buffer[i] == '\t') {
      if(++i == buffer_lenght) {
        Serial.print("Ligne mal forme a la ligne ");
        Serial.println(line_counter, DEC);
        break; // Ignore les lignes mal formé
      }
    }
    if(i == buffer_lenght) continue; // Gère les lignes mal formé
    value = &buffer[i];

    /* Transforme les données texte en valeur utilisable */
    /* C'est ce morceaux de code qu'il vous faudra adapter pour votre application 😉 */
    if(strcmp(key, "toto") == 0) {
      toto = atoi(value);
    } 
    else if(strcmp(key, "tata") == 0) {
      tata = atoi(value);
    } 
    else if(strcmp(key, "titi") == 0) {
      titi = atoi(value);
    } 
    else { // Default 
      Serial.print("Clef inconnu ");
      Serial.println(key);
    }

  }

  /* Ferme le fichier de configuration */
  config_file.close();

  /* Affiche le résultat de la configuration */
  Serial.print("toto = ");
  Serial.println(toto);
  Serial.print("tata = ");
  Serial.println(tata);
  Serial.print("titi = ");
  Serial.println(titi);
}

/* loop() */
void loop() {
  // Rien à faire ... on se fait un apéro ? 
}

L’étendu des possibilités offerte par ce systéme est énorme :
– configuration d’ip statique avec une ethernet shield,
– modification de variables de fonctionnement par l’utilisateur,
– etc …

Maintenant c’est à vous de jouer 😉

Discussion

26 réflexions sur “[Arduino] Charger un fichier de configuration depuis une carte SD

  1. Encore un super tuto 🙂
    thx

    Publié par trigger | 19 juin 2012, 20 h 17 min
  2. A quand une vidéo sur youtube sky’ ? 😉

    Publié par Ghost | 13 septembre 2012, 21 h 34 min
  3. Dans le cas de l’exemple, on utilise que des int. Mais si on veux récupérer des string, il faut inclure les parties de code suivantes:
    – déclaration du string de réception (char array): char recept[32];
    – dans la partie où l’on transforme les données texte en variables: strcopy(recept, value);

    Excellent tuto!

    Publié par Seb | 5 octobre 2012, 1 h 12 min
  4. Merci pour ce bout de code, il fonctionne à merveille pour moi. J’aimerai cependant mieu comprendre tout ça 😛

    Publié par Lentrave | 16 octobre 2012, 6 h 07 min
  5. Bonjour,

    Je me demande si il est possible de faire la même chose avec plusieurs cas. Exemple :
    [Mod-01]
    Conf-01 = Modele 01
    Mixage-V1V2 = true
    Mixage-V3V4 = false
    S1 = PWM
    S2 = OnOffOn

    [Mod-02]
    Conf-01 = Modele 02
    Mixage-V1V2 = false
    Mixage-V3V4 = false
    S1 = OnOffOff
    S2 = OnOffOn

    Ainsi de suite. Comme on peut le remarqué j’ai une clef particulière avec des crochets. Comme dans un fichier ini.

    Autre-chose. On utilise dans votre programme la SPI. peut-on par exemple déporté la SPI sur une shield éthernet sans avoir la carte directement sur la carte méga ?

    Mieux encore, récupérer un connecteur SD (J’en possède quelques un) et le brancher directement sur la carte. Quelle modiffications de programme cela apporterais ?

    Autant de question que je suis en train de cherché.

    Merci.

    P.S très beau site, très complet. Continuez comme ça.

    Publié par Alexandre | 27 octobre 2012, 12 h 12 min
    • >> Ainsi de suite. Comme on peut le remarqué j’ai une clef particulière avec des crochets. Comme dans un fichier ini.

      C’est possible en ajoutant un test aprés avoir obtenu la clef.
      Si le 1er caractère de la clef est un ‘[‘ il faudrait alors prévoir une sous routine qui découperai jusqu’au ‘]’ et stockerait le résultat dans un buffer pour la suite.

      >> Autre-chose. On utilise dans votre programme la SPI. peut-on par exemple déporté la SPI sur une shield éthernet sans avoir la carte directement sur la carte méga ?

      Pour tester le code ci dessus j’ai utilisé le slot pour carte SD de ma shield ethernet 😉

      >> Mieux encore, récupérer un connecteur SD (J’en possède quelques un) et le brancher directement sur la carte.

      Ça ce fait, j’ai même déjà tenter, cela demande juste quelques résistances pour ajuster les niveaux logiques 5v (arduino) et 3v3 (carte sd) :
      http://www.instructables.com/id/Cheap-DIY-SD-card-breadboard-socket/

      >> Quelle modifications de programme cela apporterais ?

      Aucune, c’est côté hardware que tout ce joue.

      Publié par skywodd | 27 octobre 2012, 14 h 55 min
  6. Ok pour ces infos. En fait, j’ai trouvé une solution qui me va bien en séparant sur plusieurs fichiers. Beaucoup plus simple que que mettre les modèles à la queue leu leu !

    Par contre je bloque sur une boucle infini ici : while(bufferConf[i] != ‘=’) {} Ou je ne retrouve pas le ‘=’. Donc ça boucle, ça boucle.. Je vais prévoir un nombre de passage minimum si on ne trouve pas on stripe la ligne. Mais, à ce moment là on ne résout pas le souci !

    Publié par Alexandre | 1 novembre 2012, 10 h 23 min
    • En fait ça fonctionne très bien ! Quelques erreurs de frappe et d’indentation réparé, j’ai bien mes deux valeurs. Merci pour ce code fort pratique. Par contre, je me suis permis de retirer le count de ligne et les erreurs. Je maintient l’idée du coup du minimum.

      Publié par Alexandre | 1 novembre 2012, 11 h 08 min
      • C’est un choix 😉
        Moi j’ai toujours tendance à me planter dans mes fichiers de conf, donc un test de plus ou de moins.

        Publié par skywodd | 1 novembre 2012, 13 h 50 min
      • Oui, alors que moi, j’aime choisir une structure et m’y tenir !

        Bon, maintenant j’ai remarqué que je sortais sur char ker* et char val*. Donc des pointeur. J’aurais aimer faire ce qui suit.
        Algorithme:
        //Charger tbl1[ligneCounter] = key;
        //Charger tbl1[ligneCounter] = val

        Quand je fait ceci, j’ai du vide dans mon tableau 😦

        C’est dommage car comme les deux tableaux ont une position comune, si je choisit la ckef, j’aurais sa position et si je demande dans le ,second tableau la position obtenu dans le premier tableau, je récupère la valeur de la clef.

        C’est peut-être pas propre mais au moins je peux faire diverses opérations tout en maîtrisant virtuellement toutes les données avant enregistrement.

        Bref, je cherche un moyen simple et efficace. Si il existe une soluton, un code, une piste, je suis preneur.

        Merci.

        Publié par alexandre | 1 novembre 2012, 17 h 51 min
  7. Bonjour,

    Je souhaiterais afficher des valeurs supérieurs à 32767 Pouvez vous m’éclairer sur les variable à modifier ?
    Merci d’avance

    Publié par David | 21 décembre 2013, 12 h 25 min
  8. bonjour,
    je suis débutant et je n’ai que des erreurs qui apparaissent en retour.
    Ces erreurs sont des sauts de ligne.
    Sous Windows il faut enregistrer le fichier au format .sh ?

    merci

    Publié par nico | 4 juin 2014, 0 h 04 min
    • en fait j’ai bien les valeurs mais une erreur à chaque saut de ligne
      c’est pas grave j’ai la valeur … et MERCI pour ce tuto

      Publié par nico | 4 juin 2014, 0 h 41 min
      • Tu dois avoir des fins de lignes au format Windows, ça expliquerai l’erreur à chaque fin de ligne.

        Sous Notepad++ tu as une options pour convertir les fin de lignes au format Unix 😉
        « Edition » -> « Convertir les sauts de lignes » -> « Convertir au format Unix »

        Publié par Skywodd | 9 juin 2014, 19 h 34 min
  9. Merci pour le conseil
    Depuis j’ai introduit ce code dans mon programme , je l’ai mis dans une fonction « load » et j’ai fait une fonction « sav ».
    Le tout enregistre directement en .sh , ce qui m’évite de passer par notepad++ .

    Merci encore …

    Publié par nico | 9 juin 2014, 22 h 33 min
    • Par contre j’ai un problème, une fois les valeurs de la SD chargé, mes potentiomètres ne peuvent plus atteindre une valeur en dessous de 1 . j’ai ce problème seulement depuis que j’ai déplacer le code de chargement en dehors du setup

      Publié par nico | 10 juin 2014, 0 h 34 min
      • en faite le problème viens de mon code.
        je vais voir ça après une bonne nuit

        Publié par nico | 10 juin 2014, 1 h 56 min
  10. Je ne comprend rien
    Un bug je pense… j’ai débranché l’arduino , rechargé d’autre programmes pour tester mes composants.
    J’ai remis mon programme qui me faisait n’importe quoi, et tout semble OK.
    Désolé de faire un monologue, c’est que je ne voudrais pas que quelqu’un se gratte la tête pour rien.

    Publié par nico | 10 juin 2014, 3 h 01 min
    • OK du coup j’arrive pas a dormir sans savoir pourquoi…
      Donc je vais enfin pouvoir aller me coucher car la j’ai trouver le problème. 😉
      Si je démarre l’arduino sans carte sd il se met en défaut « manque carte »
      A ce moment la, si je fait un reset, le prog se relance…
      et la, le bug se produit.
      Donc conclusion il faut que je coupe l’alim et redémarrer pour que tout se passe bien.
      Voila monologue terminé!
      Si quelqu’un a quelque chose à redire sur mon histoire…..!!
      je suis curieux.

      Publié par nico | 10 juin 2014, 4 h 28 min
      • j’ai quand même des bugs avec la sd.

        Est-ce que le code doit être modifié pour fonctionner sans problème ailleurs que dans le setup ?
        Est-ce qu’il faut faire un reset pour la sd avant de faire un chargement de données?
        Désolé avec mes questions de débutant, mais ce code est un élement essentiel pour mon projet.

        PS: mon projet est un contrôleur midi avec 16 potentiomètres qui se « switch » sur 128 contrôles X tout en gardant les valeurs X en mémoire pour éviter les sauts de valeurs lors d’un « switch ».
        Le potentiomètre est inactif tant qu’il n’a pas retrouvé sa valeur X.
        Un retour en bargraph LED pour indiquer les valeurs X à retrouver sur chaque potard.
        Et le top serait bien sûr de pouvoir enregister ses valeurs sur une SD pour permettre plusieurs organisations de music.

        Si quelqu’un a une idée ou un lien qui explique bien l’utilisation des SD avec les arduino mega.

        Merci 😉

        Publié par nico | 14 juin 2014, 7 h 03 min
  11. Bonjour, très bon tuto qui me sert beaucoup, mais je bloque concernant mon prochain projet, je veux récupérer l’adresse IP du de l’ethernet shield sur la carte SD dans un fichier INIT.

    Pour récupérer l’adresse pas de problème je la récupère soit en String ou en Char Array. Mon problème c’est qu’il me faut des entiers pour les insérer dans IPAddress ip();

    int a=192; \
    int b=168; |
    int c=44; |
    int d=180; > Ceci fonctionne
    byte mac[] = {0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED }; |
    IPAddress ip(a,b,c,d); /

    Si quelqu’un a rencontré et résolu ce genre de problème je le remercie d’avance pour son aide

    Publié par Jean François 85 | 30 décembre 2014, 15 h 34 min
    • Tu peux faire :

      if (strcmp(key, "monAddresseIp") == 0) {
        int ip1, ip2, ip3, ip4;
        if (sscanf(value, "%d.%d.%d.%d", &ip1, &ip2, &ip3, &ip4) == 4) {
          // "192.168.1.254" -> ip1 = 192, ip2 = 168, ip3 = 1, ip4 = 254
          // Idéalement il serait bon de vérifier que ip1/2/3/4 contient une valeur entre 0 et 255
        } else {
          // Format IPv4 invalide
        }
      }
       
      

      Publié par Skywodd | 31 décembre 2014, 13 h 05 min
  12. bonjour,

    ayant voulu faire la même chose mais avec fichier de conf en XML
    je vous laisse ce bout de code sa pourra toujours servir a quelqu’un

    fichier sur la carte sd : box.cnf

    <tag1>bonjour</tag1>
    <tag2>123</tag2>
    

    coté arduino :

    
    #include <SPI.h>
    #include <SD.h>
    File XML;
    
    void setup(){
    	Serial.begin(9600);
    	pinMode(4, OUTPUT);
    	SD.begin(4);
    	
    	char xml_1[20];
    	parse_xml_sd("box.cnf","tag1").toCharArray(xml_1,20);				// convertion en char
      
    	int xml_2 = parse_xml_sd("box.cnf","tag2").toInt();					// convertion en int
    	
    	Serial.print("result : ");
    	Serial.println(xml_1);
    	Serial.print("result : ");
    	Serial.println(xml_2);
    }
    
    void loop()
    {
    
      
    }
    /*
     * 
     * name: parse_xml_sd
     * @param char* XML_name,String tag
     * @return String data
     * 
     */
    String parse_xml_sd(char* XML_name,String tag){
    	String XML_recept ;													// String pour stocker le contenue du fichier
    	int tag_start = 0;													// int debut du tag d'ouverture
    	int tag_end = 0;													// int fin du tag d'ouverture	
    	
    	//Serial.println("init de la carte SD : OK");
    	XML = SD.open(XML_name);											// ouverture du fichier de config
    		if(XML){
    			//Serial.println(F("Ouverture du fichier avec succes"));
    				while (XML.available()) {
    					XML_recept += char(XML.read());						// stockage des données
    				}
    			tag_start = XML_recept.indexOf("<"+tag+">");				// detection de l'ouverture du tag
    			tag_end = XML_recept.indexOf("</"+tag+">");					// detection de la fermeture du tag
    			//Serial.println(XML_recept);
    			XML.close();												// on ferme le fichier
    		}
    	return XML_recept.substring((tag_start+tag.length()+2),tag_end); 	// on retourne le morceau entre les deux tag
    	
    }
    
    

    Publié par davi-domo | 4 juillet 2015, 16 h 18 min
  13. Bonjour,
    Tuto vraiment excellent avec des commentaires précis et simple à comprendre.
    Merci beaucoup

    Publié par Alphacatras | 1 mars 2018, 11 h 48 min

Skyduino devient Carnet du Maker

Le site Carnet du Maker remplace désormais le blog Skyduino pour tout ce qui touche à l'Arduino, l'informatique et au DIY.