Comportement erratique d'une carte ESP32-WROOM

Bonjour à tous,

De retour après une petite absence je reviens avec un problème surprenant.

Le code posté ci-dessous fonctionne correctement avec une carte ESP32-S2 mini (wemos.cc) à savoir : clignotement de la LED cohérent avec l'état de la connexion, messages sur la console série corrects
Par contre avec une autre carte, le même code (modulo le changement d'IO pour la led) ne fonctionne pas bien :

  • connexion wifi OK, serveur web fonctionnel

mais...

  • la led clignote irrégulièrement et très vite
  • la console se rempli de messages illisibles

D'où cela peut-il provenir ?

J'ai bien sûr testé blick.ino sur la carte en question, RAS

#include <Arduino.h>
#include "ESPAsyncWebServer.h"
#include "AsyncTCP.h"
#include <WiFi.h>
#include <FS.h>
#include <LittleFS.h>
#include <ElegantOTA.h>

#include "webserver.h"

#define LED_BUILTIN 15 // pour LolinS2 mini
//#define LED_BUILTIN 2  // pour ESP-32-WROOM D1

// SSID - mdp wifi
const char* ssid = "LaBoxàMoi";
const char* password = "<censuré>";

// Le serveur web, port 80
AsyncWebServer server(80);

/* --------------------------------------------------------------------------
   Gestion des états Wi-Fi
   -------------------------------------------------------------------------- */

enum WifiState {
  WIFI_STARTING,
  WIFI_DISCONNECTED,
  WIFI_CONNECTED
};

volatile WifiState wifiState = WIFI_STARTING;

// Gestion LED de la carte

unsigned long ledInterval = 1000;   // 1 Hz par défaut au démarrage
unsigned long previousMillis = 0;
bool ledState = LOW;

// Wi-Fi events

void WiFiStationConnected(WiFiEvent_t event, WiFiEventInfo_t info) {
  Serial.println("Connecté !");
}

void WiFiGotIP(WiFiEvent_t event, WiFiEventInfo_t info) {
  Serial.println("IP OK");
  Serial.print("Adresse IP : ");
  Serial.println(WiFi.localIP());

  wifiState = WIFI_CONNECTED;
  ledInterval = 2000;   // 0,5 Hz
}

void WiFiStationDisconnected(WiFiEvent_t event, WiFiEventInfo_t info) {
  Serial.println("Déconnecté, WTF ?");
  Serial.print("Info : ");
  Serial.println(info.wifi_sta_disconnected.reason);

  wifiState = WIFI_DISCONNECTED;
  ledInterval = 200;    // ~5 Hz

  WiFi.begin(ssid, password);
}

/* --------------------------------------------------------------------------
   Initialisation Wi-Fi
   -------------------------------------------------------------------------- */

void initWiFi() {
  wifiState = WIFI_STARTING;
  ledInterval = 1000;   // 1 Hz

  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);

  Serial.println("Connexion en cours...");
}

// ------------------------------------ SETUP -----------------------------------
void setup() {
  Serial.begin(115200);
  while (!Serial) { delay(10); }

  pinMode(LED_BUILTIN, OUTPUT);
  digitalWrite(LED_BUILTIN, LOW);

  Serial.println("Démarrage");

  // Défnition des évènements Wi-Fi
  WiFi.onEvent(WiFiStationConnected, ARDUINO_EVENT_WIFI_STA_CONNECTED);
  WiFi.onEvent(WiFiGotIP, ARDUINO_EVENT_WIFI_STA_GOT_IP);
  WiFi.onEvent(WiFiStationDisconnected, ARDUINO_EVENT_WIFI_STA_DISCONNECTED);

  initWiFi();

  initWebServer(server);
}

// ------------------------------- LOOP ----------------------------------------

void loop() {
  unsigned long currentMillis = millis();

  if (currentMillis - previousMillis >= ledInterval) {
    previousMillis = currentMillis;
    ledState = !ledState;
    digitalWrite(LED_BUILTIN, ledState);
  }
}

Quand vous dites

vous voulez dire ESP32-S2 mini versus ESP32-WROOM ?

L’ESP32-S2 est monocœur. Les callbacks WiFi et loop s’exécutent de façon séquentielle sur le même cœur, ce qui masque les accès concurrents aux variables globales. Modifier ledInterval depuis un callback WiFi et l’utiliser dans loop ne pose pas de problème visible dans ce cas.

L’ESP32-WROOM est bicœur. Les callbacks WiFi s’exécutent sur un autre cœur que celui de loop. ledInterval, previousMillis et ledState sont donc accédées et modifiées simultanément sans synchronisation.

Je serai tenté de dire que cela provoque des valeurs incohérentes lues dans loop.

Oui, je précise alors (désolé de manquer parfois de clarté) : l'ESP32-S2 mini fonctionne comme attendu et l'ESP32-WROOM à un clignotement bizarre.

L'explication est (comme toujours) claire et cohérente.
Mais ce que je ne comprend pas c'est que previousMillis et ledState ne sont modifiés que dans le loop() et ledInterval ne devrait pas changer, sauf défaut du wifi, ce qui n'est pas le cas et temps normal (et clairement pas pendant mes tests où le wifi était stable)

Est-ce que les déclarer en volatile suffirait ? (pas le matéril sous la main pour tester tout de suite)

(et bonne année à tous)

Volatile force la lecture ou écriture en RAM a l’adresse de la variable mais ne garantit pas l’atomicité de l’opération. Il faudrait un mutex / sémaphore.

Bonne année !

Bonjour,

  • Si effectivement 2 cœurs sont en fonction, entièrement d'accord de faire lesdites opérations d'une manière atomique au moyen des définitions comme:
volatile std::atomic<uint32_t> ledInterval;
  • Une autre méthode est d'inhiber les interruptions de tous les accès critiques comme par exemple:
cli();
ledInterval = 2000;   // 0,5 Hz
sei();
void loop() {
  unsigned long currentMillis = millis();

  cli();
  unsigned long l__ledInterval = ledInterval;
  sei();

  if (currentMillis - previousMillis >= l__ledInterval) {
    previousMillis = currentMillis;
    ledState = !ledState;
    digitalWrite(LED_BUILTIN, ledState);
  }
}
  • Une autre piste serait d'interdire l'utilisation du 2nd cœur, mais c'est un peu plus délicat et même dangereux car cela risquerait de tout mettre en vrac en dehors de l'utilisation d'un OS multitâche

NB: Sauf erreur de ma part, je ne vois que la variable globale ledInterval en accès concurent si effectivement accédée depuis les 2 cœurs

A suivre...

Sur ESP32, noInterrupts() désactive uniquement les interruptions au niveau du cœur sur lequel le code Arduino s’exécute, en pratique le plus souvent le core 1. Les interruptions matérielles du second cœur continuent de fonctionner normalement donc vaut mieux prendre l’approche mutex ou atomique si c’est approprié.

L’ajout de volatile est inutile, car std::atomic fournit déjà toutes les garanties de visibilité mémoire et d’ordonnancement. Les opérations de lecture, écriture et read-modify-write sur cet atomic sont sûres entre les deux cœurs.

Cela ne protège pas une séquence logique de plusieurs opérations distinctes, qui nécessiterait alors un mécanisme de synchronisation supplémentaire mais ce n’est pas le cas ici .

Maintenant que ça ne compile plus à l'allure d'un escargot arthritique, je reprend mon projet...

Je créé le mutex dans le setup() :

     stateMutex = xSemaphoreCreateMutex();

J'ai essayé d'encadrer tous les accès aux variables gérant la LED par des verrouillages du mutex :

    xSemaphoreTake(stateMutex, portMAX_DELAY);
        wifiState   = WIFI_CONNECTED;
        ledInterval = 2000;   // 0,5 Hz
    xSemaphoreGive(stateMutex);

C'est bien comme cela qu'il faut procéder ?

Cela n'a aucun effet malheureusement :frowning:

Encore plus bizarre que je ne le pensais : j'ai connecté une LED externe au GPIO23 et modifié le code en conséquence.

Avec blick.ino, c'est OK, la LED externe clignote
Avec mon code... la LED externe ne clignote pas mais la LED de la carte reprend son clignotement bizarre.

Autre chose, probablement liée mais inexpliquée pour moi : la console série affiche des caractères (très nombreux) incongrus (vitesse de transmission correcte) :

p`�p�pp�`�p�p`|p