Auteur Sujet: Sauvegarde de l'état en EEPROM  (Lu 4140 fois)

Marc-Henri

  • Full Member
  • ***
  • Messages: 130
    • Voir le profil
    • Modélisme ferroviaire & électronique
Sauvegarde de l'état en EEPROM
« le: avril 24, 2015, 08:16:25 am »
Bonjour à tous,

Pour un projet de commandes d'itinéraires en gare, je me suis trouvé confronté à la question de la sauvegarde de l'état des voies afin de la récupérer à la prochaine mise sous tension. J'ai mené les réflexions suivantes:

Sauvegarde de l'état en EEPROM avant la coupure de courant
Je n'ai pas assez d'expérience pour mettre en oeuvre un système qui détecte une baisse de la tension d'alimentation et déclenche la sauvegarde juste avant que l'alimentation ne soit coupée. Il faut probablement quelques composants supplémentaires autour de l'alimentation.

Sauvegarde de l'état en EEPROM lors de chaque changement
Pour mettre en oeuvre une telle solution, il faut tenir compte du fait que le nombre de cycles d'écritures des cellules mémoires de l'EEPROM est limité à environ 100'000 cycles. Heureusement, le nombre de lectures est illimité.

J'ai trouvé une note d'application d'Atmel (Cf. les 2 pièces jointes reprises du site d'Atmel) qui propose d'étendre cette limite en écrivant les données et les indices pour y accéder dans plusieurs cellules de mémoire. J'ai adapté ce concept à mes besoins et le fonctionnement est le suivant:

  • Il y a 2 buffers circulaires, l'un contenant les données et l'autre contenant les indices pour accéder à ces données.
  • Le buffer des indices a le même nombre d'éléments que le buffer de données, mais ils peuvent être de plus petite taille que le buffer de données.
  • Le buffer des indices permet de déterminer si un élément du buffer de données est libre ou occupé.
  • Les 2 buffers sont parcourus en même temps.
  • Le buffer des indices est initialement rempli avec des zéros. Cela se fait à l'aide d'un fichier .eep téléchargé dans le microcontrôleur via Avrdude.
  • Après écriture dans l'élément courant dans le buffer de données, l'indice est écrit à la même position dans le second buffer. Les indices sont incrémentés.
  • Pour déterminer quel élément est libre, on parcourt le buffer d'indices. Si l'élément courant contient un zéro, il est libre. Si la différence entre l'indice de l'élément courant et l'indice du précédent élément est plus grande que 1, utiliser cet élément.
  • Pour déterminer quel élément contient la dernière sauvegarde, on parcourt aussi le buffer d'indices. Si la différence entre l'indice de l'élément courant et l'indice du suivant est plus grande que 1, l'indice courant pointe vers la dernière sauvegarde.

J'ai commencé par écrire un programme de test sur mon PC. Je communiquerai la version pour AVR avec les accès effectifs à l'EEPROM prochainement.


//
//   TestBufferEeprom.c
//
//   Programme de test d'un buffer en EEPROM pour augmenter
//   le nombre de cycles d'écritures.
//
//   2013.09.17   MHP   Création.
//
//   Directives de compilation: PC ou AVR.
//

#ifdef PC
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#endif


#define   LONG_BUFFER   4
#define   INDICE_MAX   (2*LONG_BUFFER)
#define   VIDE   0xFF

uint16_t   aEtats[LONG_BUFFER];
uint8_t   aIndices[LONG_BUFFER];

static void Init(void)
{
uint8_t   i;

   for(i = 0; i < LONG_BUFFER; i++)
      aIndices[i] = VIDE;
} // Init


static void AfficherBuffer(void)
{
#ifdef PC
uint8_t i;

   printf("Après écriture\n");
   for(i = 0; i < LONG_BUFFER; i++)
      printf("aIndices[%d] = %d\t\taEtats[%d] = %d\n", i, aIndices[i], i, aEtats[i]);

   printf("\n");
#endif
} // AfficherBuffer


static uint8_t bVide(void)
{
uint8_t i;

   for(i = 0; i < LONG_BUFFER; i++)
   {
      if(aIndices[i] != VIDE)
         return(0);
   } // for

   return(1);
} // bVide


static uint8_t nTrouverPositionLecture(void)
{
uint8_t   i;

   #ifdef PC
   if(bVide())
   {
      printf("Vide, lecture impossible\n");
      exit(-1);
   } // if
   #endif

   i = 0;
   while(((aIndices[i] + 1) % INDICE_MAX) == aIndices[i+1])
      i++;

   return(i);
} // nTrouverPositionLecture


static uint8_t nTrouverPositionEcriture(void)
{
uint8_t i;

   if(!bVide())
   {
      i = nTrouverPositionLecture();
      return((i + 1) % LONG_BUFFER);
   }
   else
      return(0);
} // nTrouverPositionEcriture


static void EcrireEEPROM(uint16_t nVal)
{
uint8_t   i, j;

   i = nTrouverPositionEcriture();
   #ifdef PC
   printf("Ecriture de: %d en position: %d\n", nVal, i);
   #endif
   aEtats[i] = nVal;

   if(!bVide())
   {
      j = nTrouverPositionLecture();
      aIndices[i] = (aIndices[j] + 1) % INDICE_MAX;
   }
   else
      aIndices[i] = 0;

   AfficherBuffer();
} // EcrireEEPROM


static uint16_t nLireEEPROM(void)
{
uint8_t i;

   i = nTrouverPositionLecture();
   #ifdef PC
   printf("Position: %d, lecture: %d\n\n", i, aEtats[i]);
   #endif
   return(aEtats[i]);
} // nLireEEPROM


void main(void)
{
#ifdef PC
uint16_t nVal;

   Init();

   EcrireEEPROM(10);
   EcrireEEPROM(23);
   EcrireEEPROM(34);
   nVal = nLireEEPROM();
   EcrireEEPROM(5);
   EcrireEEPROM(30);
   nVal = nLireEEPROM();
   EcrireEEPROM(1013);
   nVal = nLireEEPROM();
   EcrireEEPROM(1012);
   EcrireEEPROM(255);
   nVal = nLireEEPROM();
   EcrireEEPROM(51);
   EcrireEEPROM(53);
   EcrireEEPROM(55);
   nVal = nLireEEPROM();
   EcrireEEPROM(125);
   nVal = nLireEEPROM();
   EcrireEEPROM(355);
   nVal = nLireEEPROM();
#endif
} // main

Le résultat de son exécution est:


Ecriture de: 10 en position: 0
Après écriture
aIndices[0] = 0      aEtats[0] = 10
aIndices[1] = 255      aEtats[1] = 0
aIndices[2] = 255      aEtats[2] = 0
aIndices[3] = 255      aEtats[3] = 0

Ecriture de: 23 en position: 1
Après écriture
aIndices[0] = 0      aEtats[0] = 10
aIndices[1] = 1      aEtats[1] = 23
aIndices[2] = 255      aEtats[2] = 0
aIndices[3] = 255      aEtats[3] = 0

Ecriture de: 34 en position: 2
Après écriture
aIndices[0] = 0      aEtats[0] = 10
aIndices[1] = 1      aEtats[1] = 23
aIndices[2] = 2      aEtats[2] = 34
aIndices[3] = 255      aEtats[3] = 0

Position: 2, lecture: 34

Ecriture de: 5 en position: 3
Après écriture
aIndices[0] = 0      aEtats[0] = 10
aIndices[1] = 1      aEtats[1] = 23
aIndices[2] = 2      aEtats[2] = 34
aIndices[3] = 3      aEtats[3] = 5

Ecriture de: 30 en position: 0
Après écriture
aIndices[0] = 4      aEtats[0] = 30
aIndices[1] = 1      aEtats[1] = 23
aIndices[2] = 2      aEtats[2] = 34
aIndices[3] = 3      aEtats[3] = 5

Position: 0, lecture: 30

Ecriture de: 1013 en position: 1
Après écriture
aIndices[0] = 4      aEtats[0] = 30
aIndices[1] = 5      aEtats[1] = 1013
aIndices[2] = 2      aEtats[2] = 34
aIndices[3] = 3      aEtats[3] = 5

Position: 1, lecture: 1013

Ecriture de: 1012 en position: 2
Après écriture
aIndices[0] = 4      aEtats[0] = 30
aIndices[1] = 5      aEtats[1] = 1013
aIndices[2] = 6      aEtats[2] = 1012
aIndices[3] = 3      aEtats[3] = 5

Ecriture de: 255 en position: 3
Après écriture
aIndices[0] = 4      aEtats[0] = 30
aIndices[1] = 5      aEtats[1] = 1013
aIndices[2] = 6      aEtats[2] = 1012
aIndices[3] = 7      aEtats[3] = 255

Position: 3, lecture: 255

Ecriture de: 51 en position: 0
Après écriture
aIndices[0] = 0      aEtats[0] = 51
aIndices[1] = 5      aEtats[1] = 1013
aIndices[2] = 6      aEtats[2] = 1012
aIndices[3] = 7      aEtats[3] = 255

Ecriture de: 53 en position: 1
Après écriture
aIndices[0] = 0      aEtats[0] = 51
aIndices[1] = 1      aEtats[1] = 53
aIndices[2] = 6      aEtats[2] = 1012
aIndices[3] = 7      aEtats[3] = 255

Ecriture de: 55 en position: 2
Après écriture
aIndices[0] = 0      aEtats[0] = 51
aIndices[1] = 1      aEtats[1] = 53
aIndices[2] = 2      aEtats[2] = 55
aIndices[3] = 7      aEtats[3] = 255

Position: 2, lecture: 55

Ecriture de: 125 en position: 3
Après écriture
aIndices[0] = 0      aEtats[0] = 51
aIndices[1] = 1      aEtats[1] = 53
aIndices[2] = 2      aEtats[2] = 55
aIndices[3] = 3      aEtats[3] = 125

Position: 3, lecture: 125

Ecriture de: 355 en position: 0
Après écriture
aIndices[0] = 4      aEtats[0] = 355
aIndices[1] = 1      aEtats[1] = 53
aIndices[2] = 2      aEtats[2] = 55
aIndices[3] = 3      aEtats[3] = 125

Position: 0, lecture: 355

Comme on écrit la même information plusieurs fois dans l'EEPROM, il faut la compacter au maximum. J'ai fait une estimation de la durée possible d'utilisation avec un tel système dans le cas de la gestion des itinéraires en gare et des buffers à 20 positions.

  • Nb. changements d'états par heure: 120. C'est réaliste car les changements d'états dépendent, dans cette application, uniquement des actions de l'utilisateur.
  • Utilisation du réseau: 2 heures par semaine -> 240 changements d'états par semaine -> 240 * 52 = 12'480 changements d'états par année.
  • Nb. cycles max = nb. d'entrées dans le buffer * nb. cycles max EEPROM = 20 * 100'000 = 2'000'000.
  • Nb. d'années d'exploitation = 2'000'000 / 12'480 = 160 années !!!

Pour la petite histoire, la limite du nombre maximal de cycles d'écritures en EEPROM s'appliquent aussi aux mémoires flash "disques" SSD qui équipent les derniers modèles de PC portables. Les techniques employées pour répartir les écritures sur plusieurs cellules sont certainement similaires.
« Modifié: avril 27, 2015, 09:11:24 am par savignyexpress »

Thierry

  • Global Moderator
  • Hero Member
  • *****
  • Messages: 500
    • Voir le profil
Re : Sauvegarde de l'état en EEPROM
« Réponse #1 le: avril 24, 2015, 10:18:36 am »
Je suis en train de mettre la main finale à une librairie EEPROMex dont j'ai besoin pour un projet plus complexe. Cette libraire très simple ajoute quelques fonctionnalités à la classe EEPROM basique livrée avec l'IDE. A côté des read et write de la base, j'ai ajouté un readAnything et writeAnything pour manipuler autre chose que des bytes. Ce sont des fonctions trouvées sur le net qui seront créditées de leur véritable auteur comme il se doit. J'y ajoute une classe de mon cru permettant de créer une liste hiérarchique d'éléments (des parents et des enfants) gérant la place disponible...
Il serait sans doute très utile d'y ajouter, au moins optionnellement, cette gestion de buffer circulaire qui me parait bien adaptée à nos besoins.
Dans ce que j'ai pu trouver sur le net, le nombre de 100000 écritures (http://hackaday.com/2011/05/16/destroying-an-arduinos-eeprom/) a été testé en grandeur réelle, et il se trouve que le testeur a en fait pu faire plus d'un million d'écritures avant de rencontrer un problème ! Je trouve que ça relativise pas mal le besoin, mais ça ne le supprime pas.

Marc-Henri

  • Full Member
  • ***
  • Messages: 130
    • Voir le profil
    • Modélisme ferroviaire & électronique
Re : Sauvegarde de l'état en EEPROM
« Réponse #2 le: avril 25, 2015, 04:33:17 pm »
Bonjour à tous,

Voici les 2 fichiers source en C constituant le module de sauvegarde / restauration de l'état courant de la gare du réseau de Jappy (membre du forum du N).

Dans ce module, la taille d'un élément de données est un entier non signé 16 bits déclaré par:
static uint16_t EEMEM aDonnees[NB_CASES] =
{
ZONES_BLOQUEES, ZONES_BLOQUEES, ...
}

Cette déclaration permettra de créer le fichier .eep contenant l'état initial de l'EEPROM. Pour adapter à une autre taille de données, remplacer uint16_t par le type approprié, par exemple une structure. Dans ce cas, les fonctions eeprom_read_word et eeprom_write_word seront remplacées par eeprom_read_block respectivement eeprom_write_block.

Le buffer des indices est déclaré comme suit:
static uint8_t EEMEM aStatus[NB_CASES] =
{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};

Cette déclaration aura pour effet de placer l'état initial du buffer d'indices dans le fichier .eep.

Les 3 fonctions accédant effectivement l'EEPROM et qu'il faudrait exporter dans une librairie d'usage général, sont:
  • nRechercherPositionCourante: appelle la fonction nLireStatus
  • EcrireDonnees
  • EcrireStatus
  • LireDonnees

Les fonctions SauverEtatCourant et LireEtatCourant montrent comment utiliser ces 3 fonctions. Toutes les autres fonctions du module sont spécifiques à l'application, elles traitent le codage / décodage de l'état pour le sauver sur 16 bits.

Dernière précision, il y a des directives de compilation AVR et PC qui délimitent le code spécifique à l'AVR et le code utilisé pour la mise au point sur PC.

Bonne fin de semaine & meilleures salutations.

Marc-Henri
« Modifié: avril 25, 2015, 04:37:36 pm par savignyexpress »

Jean-Luc

  • Global Moderator
  • Hero Member
  • *****
  • Messages: 1438
    • Voir le profil
Re : Sauvegarde de l'état en EEPROM
« Réponse #3 le: avril 26, 2015, 09:43:37 am »
Bonjour,

La durée de vie de l'EEPROM reste un vrai problème. Si ele doit mémoriser tous les changements d'état si le réseau est piloté par ordinateur, ça fait vraiment beaucoup d'écritures. Les données constructeur sur la durée de vie sont assez conservatives et ça ne m'étonne pas que l'on puisse arriver à un million. Mais rien ne prouve que ca sera systématiquement le cas.

SavignyExpress m'avait déjà parlé de cette solution et l'idée d'en faire une bibliothèque permettant son exploitation de la manière la plus transparente possible est séduisante. 
Cordialement

Thierry

  • Global Moderator
  • Hero Member
  • *****
  • Messages: 500
    • Voir le profil
Re : Sauvegarde de l'état en EEPROM
« Réponse #4 le: avril 27, 2015, 06:14:33 pm »
J'ai codé 'la chose' dans ma librairie EEPROMex. Voir l'exemple circularbuffer dans la pièce jointe.
Je dois juste m'assurer que je peux utiliser la librairie dans mon autre librairie DcDccControler, et si tout va bien je commencerai à rédiger un article sur EEPROMex.
Viendra ensuite LcdUI qui permet de réaliser -assez- facilement une interface utilisateur un peu évoluée (Splash Screen, écrans de saisie entier, chaine, booléen, choix multiples, enchaînements d'écrans et messages d'interruption...).
Et puis enfin DDC, qui exploite ces deux librairies !

Marc-Henri

  • Full Member
  • ***
  • Messages: 130
    • Voir le profil
    • Modélisme ferroviaire & électronique
Re : Sauvegarde de l'état en EEPROM
« Réponse #5 le: avril 28, 2015, 12:22:57 pm »
Merci beaucoup Thierry pour ce développement !

J'ai cependant une question. Tu as préféré appeler les fonctions eeprom_read_byte et eeprom_write_byte dans des boucles for plutôt que d'utiliser les fonctions eeprom_read_block et eeprom_write_block. As-tu une raison pour cela ?

Meilleures salutations.

Marc-Henri

Thierry

  • Global Moderator
  • Hero Member
  • *****
  • Messages: 500
    • Voir le profil
Re : Sauvegarde de l'état en EEPROM
« Réponse #6 le: avril 28, 2015, 02:07:34 pm »
Il y a une bonne et une mauvaise raison !
La bonne, c'est parce que ma fonction write vérifie d'abord avec un eeprom_read_byte que le contenu n'est pas déjà celui souhaité, et n'écrit avec eeprom_write_byte que si c'est nécessaire. Mais je ne suis pas sûr de ne pas avoir lu quelque part que la puce d'eeprom ne s'en occupait pas déjà...
La mauvaise, c'est que j'ai recopié puis fortement amendé une fonction trouvée sur le net et que je n'ai pas voulu changer ça... Mais c'est vrai que j'ai découvert avec surprise qu'il y avait bien plus que ce que propose le simple EEPROM.h .

Marc-Henri

  • Full Member
  • ***
  • Messages: 130
    • Voir le profil
    • Modélisme ferroviaire & électronique
Re : Sauvegarde de l'état en EEPROM
« Réponse #7 le: juin 15, 2018, 10:37:47 am »
Bonjour à tous,

Je souhaite utiliser l'EEPROM sur un Arduino. Je souhaite savoir comment charger des valeurs initiales en EEPROM lors de l'envoi du script au processeur.

Je sais faire cela via un fichier .eep et avrdude sans passer par l'IDE Arduino (cf mes posts précédents ci-dessus), mais je n'ai rien trouvé sur le web décrivant comment le faire en Arduino. Un processeur neuf a son EEPROM remplie par des 0xFF, mais il faudrait pouvoir garantir cette valeur.

Merci de vos conseils.
Belle fin de semaine et meilleures salutations.

Marc-Henri

msport

  • Hero Member
  • *****
  • Messages: 661
  • HO avec DCC++ en DIY Réseaux très éphémères
    • Voir le profil
Re : Sauvegarde de l'état en EEPROM
« Réponse #8 le: juin 15, 2018, 11:13:26 am »
Il y a un sketch pour mettre des 0 dans l'EEPROM, j'imagine qu'on peut les remplacer par des FF.
https://www.arduino.cc/en/Tutorial/EEPROMClear

et dans ce sketch, écrit un peu à la  hache, il y a la sauvegarde et la récupération en EEPROM des adresses des locos.
http://trainelectronics.com/DCC_Arduino/DCC++/Throttle/

En fait ici, les valeurs par défaut du sketch ne sont pas écrites immédiatement mais lors des modifications.
Cordialement

Marc-Henri

  • Full Member
  • ***
  • Messages: 130
    • Voir le profil
    • Modélisme ferroviaire & électronique
Re : Sauvegarde de l'état en EEPROM
« Réponse #9 le: juin 19, 2018, 01:09:27 pm »
Merci msport pour ta réponse.

En cherchant sur le web sur ce sujet, j'ai trouvé une solution qui me paraît très intéressante.

  • On convient d'une adresse prédéfinie en EEPROM, par exemple l'adresse 0.
  • Le programme lit la variable à cette adresse. Choisir par exemple une taille de 16 bits.
  • Si la variable égale une valeur prédifinie, appelée signature, l'EEPROM contient des données pertinentes pour le programme.
  • Si la variable n'est pas égale à la signature, l'EEPROM ne contient pas de données pertinentes pour le programme. Le programme peut alors initialiser l'EEPROM avec des données, sans oublier de mettre à jour la signature.
  • Lorsqu'une nouvelle version du programme s'attend à une organisation différente des données en EEPROM, un changement de la signature aura pour effet de forcer l'initialisation de l'EEPROM avec des données selon la nouvelle organisation.

Pour la signature, il faut essayer prendre une valeur autre que 0x00 et 0xFF. Le risque que la valeur à l'adresse 0 corresponde à la signature alors que l'EEPROM n'a pas été initialisée ou contienne d'anciennes données est très faible.

Thierry

  • Global Moderator
  • Hero Member
  • *****
  • Messages: 500
    • Voir le profil
Re : Sauvegarde de l'état en EEPROM
« Réponse #10 le: juin 19, 2018, 03:34:07 pm »
C'est la méthode utilisée dans DcDccNanoContoller ou le soft du Locoduinodrome en cours de conception...