LOCODUINO
Parlons Arduino => Bibliothèques => Discussion démarrée par: savignyexpress 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.
-
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/ (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.
-
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
-
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.
-
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 !
-
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
-
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 .
-
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
-
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.
-
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.
-
C'est la méthode utilisée dans DcDccNanoContoller ou le soft du Locoduinodrome en cours de conception...