Quelques points sur lesquels il faudrait creuser (mais c'est le dernier le plus important)
SDATA occupe 12 octets car le compilateur aligne sur 32 bits. vous allouez donc 2 x 3600 x 12 = plus de 84 ko de RAM sur 160 max allouables de manière statique. Avez vous d'autres gros tableaux ?
votre fonction c'est
// copie des données
void CopieTableau() {
Serial.println("bug - 2 <-- jusque là tout va bien");
while (xSemaphoreTake(mutex, portMAX_DELAY) != pdTRUE) {
// on attend le sémaphore
Serial.println("bug - 3 <-- jusque là tout va bien");
sleep(1);
memcpy(CopieTabMesures, TabMesures, sizeof TabMesures);
Serial.println("bug - 4 <-- jusque là tout va bien");
CopieIdxMesure = IdxMesure;
xSemaphoreGive(mutex);
// on relâche le sémaphore
Serial.println("bug - 5 <-- jusque là tout va bien");
}
}
Vous avez raison de dire que vous n'êtes pas rentré dans le while, puisque vous voyez le bug 2 et bug 5. Il faudrait creuser cela (comprendre votre configuration exacte et le reste du code).
Un point à mentionner c'est que vous ne pouvez pas faire confiance à ce que vous voyez dans le terminal série pou savoir où le code "s'est planté" si vous ne mettez pas un Serial.flush();
après chaque Serial.println()
car l'impression du message est asynchrone et à 115200 bauds prends "super longtemps" par comparaison à la vitesse d'exécution du processeur. vous pouvez avoir 3 messages dans le buffer de sortie et le code est déjà 100 lignes plus loin dans votre code, voire dans un autre thread au moment du plantage.
Un autre point c'est que xSemaphoreTake(mutex, portMAX_DELAY)
est bloquant (environ une semaine) puisque vous mettez comme temps d'attente portMAX_DELAY
, à mon avis ce n'est pas la peine de mettre un while. Vous pourriez mettre
void CopieTableau() {
if (xSemaphoreTake(mutex, portMAX_DELAY) == pdTRUE) {
Serial.println("obtenu le sémaphore"); Serial.flush();
memcpy(CopieTabMesures, TabMesures, sizeof TabMesures);
CopieIdxMesure = IdxMesure;
xSemaphoreGive(mutex);
} else {
Serial.println("erreur, le sémaphore n'est pas dispo"); Serial.flush();
CopieIdxMesure = 0; // erreur on n'a pas eu le mutex
}
}
Ensuite vous avez une fonction processor hyper couteuse en mémoire et en temps... Imaginez que vous ayez CopieIdxMesure
= 3600. Votre calcul de n
donne 7 (vous prenez 1 point sur 7) soit environ 514 points.
à chaque tour de la boucle for()
String donnees = "";
char element[20] = "";
for (int i = 0 ; i < CopieIdxMesure ; i = i+n) {
sprintf((char*) element, "{x:%lu,y:", CopieTabMesures[i].tmillis/1000);
donnees = donnees + String(element);
dtostrf(CopieTabMesures[i].temp, 3, 1, element);
donnees = donnees + String(element);
donnees = donnees + String("},");
}
quand vous demandez donnees = donnees + ...
vous demandez à la classe String de trouver de plus en plus de mémoire ... En effet quand vous faites
donnees = donnees + String(element);
le compilateur doit fabriquer une String temporaire pour String(element)
(allocation dynamique, risque dans le tas) puis comme vous voulez l'ajouter à donnees
il doit trouver de la mémoire pour le contenu de donnees avec le contenu de la String temporaire sans toucher à données, puis il copie dans cet espace le contenu de données et de la String puis enfin il y a la réaffectation à données qui recopiera tout cela et donc une nouvelle demande d'allocation d'espace pour faire grandir la variable donnees
. à ce moment là vous avez en mémoire
- la copie originale de
donnees
- la concaténation de
donnees
avec la String temporaire element
- la zone mémoire future pour
donnees
avec la String temporaire element
➜ plus vous bouclez, plus la taille de donnees
augmente, plus vous augmentez le besoin en RAM, plus vous faites des mouvement en mémoire importants (et inutiles) et vous faites cela 3 fois par boucle for, avec le risque de morceler le tas...
C'est pour cela que l'on dit qu'il ne faut jamais utiliser la forme
donnees = donnees + String(element);
pour une concaténation mais plutôt
donnees += String(element);
qui aura l'avantage d'éviter deux des allocations intermédiaires.
De plus sprintf() sur ESP supporte le %f donc autant bâtir en une seule fois ce qu'il faut rajouter.
Il n'en reste pas moins que la String résultante donnees
est très grande et comme la fonction la retourne par copie, il y a une nouvelle dernière duplication mémoire au moment de la fin de la fonction.
➜ peut-être avez aussi vous un souci mémoire. Vous devriez générer la réponse directement en ligne (est-ce que /graph.html est long ?)
Sinon concernant le watchdog (et c'est là à mon avis votre souci), c'est dans le processor que je mettrais un yield et que j'optimiserai la construction de la String (en espérant qu'il n'y ait pas de souci mémoire)
String processor(const String& var) {
// optimiser la boucle pour réduire le nombre de points quand graphique est grand...
int n = CopieIdxMesure / 600 + 1;
if (var == "DATAS") {
//return {x:241,y:-3.0}, etc...
String donnees;
char element[30];
for (int i = 0 ; i < CopieIdxMesure ; i += n) {
yield();
int nb = snprintf(element, sizeof element, "{x:%lu,y:%.3f},", CopieTabMesures[i].tmillis / 1000ul, CopieTabMesures[i].temp); // https://cplusplus.com/reference/cstdio/snprintf/$0
if (nb >= 0 && nb < sizeof element) {
donnees += element;
} else {
Serial.println("erreur pas assez de place dans element");
break;
}
}
return donnees;
}
// d'autres traitements sans intérêt ici...
}
avec sprintf() et une seule concaténation de String "propre", la copie de 500 éléments devrait prendre environ 125ms.
testez ce code:
struct SDATA {
float temp;
long tmillis;
bool err;
};
SDATA CopieTabMesures[500] = {{3000, 123.456, false},};
String processor() {
String donnees;
char element[30];
for (int i = 0 ; i < 500 ; i++) {
yield();
int nb = snprintf(element, sizeof element, "{x:%lu,y:%.3f},", CopieTabMesures[i].tmillis / 1000ul, CopieTabMesures[i].temp); // https://cplusplus.com/reference/cstdio/snprintf/$0
if (nb >= 0 && nb < sizeof element) {
donnees += element;
} else {
Serial.println("erreur pas assez de place dans element");
break;
}
}
return donnees;
}
void setup() {
Serial.begin(115200);
uint32_t t0 = micros();
String resultat = processor();
uint32_t t1 = micros();
Serial.print("Temps de construction de la chaîne = "); Serial.print(t1 - t0); Serial.println(" µs.");
Serial.print("Taille de la chaîne = "); Serial.print(resultat.length() + 1); Serial.println(" octets.");
}
void loop() {}
sur wokwi ça me dit
Temps de construction de la chaîne = 124990 µs.
Taille de la chaîne = 7004 octets.
si vous mettez votre code avec les copies de String a tout va
sprintf((char*) element, "{x:%lu,y:", CopieTabMesures[i].tmillis/1000);
donnees = donnees + String(element);
dtostrf(CopieTabMesures[i].temp, 3, 1, element);
donnees = donnees + String(element);
donnees = donnees + String("},");
vous obtenez
Temps de construction de la chaîne = 6668798 µs.
➜ 6 ,6 secondes !!! c'est plus qu'il n'en faut pour déclencher le watchdog...