[RESOLU] sauvegarde d'une structure en EEPROM

Bonjour à tous,

J’identifie mes ruches avec un numéro ( 1 a …) et 2 tags RFID (1 en 125K et 1 en 13.56M), je réalise un lecteur RFID qui me donne le numéro de ruche après avoir lu l’ un des deux tags
Pour l’instant je gère ces données dans une structure initialisée dans mon code et la lecture d’un tag me retrouve bien le numéro de ruche.

j’ai besoin d’un peu d’aide pour comprendre comment sauvegarder et restituer des données en EEPROM (je précise c’est ma première expérience avec une structure et l’EEPROM …)

J’ai consulté plein d’ex de structure en EEPROM et commencé a intégrer des morceaux de code (certains se reconnaitrons …)

#include <EEPROM.h>
const uint16_t motClef = 0xABCD;
const uint16_t adresseMotClef = 0x00;
// __attribute__ ((packed)) permet de forcer la conservation de l'ordre des champs
// c'est utile pour éviter que le compilateur mette le bazar quand on en rajoute au fil du temps
struct __attribute__ ((packed) identiteRuche {
  byte numRuche;
  unsigned long tag1;
  unsigned long tag2;
};

identiteRuche mesRuches[] = {
  {1, 0x8807C204, 0xBB9A76},
  {2, 0x8803C304, 0xBB50AE},
  {3, 0x88F64904, 0xBB99BE},
  {4, 0x88EFE604, 0xBB1818},
  {5, 0x880EFA04, 0xBB7EE1},
  {6, 0x8804A604, 0xBB514D},
  {11, 0x8801AE04, 0x0},
  {12, 0x8809F304, 0xBB3518},
  {13, 0x88061E04, 0xBB6530},
  {14, 0x88FB1F04, 0xBB8CE2},
  {15, 0x8806F804, 0xBB78A7},
  {16, 0x8806EE04, 0xBB610B},
  {51, 0x88A47604, 0xBB3ADF},
  {52, 0x88A26804, 0xBB4117},
  {53, 0x88A85004, 0xBB5349},
  {54, 0x88A2DA04, 0xBB9303},
  {55, 0x88A2DF04, 0xBB80BB},
  {56, 0x88A41504, 0xBB41E1}
};

const size_t nbRuches = sizeof(mesRuches) / sizeof(mesRuches[0]);
const uint16_t adresseDesRuches = adresseMotClef + sizeof(motClef);

j’imagine que si je fais un “EEPROM.put (adresseDesRuches, mesRuches)” l’ensemble des données de la structure sera stocké à partir de l’adresse 02
mais je ne comprend pas comment par la suite un “EEPROM.get (adresseDesRuches, mesRuches)” va savoir ou se trouve la dernière valeur ?

et puis forcement le nombre de ruche va augmenter, j’imagine faire du code pour saisir de nouvelles valeurs dans ma structure qu’il faudra rajouter à la sauvegarde …

Il n'y a pas qu'Arduino pour écrire dans l'eeprom.
En dehors de la datasheet il y a les macros Atmel de l'avr-libc si tu utilise un micro avr.

Le site de l'avr-libc n'est pas très limpide et je cherche à chaque fois.
Petite progression dans le site :
http://www.nongnu.org/avr-libc/
puis :
http://www.nongnu.org/avr-libc/user-manual/modules.html
et enfin :
http://www.nongnu.org/avr-libc/user-manual/group__avr__eeprom.html
Je ne sais pas si tu va y trouver ton bonheur, il y a peut être à creuser du coté des "blocks" et des pointeurs.

Effectivement, j’avais déjà utilisé EEPROM.get avec une structure, sans me poser plus de questions.

hardware/arduino/avr/libraries/EEPROM/src/EEPROM.h

get est une fonction template :

    template< typename T > T &get( int idx, T &t ){
        EEPtr e = idx;
        uint8_t *ptr = (uint8_t*) &t;
        for( int count = sizeof(T) ; count ; --count, ++e )  *ptr++ = *e;
        return t;
    }

@+

Vous pouvez jeter un coup d’œil à mon code de projet de mesure de Température

En gros j’utilise 4 octets en début d’EEPROM pour marquer le fait que la mémoire a été initialisée (ce que vous avez fait avec Vos 2 octets motClef 0xABCD) et ensuite je mets la structure directement en binaire

Au lancement du programme vous regardez si les 4 octets magiques sont là, si oui vous pouvez lire la mémoire, sinon vous initialisez votre structure avec des valeurs par défaut et allez écrire les 2/4 octets motClef et la structure en mémoire pour la prochaine fois

On peut aussi - si vous faites des programmes un peu complexes qui évoluent - mettre après les 4 octets magiques un Numéro de version qui permettra de savoir dans quelle version de structure lire les octets (qui se trouvent après). Ça permet - quand vous passez à de nouvelles versions de gérer la migration (on lit dans l’ancienne structure que vous avez pris soin de conserver dans le programme, vous migrez les données vers une nouveau format de structure et c’est celle là que vous écrivez après avoir changer le N° de version au début de l’EEPROM)

Enfin si la structure que vous devez conserver est un tableau qui est amener à grandir, alors vous allez simplement en “coller plein les unes après les autres” et chacune représentant des paramètres d’une instance. Vous stockez dans la première structure le nombre d’instances N et ensuite vous lisez N structures en EEPROM avec une boucle.

→ Dans ce cas vous avez 2 types de structures - celle qui décrit l’organisation de l’EEPROM, sans doute {N° de version, nombre de structures} et une autre structure Ruche {data1 data2 data3} qui est ce que vous voulez vraiment sauver pour chaque ruche. (vous pouvez combiner cela avec la technique des versions). La taille d’une structure Ruche vous dira de combien déplacer le curseur en mémoire EEPROM pour aller chercher la suivante

eeprom.png
(ÉDIT: les entiers tenant sur plus d’un octet étant stockés en mode little endian, l’ordre des octets sera physiquement inversé donc les 4 premiers octets qui contiennent 0xDEADBEEF seront dans l’ordre EF BE AD DE)

(EDIT2: d’ailleurs je me suis trompé dans l’image j’ai écrit DEADEEFF…)

Une autre possibilité si vous ne voulez pas avoir à gérer chaque entrée individuellement est de dimensionner la mémoire comme il faut au début avec un gros tableau statique dans la structure des paramètres (et même reserver un peu de place dans la structure de description de ruches pour d’autres versions comme ça il n’y aura pas besoin de réorganiser la mémoire)

const uint32_t clef = 0xDEADBEEF; // 4 octets

struct __attribute__ ((packed)) identiteRuche_t {
  byte numRuche;
  uint32_t tag1;
  uint32_t tag2;
  uint32_t pourPlusTard[2]; // 8 octets qu'on n'utilise pas pour le moment
}; // ça fait 32 octets. de réservés

const size_t maxRuches = 30; // si on a 1Ko d'EEPROM, 30 x 32 = 960 octets - ça rentre

struct __attribute__ ((packed)) parametres_t {
  uint8_t version; // 1 octet (255 versions possibles)
  uint8_t nbRuches; // 1 octet 255 ruches max
  identiteRuche_t mesRuches[maxRuches];
} ;

parametres_t param = {
  1,
  3,
  {
    {1, 0x8807C204, 0xBB9A76, {0, 0}},
    {2, 0x8803C304, 0xBB50AE, {0, 0}},
    {3, 0x88F64904, 0xBB99BE, {0, 0}},
    {0, 0x0, 0x0, {0, 0}}
  }
};

mais bien sûr ça vous mange aussi pas mal de SRAM pour conserver tout cela…

Si vous avez des questions n’hésitez pas

Je crois que le problème principal était lié au départ à la taille du tableau et à sa relecture.

Je suppose que la liste mesRuches dans le code est une liste initiale pour un code de test. Cette liste ne sera pas forcément présente dans le code définitif.
Les tags seront enregistrés dans l’EEPROM au fur et à mesure de l’ajout de nouvelles ruches.
Il faut à mon avis, en plus du nombre magique, conserver également le nombre de ruches enregistrées.

Donc dans l’ordre:

  • nombre magique
  • nombre de ruches
  • liste de ruches
uint16_t magic, population;
const uint16_t motClef = 0xABCD;
const uint16_t adresseMotClef = 0x00;
const uint16_t adresseNombre = adresseMotClef + sizeof(magic);
const uint16_t adresseDesRuches = adresseNombre + sizeof(population);

void setup()
{
  Serial.begin(115200);
  identiteRuche ruches[30];
  
  EEPROM.get (adresseMotClef, magic);
  Serial.println(magic);
  if (magic == motClef) {
    EEPROM.get (adresseNombre, population);
    EEPROM.get (adresseDesRuches, ruches);
    Serial.println(population);

    for (int i = 0 ; i < population ; i++) {
      Serial.print(ruches[i].numRuche);
      Serial.print(": ");
      Serial.print(ruches[i].tag1);
      Serial.print(" : ");
      Serial.println(ruches[i].tag2);
    }
  }
  else {
    // donnés invalides : on enregistre le magic + le nombre de ruches : 0
    EEPROM.put (adresseMotClef, motClef);
    EEPROM.put (adresseNombre, 0);
    // mais on peut également enregistrer une liste par défaut
    // EEPROM.put (adresseNombre, nbRuches);
    // EEPROM.put (adresseDesRuches, mesRuches);
  }
}

Dans cet exemple, le nombre de ruches maximal est fixé à 30. A toi de dimensionner à ta mesure (ou démesure).
EEPROM.get lira donc 30 structures (la taille de la variable nommée ruches).
Si le nombre de ruches est réellement égal à 18, il y aura dans la variable ruches 18 structures valides et 12 invalides.
Ce n’est pas grave, a toi d’exploiter correctement la variable nommée population.

population pourra augmenter avec l’ajout d’une nouvelle ruche, et donc sa valeur en EEPROM devra être modifiée.
Si tu envisages d’avoir la possibilité de diminuer le nombre de ruches (une ruche peut être cédée), il faudra remplacer par exemple le N° de ruche par ZERO et prendre en compte dans le code qu’une ruche N°0 est inexistante.
Mais tu peux également supprimer et “compacter”.

@+

La suggestion de J-M-L de mettre toutes ces variables dans une structure parametres_t est aussi une bonne chose.
Cela évite les calculs de taille du genre adresseTruc = adresseMachin + sizeof(machin)

@+

En fait je viens de m'apercevoir que la structure de J-M-L contient également le nombre de ruches.
Donc nous nous rejoignons ...
@+

hbachetti:
En fait je viens de m'apercevoir que la structure de J-M-L contient également le nombre de ruches.
Donc nous nous rejoignons ...
@+

les grands esprits... :slight_smile:

Bonjour à tous,
d'abord un ÉNORME merci aux grands esprits :slight_smile:

j'avais dit ne pas avoir d'expérience en structure et EEPROM mais dans mon premier projet de ruche pédagogique j'avais traité "à la mano" la sauvegarde des paramètres journaliers dans la RAM de ma RTC pour qu'en cas de coupure secteur ne pas perdre les max, min, entrées, sorties, ... et je pensais avoir trouvé le truc génial pour m'éviter tout ce travail ...

donc vos propos me parlent bien (ça me rappelle mes premières bidouilles en assembleur sur le 8080 pour vous situer mon age ...) et j'ai décidé de griller quelques cartouches de mon EEPROM :
la sauvegarde de la structure marche impeccable en une seule ligne :sunglasses:
la restauration dans une autre structure "taillée" en fixe fonctionne bien
alors j'ai pensé "naïvement" qu'en sauvegardant le nombre de ruches je pourrais déclarer ma nouvelle structure avec cette taille ... et bien non le compilo n'en veut pas

voici la restit console de mon essai

dump EEPROM : CD AB 12 00 01 04 C2 07 88 76 9A BB 00 02 04 C3 03 88 AE 50 BB 00 03 04 49 F6 88 BE 99 BB 
le mot clef est bon  : ABCD
a l'adresse : 2 il y a : 18 Ruches
qui commencent a l'adresse : 4
1	8807C204	BB9A76
2	8803C304	BB50AE
3	88F64904	BB99BE
4	88EFE604	BB1818
5	880EFA04	BB7EE1
6	8804A604	BB514D
11	8801AE04	0
12	8809F304	BB3518
13	88061E04	BB6530
14	88FB1F04	BB8CE2
15	8806F804	BB78A7
16	8806EE04	BB610B
51	88A47604	BB3ADF
52	88A26804	BB4117
53	88A85004	BB5349
54	88A2DA04	BB9303
55	88A2DF04	BB80BB
56	88A41504	BB41E1

encore MERCI à vous, j'ai l'impression que le neurone Arduino de mon avatar à encore grossi ....

PS pour JML :
oui c'est beaucoup à partir de tes posts que je puise mon apprentissage
j'ai mis le dump de l'EEPROM car ton image est hyper didactique mais il y a un petit bug :wink:

déclarer ma nouvelle structure avec cette taille

Oui, pourquoi pas ?
Mais il faudrait le faire avec une allocation dynamique (malloc). Cette structure serait allouée dans le setup().
Elle occuperait moins de place qu'un tableau surdimensionné de taille fixe en global dans un premier temps, grossirait ensuite avec l'ajout de nouvelles entrées.
Ensuite se pose le problème de l'ajout. Il faut ré-allouer (realloc) le tableau avec une case en plus, et là je ne sais pas du tout comment cela va se comporter. Avec un seul bloc alloué, il est possible que cela passe.

Je pense qu'un tableau fixe de 30 ruches est une bonne solution, car je ne connais aucun apiculteur capable de s'occuper de 30 ruches, à moins que vous soyez plusieurs ...

@+

rjnc38:
j'ai mis le dump de l'EEPROM car ton image est hyper didactique mais il y a un petit bug :wink:

Oui j'avais fait exprès d'écrire sans tenir compte du "little endian" pour ne pas rajouter à la confusion :slight_smile:

C’etait Une vue conceptuelle avec un groupe de couleur pour montrer ou les valeurs étaient stockées