Esp32 : deep sleep et SPIFFS

Bonsoir

En étudiant le deep sleep de l'esp32 j'ai vu que les variables sont perdues si on ne prend pas la précaution de les stocker dans la RAM de la RTC, qui est le seul bloc du SoC qui reste alimenté. Mais qu'en est il de la mémoire SPIFFS ? Est ce que les fichiers stockés sont conservés durant ce deep sleep ?

Je crois que la RAM de la RTC fait 4ko ou 8ko. Le compilateur m'indique que les variables globales de mon code occupent près de 50000 octets, donc ça ne rentrera pas dans la RAM RTC, même si je pousse très fort... D'où l'idée de stocker ce dont je me sers dans un fichier dans la SPIFFS. Qu'en pensez vous ?

Le SPIFFS est conservé même si vous coupez le jus. c’est en mémoire flash, pas SRAM
Ce sera lent au réveil de tout charger.

50Ko de variables globales ? vous y faites conservez quoi ?

C'est vrai que le code est un peu long et fait appel à pas mal de bibliothèques. Il va chercher l'heure et la météo sur Internet, parse les données Json et les affiche sur deux écrans OLED. Il y a aussi un accéléromètre et un DHT22.

L'IDE m'indique ceci:

Le croquis utilise 876190 octets (66%) de l'espace de stockage de programmes. Le maximum est de 1310720 octets.
Les variables globales utilisent 45108 octets (13%) de mémoire dynamique, ce qui laisse 282572 octets pour les variables locales. Le maximum est de 327680 octets.

C'est bien 45 ko de variables globales ? C'est vrai que j'ai une douzaine de const String utilisés par la bibliothèque ArduinoJson, mais le reste de mes variables n'est pas grand chose, à part 5 tableaux de dimension 6...

Cette consommation vient-elle des bibliothèques ?

Bonjour

Pour nos applications on dipose de 8k en Ram RTC (c'est la RTC Slow Memory)

Une seconde RAM RTC de 8k (= RTC Fast Memory) , elle ausi préservée en 'sommeil profond', est résevée au coeur 'protocole' par opposition au coeur 'application'.

-> Délimiter les informations essentielles qu'il faut conserver, pas besoin de sauver tout le contexte qui a permis de les produire. Pourquoi sauvegarder le buffer json avant parsing , par exemple ?

8K de Ram préservée ça parait déjà conséquent pour l'application décrite !

al1fch:
-> Délimiter les informations essentielles qu'il faut conserver, pas besoin de sauver tout le contexte qui a permis de les produire. Pourquoi sauvegarder le buffer json avant parsing , par exemple ?

8K de Ram préservée ça parait déjà conséquent pour l'application décrite !

Je suis d'accord avec toi, c'est pourquoi je posais cette question. Je pense que le buffer json est local à la fonction qui le parse. Je peux gagner de la mémoire en mettant mes const String là où je les utilise.

Le reste j'en ai besoin car typiquement les données sont produites dans une fonction et affichées dans d'autres.

Si vous pouvez régénérer les données (nouvelle lecture capteur) ce n’est pas nécessaire de les conserver non plus

J'ai fait la chasse au gaspi, mais le résultat n'est pas fameux :

Le croquis utilise 876242 octets (66%) de l'espace de stockage de programmes. Le maximum est de 1310720 octets.
Les variables globales utilisent 44848 octets (13%) de mémoire dynamique, ce qui laisse 282832 octets pour les variables locales. Le maximum est de 327680 octets.

Voila mes déclarations de bibliothèques :

#include <Arduino.h>
#include <Wire.h>
#include "I2Cdev.h"
#include "MPU6050.h"
#include <U8g2lib.h>
#include <dummy.h>  //for esp32
#include <WiFi.h>
#include <HTTPClient.h>
#include "time.h"
#include "DHTesp.h"
#include "ArduinoJson.h"

// Bus 1 : les 2 écrans
#define SDA1 19
#define SCL1 23

// Bus 2 : l'accéléro
#define SDA2 5
#define SCL2 4

#define LED_PIN 22
#define DHT_PIN 26

U8G2_SH1106_128X64_NONAME_F_SW_I2C display1(U8G2_R0, SCL1, SDA1);
U8G2_SH1106_128X64_NONAME_F_SW_I2C display2(U8G2_R0, SCL1, SDA1);

// U8G2_SSD1306_128X64_NONAME_F_SW_I2C display1(U8G2_R0, SCL1, SDA1);
// U8G2_SSD1306_128X64_NONAME_F_SW_I2C display2(U8G2_R0, SCL2, SDA2);
MPU6050 accelgyro;
DHTesp dht;

#include "Variables.h"
#include "Internet.h"
#include "Affichage.h"
#include "Accelero.h"

et mes variables :

// Accelerometer
byte Orientation;
bool AcceleroSuccess = false;
enum Direction {Xplus, Xmoins, Yplus, Ymoins, Zplus, Zmoins, Unknown};

// Time
int second;
int minute;
int hour;
int day;
int month;
int year;
int weekday;

// Second interval fine adjustment
#define ADJUST_SECONDS 0 // 0 pour ne pas rechercher l'optimum sinon 1
unsigned long nmicro = 1000;
int oldsec;

// Meteo
// Current weather
const char* current_weather_description;
float current_temp;
float current_pressure;
const char* current_PressureTendency_Code;
const char* current_weather_icon;
float current_precip_mm;
int current_humidity;
float current_uv;
// Hourly forecast
#define nbHourForecast 6
byte hourlyHours[nbHourForecast] = {0,1,2,5,8,11};
const char* hourly_weather_description[nbHourForecast];
float hourly_temp[nbHourForecast];
int hourly_weather_icon[nbHourForecast];
int hourly_PreciProba[nbHourForecast];
// Daily forecast
#define nbDayForecast 3
const char* daily_weather_description[nbDayForecast];
const char* daily_weather_icon[nbDayForecast];
const char* daily_date[nbDayForecast];
float daily_mintemp[nbDayForecast];
float daily_maxtemp[nbDayForecast];
float daily_Precip[nbDayForecast];
const char* daily_sunrise[nbDayForecast];
const char* daily_sunset[nbDayForecast];

// Program
bool blinkState = false;
unsigned long chronoD1;
unsigned long chronoAcc;
bool Aff2 = false;

Au passage, quand je compile un code vide (setup et loop vides, pas de variables ni de bibliothèques), j'obtiens quand même :

Le croquis utilise 173740 octets (13%) de l'espace de stockage de programmes. Le maximum est de 1310720 octets.
Les variables globales utilisent 13272 octets (4%) de mémoire dynamique, ce qui laisse 314408 octets pour les variables locales. Le maximum est de 327680 octets.

De 13000 à 45000, ça me fait 32000 octets : je ne vois pas où ils sont dans mes variables !

l'exemple 'Bare Minimum' donne 13 272 ko en 'variables globales' (en gros les besoins du 'firmware embarqué
l'exemple 'WiFi Client' donne 41 504 ko

plus peut être les buffers des afficheurs selon les librairies (écriture directe dans l'afficheur ou via un buffer en ram ?)

Où vois-tu ça ?

A terme, je compte remplacer les OLED (0.96") par des e-paper. Mais ils seront plus grands...

Une façon simple de traiter le problème est de vous concentrer uniquement sur vos données et traiter le réveil du deep sleep comme un reboot (voir carrément rebooter)

c'est un peu le même principe de la reprise après panne / démarrage à froid -> vous avez stocké dans une mémoire permanente les valeurs clés et vous initialisez vos variables avec cela

Quand j'ai ce besoin généralement je mets toutes mes variables d'états dans une grosse structure globale (cf un exemple dans mon petit projet de gestion te T°) en utilisant une "séquence magique" pour vérifier que la mémoire a bien été initialisée. L'avantage de la structure c'est que vous pouvez écrire ça comme un BLOB (binary large object) et tout dumper ou lire d'un seul coup en EEPROM ou sur un fichier

Merci J-M-L, je vais étudier ça

l'avantage de "forcer" le reboot c'est que vous repassez par le setup() et donc réinitialisez toutes vos classes comme il faut (faut prêter attention à toutes celles qui ont un begin() ou un init() sinon)

j'ai des montages ou je ne m'ennuie pas à gérer le sleep car il y a aussi l'alimentation de tout le reste donc j'éteins carrément le système en utilisant un Pololu Power Switches. ce switch c'est ce que l'utilisateur va appuyer pour démarrer manuellement le système mais en le connectant à une RTC ou un autre petit système vous pouvez aussi déclencher le réveil quand vous voulez en envoyant un front au power switch

Faire l'inventaire des donnnées minimales dont il faut absolument disposer au redémarrage
(les données à afficher ? d'autres ?)

SI le cumul tient dans les 8k et s'il n'est pas indispensable de conserver les données en cas de coupure de l'alimentation utiliser la Ram RTC

Sinon utiliser la mémoire Flash SPI par SPIFFS comme indiqué par J-M-L
(L'EEPROM émulée peut également servir mais il faut tenir compte de la taille des pages et sans doute faire un partitionnement adapté)

J-M-L, le réveil partir d'un deepsleep(ESP8266 ou ESP32) est un redémarrage avec exécution du setup() !

En deep sleep tout est 'éteint' dans les ESP sauf la partie dite RTC (timer et ram)
C'est un redémarrage total avec le bénéfice de la conservation du contenu de la RAM RTC.
Il me semble d'ailleurs qu'on retrouve même les états initiaux des GPIO. Les valeurs en RAM normale sont aléatoires.
L'ESP doit recharger le code de la Flash vers la Ram. (en gros 100ms)
Bien entendu millis() repart à zéro , je sauvegarde en Ram RTC sa valeur juste avant l'appel du deepsleep , ça me permet de suivre la durée des phases d'eveil que je cherche à réduire.

Bien entendu en deep sleep il reste une petite consommation , mais avec les 5µA de l'ESP32 j'ai renoncé à faire des coupures d'alimentation. (5µA obtenus sans interface série/USB et sous alimentation directe 3,2V donc sans régulateur)

Bon, merci de votre aide. Pour l'instant ça marche...

Il me reste à tester le réveil par interruption de l'accéléromètre et par les touchs de l'ESP32.
Et à peaufiner le code. Mais je ne vois toujours pas d'où viennent ces 45ko de variables globales. Lorsque je passe mes variables à moi dans la RAM RTC, le nombre diminue un peu : en gros j'utilise réellement moins d'un ko.
Le reste c'est en effet les buffers des écrans et ... quoi ? Les buffers des json ?

Ton apllication repose sur un socle de fonctionnalités dont RTOS qui est en service d'office en arrière plan pour l'ESP32.(même si tu ne t'y réfères pas explicitement)

La sur-couche d'abstraction Arduino par dessus l'IDF Espressif masque tout cela.

Regardes l'exemple 'Bare Minimum' : setup() et main() vide -> 13272 octets de RAM
Ajoutes la pile de protocole réseau , le WIFI et un client -> 41 504 octets
Le complément vient avec des librairies....... et enfin..... 'peanuts' tes variables.

Voir par exemple cette analyse d'un simple Hello World

J-M-L, le réveil partir d'un deepsleep(ESP8266 ou ESP32) est un redémarrage avec exécution du setup() !

Ce que je voulais dire (à vérifier) c’est qu’au reboot les variables globales sont bien mises à zéro et la config des pins connue etc alors que je ne suis pas sûr que le démarrage post deep sleep soit similaire - mais je me trompe peut être, je n’ai pas exploré en détail cette partie ‘je préfère tout éteindre)

J'ai moi même été surpris de la profondeur du sommeil, je ne m'attendais pas à celà !!
Plus qu'un réveil c'est une re-naissance !
Le deep-sleep eteint lui même tout à la seule exception de la partie nommée RTC.
Tout redémarre au reveil comme pour une mise sous tension ...... c'est ailleurs effectivement une mise sous tension de la quasi totalité du SOC

Oui

==>

(source ESP32 Deep Sleep Tutorial)

Mais ça n’éteint pas les systèmes connexes - d’où le petit switch pilotable

Mais ça n'éteint pas les systèmes connexes - d'où le petit switch pilotable

Effectivement , l'ESP32 dort profondément quand les autres restent eveillés !!

Pour faire une gestion globale j'aime bien le TPL5110 (timer + commande du PMOS externe)


Un 'breakout' d'Adafruit rassemble le tout
(Je ne connaissais pas les modules Power Switch de Pololu)

J-M-L:
Mais ça n’éteint pas les systèmes connexes - d’où le petit switch pilotable

Je suppose que c'est ce qui permet de le réveiller via une interruption envoyée par un capteur, dans mon cas l'accéléromètre ?