Sauvegarder sur la mémoire Flash SPIFFS d'un ESP32

L'ESP est un outil formidable pour creer des capteurs divers comme sa consommation est limitée, et qu'il propose une connection WIFI et des pins similaires a arduino. On trouve couramment sur internet des capteurs fait a base d'ESP qui vont prendre une mesure et l'envoyer sur un serveur distant.

Mais que se passe t'il si la connection Wifi ne marche pas? Que se passe t'il si le serveur distant est hors ligne? Et si jamais on veux se passer de serveur distant? L'idée est donc de sauvegarder les données sur l'ESP, mais ce n'est pas si simple.

A mémoire d'ESP

Tout d'abord faisons un petit tour d'horizon des mémoires disponibles sur ce micro-controleur.

L'ESP dispose tout d'abord d'une mémoire de type RAM. Cette memoire est extremement rapide, mais aussi extremement volatile, c'est a dire que les données stockées dessus on une durée de vie trés courte. C'est ici que sont stockées les variables de notre programme par exemple. Il n'es pas possible de l'utiliser pour stocker sur une longue durée car elle est vidée à chaque reboot, en cas de plantage, et meme en cas de mise en veille (deepsleep) de la puce.

L'ESP dispose ausi d'une mémoire RAM type RTC. C'est a dire un espace mémoire qui est persistant au reboot, mais qui sera toujours perdu en cas de coupure de courant. Habituellement cet espace de stockage sert a conserver l'horloge interne entre les deepsleep, mais sur ESP on peux y stocker des données librement pendant un court laps de temps (quelques minutes). La librairie NodeMCU rtcfifo permet d'acceder à cet espace mémoire. Attention, en cas de plantage et/ou de coupure totale, cette mémoire est perdue, de plus la librairie rtcfifo ajoute d'autres limitations.

Enfin, la puce dispose aussi d'un module mémoire flash. Cet espace de stockage est persistant, meme en cas de coupure de courant. C'est d'ailleurs ici que l'on charge notre code source qui servwira ensuite à l'execution. En centrepartie, ce type de mémoire est trés long à acceder à la fois en lecture et en écriture. Autre bémol important, cette mémoire s'use à l'écriture (100.000 cycles environ).

Implementation naive

En NodeMCU, une ecriture de fichier se fait avec le module file:

Le code minimal pour lire un fichier est le suivant:

   if file.open("init.lua") then
      print(file.read())
      file.close()
   end

Et pour écrire:

    -- open 'init.lua' in 'a+' mode
    fd = file.open("init.lua", "a+")
    if fd then
        -- write 'foo bar' to the end of the file
        fd:write('foo bar')
        fd:close()
    end

A chaque fois, on à le meme schéma: d'abord on ouvre le fichier avec open, ensuite on effectue les opérations (read/write) dessus, et enfin on ferme (close) ce fichier. Ce code est plutot simple car on ajoute du contenu à la fin (mode a+). Voir ici pour une liste des autres modes.

La fermeture est trés importante, car les operations d'écriture ne seront effectives (cad vraiment inscrites dans la flash) qu'aprés cette étape. De plus, nodeMCU est limité a un seul et unique fichier ouvert en simultané. Il est donc primordial de bien penser à fermer un fichier le plus tot possible aprés son ouverture !

Et voila, il ne reste plus qu'a écrire nos résultats de sonde dans ce fichier. Mais cette implementation naive risque de réduire la durée de vie.

Considérations sur la durée de vie

Comme énoncé plus haut, la mémoire flash est limitée dans le nombre de cycles d'écritures qu'elle peux supporter. En general la specification annonce 100.000 cycles mais certains modules chinois supportent beaucoup moins. Chaque type de mémoire flash est soumise aux mémes limitations, que l'on parle de la mémoire de l'ESP, mais aussi de celle des cartes SD ou encore des clés USB.

Cette usure s'annonce par bloc, c'est a dire que meme si un bloc est usé, il est possible que les blocs adjacents fonctionnent parfaitement. Par contre, NodeMCU n'est pas capable de détecter et de gérer un bloc usé et plantera donc lors de la lecture de ce bloc. Pour information, node MCU gére des blocs de 4096bytes.

Il ne faut donc jamais réécrire encore et toujours à la meme position. Si on ouvre toujours le meme fichier et que l'on remplace le contenu a chaque fois, on fait travailler un seul et unique bloc mémoire et on aura une flash corrompue alors que 99% des blocs seront encore bons.

Pour repousser cette échéance, il suffit donc d'utiliser tous les blocs de la flash un par un. Pour ça il suffit, par exemple chaque jour, d'ouvrir un nouveau fichier avec un nom différent. Au lieu de remplacer le contenu, on écrira la nouvelle valeur à la fin sans effacer les précedentes. En procédant ainsi on force l'usure à se répartir sur l'ensemble de la surface de la RAM.

Prennons l'exemple d'une sonde qui écrit une valeur par minute, et une qui écrit une valeur par seconde:

  • Par seconde, sans répartition d'usure: 1w/s = 3600 w/h = 86400 w/d. c'est à dire que le bloc concerné sera mort en un peu plus de 24heures !
  • Toutes les 10 secondes, sans répartition d'usure: 6w/min = 8 640‬ w/d. A ce rythme le bloc survit un peu plus de 11 jours.
  • Toutes les minutes, sans répartition d'usure: 1w/min = ‭1 440‬ w/d. On arrive a environ 69 jours. C'est limite mais deja plus raisonnable.

Enfin, prennons un ESP8266 avec 4Mbytes de Flash, soit 4,194,304 bytes. On a donc environ 1 024 secteurs de 4096 bytes. Admettons que seulement 1/4 de ces secteurs peut etre utilisé pour stocker nos valeurs, on a donc 256 secteurs, soit 1 048 576 bytes (soit ‭262 144‬ chiffres de type long).

En reprennant les calculs d'avant:

  • Par seconde, avec répartition d'usure sur 256 secteurs: 86400 w/d = ‭338 w/d/s, ce qui nous donne ‭295 jours de durée de vie.
  • Toutes les 10 secondes, avec répartition d'usure sur 256 secteurs: 6w/min = 8 640‬ w/d = 33,75 w/d/s soit 2 962 jours (8 ans !)
  • Toutes les minutes, avec répartition d'usure sur 256 secteurs: 1w/min = ‭1 440‬ w/d = 5,625‬ w/d/s, ce qui nous donne 17 777 jours (48 ans !)

Avec la répartition d'usure, on arrive donc à des chiffres beaucoup plus corrects de durée de vie.

Pour ceux qui veulent encore pousser sur la gestion des mémires FLASH, je vous recommande cet article, mais aussi cette explication du systeme de wear leveling présent ser les cartes SD et les clés USB. Je recommande aussi cet article qui explique comment nodeMCU gére l'espace de stockage.

Implementation

Cette partie est a venir ! Stay tuned !

Qu'avez vous pensé de cet article?