Arduino: sauvegarde de données en EEPROM en cas de coupure d'alimentation

Un petit tuto pour les membre qui se posent le problème de sauvegarder des données en catastrophe lorsque l'alimentation disparaît :

Chiffres et tests à l'appui.

Bonnes sauvegardes.

Un petit paragraphe supplémentaire :

7. Améliorons avec un CRC

Comment écrire des données dans l'EEPROM en ajoutant un CRC pour assurer l'intégrité des données.

Et bien entendu comment relire ces données en vérifiant le CRC.

Encore un :

Ça se corse : RitonDuino: Arduino: sauvegarde de données en EEPROM en cas de coupure d'alimentation

Comment gérer les différentes versions des données à sauvegarder dans l'EEPROM.

Vraiment ingénieux ce système de sauvegarde, parait tellement simple à mettre en oeuvre et surtout merci pour le détail, le choix porté sur les composants et les calculs.

La lecture de votre code m'a fait pensé a un problème concernant le fait de pouvoir faire un auto off d'une carte arduino (il y a quelques temps, j'ai fais des recherches là dessus ) car techniquement c'est un souci bien que l'on puisse croire que cela fonctionnera bien. Je me dis qu'avec cette technique du condensateur, cela pourrait bien faciliter les choses.

auto off d'une carte arduino

Pourquoi pas ?
Adapter simplement la capacité à la longueur du traitement à réaliser.

Salut - merci pour ce partage. un vrai besoin!

Petites améliorations éventuelles:

  • dans le texte il manque un bout de phrase

Dans la fonction powerLoss le nombre magique est mis à zéro au départ. Cela permet de s'assurer que la fonction aura bien le temps d'écrire l'intégralité des données plus le nombre magique.
Si celui-ci est relu et qu'il vaut zéro, cela voudra dire que ... suspense intenable :)...

  • pour garantir la longueur des données et figer les types, ce serait pas mal de ne pas utiliser un #define pour MAGIC mais un const uint32_t et de mettre UL pour la lisibilité.

  • ce serait pas mal de définir la structure eeprom_data avec __attribute__ ((packed)) partout (ça manque au tout début) et expliquer à quoi ça sert de mettre ce mot clé (très utile en gestion de version par exemple). Il serait bon d'ailleurs de prévoir aussi __aligned__ car sur des architectures 32 bits ça peut jouer des tours quand on étend la structure de début. c'est aussi non standard, une extension des compilo que GCC supporte

  • tel que vous l'utilisez, il me semble que le mot clé struct n'est plus obligatoire, le typedef est implicite dans ce namespace doncstruct eeprom_data eepromData;peut s'écrireeeprom_data eepromData;(idem dans les appels à offsetof()

  • de même struct tm t = {0};peut s'écriretm t = {0};et attention cette notation n'est pas conforme à la dernière norme et le compilateur peut se plaindre de

[color=orange]
warning: missing initializer for member 'tm::tm_min' [-Wmissing-field-initializers]
warning: missing initializer for member 'tm::tm_hour' [-Wmissing-field-initializers]
warning: missing initializer for member 'tm::tm_mday' [-Wmissing-field-initializers]
warning: missing initializer for member 'tm::tm_wday' [-Wmissing-field-initializers]
warning: missing initializer for member 'tm::tm_mon' [-Wmissing-field-initializers]
warning: missing initializer for member 'tm::tm_year' [-Wmissing-field-initializers]
warning: missing initializer for member 'tm::tm_yday' [-Wmissing-field-initializers]
warning: missing initializer for member 'tm::tm_isdst' [-Wmissing-field-initializers]
   tm t = {0};[/color]

La notation tm t = {};devrait forcer les éléments à 0 sans warning.

  • dans le texte il manque un bout de phrase

Corrigé.

  • ce serait pas mal de définir la structure eeprom_data avec attribute ((packed)) partout (ça manque au tout début) et expliquer à quoi ça sert de mettre ce mot clé

Idem.

  • tel que vous l'utilisez, il me semble que le mot clé struct n'est plus obligatoire, le typedef est implicite dans ce namespace donc

Certes mais j'ai toujours évité les typedefs pour les structures, un vieil héritage d'OpenBSD !
J'ai gardé cette habitude, qui a l'avantage de passer partout.

A noter :
Imaginons qu'une structure eeprom_data soit déclarée dans un fichier eeprom_data.h.

Dans un autre fichier header machin.h, un prototype de fonction :

int write_data(struct eeprom_data *data);

Au lieu d'inclure eeprom_data.h il est possible d'écrire :

struct eeprom_data;

int write_data(struct eeprom_data *data);

A partir du moment où data est un pointeur, le compilateur n'a pas besoin de connaître la taille de la structure.
Cette manière de faire n'est pas possible avec un typedef, sauf s'il est implicite bien sûr.

struct tm t = {0};

Un copier / coller malencontreux sans doute.

J'ai préféré ajouter ceci, plus explicite :

  memset(&t, 0, sizeof(struct tm));

De toutes façons le éléments sont tous initialisés plus loin, à part tm_wday et tm_yday.

OK - et oui étant donné que vous initialisez la variable tm plus loin, le memset() n'est pas nécessaire
comme dit plus haut, si vous voulez forcer la mise à 0 --> struct tm t = {};devrait le faire.

Tous les éléments ne sont pas initialisés explicitement (tm_wday et tm_yday ne le sont pas).

D’après ce fil, peu de choses sont définies en C standard au sujet les initialisations partielles.

C++ apporterait quelques petits + :

  struct tm t = {0};
  struct tm t = {};       // extension GNU semble t-il ?
  // seraient équivalents à :
  struct tm t = {0, 0, 0, 0, 0, 0, 0, 0, 0};

Mais je travaille rarement en C++, plutôt en C (pas forcément sur ARDUINO), et dans ce cas, je préfère initialiser explicitement avec un memset.
De plus je trouve que cela montre clairement que quelque chose doit être fait (et que c’est fait).

Je trouve le compilateur AVR-GCC peu loquace à propos des variables locales non initialisées (et pas que). Il y a certainement moyen d’ajouter des options.
Un compilateur ARM serait beaucoup plus bavard par défaut.

En résumé je préfère la prudence, plutôt que de me fier à des automatismes particuliers à C++ ou à des extensions d’un compilateur en particulier.

c’est sûr que   struct tm t = {0, 0, 0, 0, 0, 0, 0, 0, 0};est le plus clean

Pas vraiment.
Avec un appel memset je n'ai pas besoin de connaître le nombre d'éléments ni leur type.
Et je suis à l'abri d'une évolution de la structure.

Pour le memset() mon commentaire concernait plus le fait que vous n’avez pas besoin d’initialiser t puisque c’est fait par la fonction ensuite

Bien sûr, si l'on ne sert pas des membres tm_wday et tm_yday c'est inutile.

Et si vous vous en servez et qu’ils sont à 0 c’est pas plus juste statistiquement qu’une autre valeur.. le plus simple c’est qu’une fois la structure initialisée, vous appelez [url=http://www.cplusplus.com/reference/ctime/mktime/]mktime()[/url] qui se chargera de mettre à jour les 2 variables manquantes voire de corriger les valeurs « out of range »)