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 courantJe 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 changementPour 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.