Hi,
I’ve been trying (for like 8 months now) to build a RC Rover quite similar to Perseverance.
Part of the project aims at displaying the picture taken by a camera on the rover (ESP32S3-Cam N16R8), on the 7-inch screen of an ESP32S3-8048s070c (big CYD)
Picture size : 480*320, approx 20-30 kb each, jpg.
The touchscreen sends a few commands :
- laser = briefly activates a linear laser to indication the “forward” direction on the picture
- flash = LED flash during capture
- rotation = servo rotates the whole “head” of the rover, and with it the camera
Here is my problem :
→ the rover behaves correctly when i’m issuing commands manually through a computer browser. I therefore tend to think that the code on my ESPCam is correct
→ however, the 7-inch ESP32S3 can’t keep a stable connection to the ESPCAM. Sometimes I manage to take a whole series of pictures, sometimes the ESP32 returns a “connection refused” error.
I’ve asked for help on 4 different IA agents (chatGPT, Gemini, Claude, Mistral), but they are quite useless.
QUESTION 1 : has anyone an idea about what could cause such an unreliable behavior with the 7inch CYD ?
QUESTION 2 : Are there other projects similar to this one (remotely capturing and displaying a picture from an ESPCAM)
Here are both codes (ESPCAM and CYD)
ESP32-S3 CAM
// ------------------------------------------------------------------------
// Camera embarquée ESP32CAMS3
// Code par Florent Kieffer
// 2 fonctions : piloter le servo par pwm manuel puis capturer ponctuellement l'image afin de l'afficher sur la tablette de contrôle
// ------------------------------------------------------------------------
#include "esp_camera.h"
#include <WiFi.h>
#include <WebServer.h>
// --- Configuration Pins ESP32-S3 CAM (Freenove / AI-Thinker S3) ---
#define PWDN_GPIO_NUM -1
#define RESET_GPIO_NUM -1
#define XCLK_GPIO_NUM 15
#define SIOD_GPIO_NUM 4
#define SIOC_GPIO_NUM 5
#define Y9_GPIO_NUM 16
#define Y8_GPIO_NUM 17
#define Y7_GPIO_NUM 18
#define Y6_GPIO_NUM 12
#define Y5_GPIO_NUM 10
#define Y4_GPIO_NUM 8
#define Y3_GPIO_NUM 9
#define Y2_GPIO_NUM 11
#define VSYNC_GPIO_NUM 6
#define HREF_GPIO_NUM 7
#define PCLK_GPIO_NUM 13
const byte laserPin = 46;
const byte flashPin = 42;
const byte pinServo = 3;
// --- Variables d'état ---
int targetAngle = 90;
int currentAngle = 90;
const char* ssid_AP = "NEM6NaviCam";
const char* password_AP = "pw*navicam3";
WebServer server(80);
// --- Handlers HTTP ---
// 1. Route pour la rotation : /rotation?angle=90
void handleRotation() {
Serial.println("=== Nouvelle requête HTTP HANDLEROTATION ===");
Serial.print("IP client : ");
Serial.println(server.client().remoteIP());
Serial.print("Nombre de connections");
Serial.println(WiFi.softAPgetStationNum());
if (server.hasArg("angle")) {
int val = server.arg("angle").toInt();
targetAngle = constrain(val, 50, 140);
targetAngle = 180 - targetAngle; // engrenage : inversion du sens de rotation
Serial.printf("Rotation demandée : %d°\n", targetAngle);
server.send(200, "text/plain", "OK");
server.client().stop(); // ← fermeture explicite
} else {
server.send(400, "text/plain", "Angle manquant");
server.client().stop(); // ← fermeture explicite
}
}
// 2. Route pour la capture : /capture?flash=1&laser=0
// ============ code CLAUDE IA
void handleCapture() {
Serial.println("=== Nouvelle requête HTTP HANDLECAPTURE ===");
Serial.print("IP client : ");
Serial.println(server.client().remoteIP());
Serial.print("Nombre de connections");
Serial.println(WiFi.softAPgetStationNum());
server.client().setNoDelay(true); // désactive Nagle, envoi immédiat
bool useFlash = (server.hasArg("flash") && server.arg("flash") == "1");
bool useLaser = (server.hasArg("laser") && server.arg("laser") == "1");
digitalWrite(flashPin, useFlash ? HIGH : LOW);
digitalWrite(laserPin, useLaser ? HIGH : LOW);
if (useFlash || useLaser) delay(200);
// Vider le buffer caméra pour avoir une image fraîche
camera_fb_t* fb = esp_camera_fb_get();
if (fb) esp_camera_fb_return(fb); // on jette la première (peut être ancienne)
fb = esp_camera_fb_get(); // on prend la suivante, vraiment fraîche
if (!fb) {
digitalWrite(flashPin, LOW);
digitalWrite(laserPin, LOW);
server.send(500, "text/plain", "Erreur Camera");
return;
}
// Envoi via le client directement référencé, sans copie
WiFiClient& client = server.client(); // référence, pas copie !
// Construction manuelle de la réponse HTTP
String header = "HTTP/1.1 200 OK\r\n";
header += "Content-Type: image/jpeg\r\n";
header += "Content-Length: " + String(fb->len) + "\r\n";
header += "Connection: close\r\n\r\n";
client.print(header);
// Envoi par chunks pour ne pas saturer le buffer TCP
const size_t chunkSize = 1024;
size_t sent = 0;
while (sent < fb->len) {
size_t toSend = min(chunkSize, fb->len - sent);
client.write(fb->buf + sent, toSend);
sent += toSend;
}
client.flush(); // s'assure que tout est bien parti
client.stop(); // ← ajout après le flush
esp_camera_fb_return(fb);
digitalWrite(flashPin, LOW);
digitalWrite(laserPin, LOW);
Serial.printf("Image envoyée : %d octets\n", fb->len);
}
void webServerTask(void* arg) {
for (;;) {
server.handleClient();
vTaskDelay(pdMS_TO_TICKS(2)); // 2ms suffit, libère le CPU entre deux clients
}
}
// --- Tâche Servo (Core 1) ---
void servoTask(void* arg) {
pinMode(pinServo, OUTPUT);
while (true) {
// Déplacement progressif (Smooth)
if (currentAngle < targetAngle) currentAngle++;
else if (currentAngle > targetAngle) currentAngle--;
// Génération du signal PWM manuel (plus stable sur S3 que ledc pour les servos)
int pulse = map(currentAngle, 0, 180, 500, 2500);
digitalWrite(pinServo, HIGH);
delayMicroseconds(pulse);
digitalWrite(pinServo, LOW);
// Fréquence ~50Hz (20ms) moins le temps du pulse
vTaskDelay(pdMS_TO_TICKS(20));
}
}
void setup() {
Serial.begin(115200);
pinMode(laserPin, OUTPUT);
pinMode(flashPin, OUTPUT);
pinMode(pinServo, OUTPUT);
pinMode(2, OUTPUT);
// Configuration Caméra S3
camera_config_t config;
config.ledc_channel = LEDC_CHANNEL_0;
config.ledc_timer = LEDC_TIMER_0;
config.pin_d0 = Y2_GPIO_NUM;
config.pin_d1 = Y3_GPIO_NUM;
config.pin_d2 = Y4_GPIO_NUM;
config.pin_d3 = Y5_GPIO_NUM;
config.pin_d4 = Y6_GPIO_NUM;
config.pin_d5 = Y7_GPIO_NUM;
config.pin_d6 = Y8_GPIO_NUM;
config.pin_d7 = Y9_GPIO_NUM;
config.pin_xclk = XCLK_GPIO_NUM;
config.pin_pclk = PCLK_GPIO_NUM;
config.pin_vsync = VSYNC_GPIO_NUM;
config.pin_href = HREF_GPIO_NUM;
config.pin_sccb_sda = SIOD_GPIO_NUM;
config.pin_sccb_scl = SIOC_GPIO_NUM;
config.pin_pwdn = PWDN_GPIO_NUM;
config.pin_reset = RESET_GPIO_NUM;
config.xclk_freq_hz = 20000000;
config.frame_size = FRAMESIZE_HVGA; // 480x320
config.pixel_format = PIXFORMAT_JPEG;
config.grab_mode = CAMERA_GRAB_LATEST;
config.fb_location = CAMERA_FB_IN_PSRAM;
config.jpeg_quality = 10;
config.fb_count = 2;
if (esp_camera_init(&config) != ESP_OK) {
Serial.println("Erreur Camera");
}
sensor_t* s = esp_camera_sensor_get();
s->set_vflip(s, 1);
//s->set_hmirror(s, 1);
s->set_quality(s, 10);
// WiFi & Serveur
WiFi.softAP(ssid_AP, password_AP);
IPAddress IP = WiFi.softAPIP();
Serial.print("Point d'acces cree. SSID: ");
Serial.println(ssid_AP);
Serial.print("Adresse IP: http://");
Serial.println(IP);
server.on("/rotation", HTTP_GET, handleRotation);
server.on("/capture", HTTP_GET, handleCapture);
server.begin();
// Task Servo sur Core 1
xTaskCreatePinnedToCore(servoTask, "Servo", 2048, NULL, 1, NULL, 1);
// Task Server sur Core 0
xTaskCreatePinnedToCore(webServerTask, "WebSrv", 4096, NULL, 2, NULL, 0);
digitalWrite(laserPin, 1);
delay(300);
digitalWrite(laserPin, 0);
digitalWrite(flashPin, 1);
delay(300);
digitalWrite(flashPin, 0);
digitalWrite(pinServo, 1);
delay(300);
digitalWrite(pinServo, 0);
}
unsigned long ltimer;
void loop() {
if (millis() - ltimer > 2000) {
analogWrite(2, 40);
}
if (millis() - ltimer > 2200) {
analogWrite(2, 0);
ltimer = millis();
}
vTaskDelay(5); // Laisse l'IDLE task respirer
}
7 INCH CYD : ESP32-S3 8048 S070c
String version = "0.7";
#include <LovyanGFX.hpp>
#include <lgfx_user/LGFX_Sunton_ESP32-8048S070.h>
#include <WiFi.h>
#include <esp_wifi.h>
#include <WiFiClient.h>
#include <HTTPClient.h>
#include <esp_heap_caps.h>
#include "GUI.h"
static LGFX lcd;
LGFX_Sprite spriteTete(&lcd);
// ATTENTION DEFINITION DES COULEURS : utiliser lcd.
#define bleu1 0x7c98
#define bleu2 0x5d19
#define bleunuit 0x2946
#define rougefonce 0x2021
#define bggrey 0x10a2 //0x18a3
#define ORANGE lcd.color565(255, 165, 0)
#define YELLOW lcd.color565(255, 255, 0)
#define MINIMAL_FRAME_INTERVAL 10000
/* ========================================================= */
bool lastClic = 0;
unsigned long lastClicTime = 0;
unsigned long lastFrameTime = 0;
const char* ssid = "NEM6NaviCam";
const char* password = "pw*navicam3";
volatile bool wifiOK;
String camUrl = "http://192.168.4.1/capture?flash=";
String rotUrl = "http://192.168.4.1/rotation?angle=";
bool rotationRequest = 0;
unsigned long rotationSentTime = 0; // utile pour le délai entre les requetes rotation et capture
unsigned long lastWifiCheck; // verification du WiFi pour affichage sur écran
volatile unsigned long imageReceivedTime = 0;
volatile bool imageFailed = false;
uint8_t* jpgBuf = nullptr;
size_t jpgLen = 0;
SemaphoreHandle_t jpgMutex;
volatile bool newFrame = false;
volatile bool fetchNewImage = false;
unsigned long redrawTimeout;
enum graphicsRequestENUM {
REDRAW_NONE,
REDRAW_FLASH,
REDRAW_LASER,
REDRAW_PRE_JPG,
REDRAW_WAIT_ROT,
REDRAW_JPG,
};
volatile graphicsRequestENUM graphReq = REDRAW_NONE;
bool flash = 0;
bool laser = 0;
int rotation_step = 0;
/* ======================= Fonctions d'affichage sur core 1 ================= */
void trace_ecran_accueil() {
Serial0.println("-----Tracé de l'écran d'accueil -------- ");
lcd.clear(bggrey);
lcd.setFont(0);
lcd.setTextDatum(BR_DATUM);
lcd.setTextColor(TFT_WHITE);
lcd.drawString(version, 799, 479);
// cadres partie droite
lcd.fillRect(315, 60, 482, 322, TFT_BLACK);
lcd.drawRect(315, 60, 482, 322, TFT_RED);
lcd.fillRect(340, 390, 400, 26, TFT_BLACK);
// tracé de textes
traceTitreCadre("NEM_6 NAVICAM", 320, 40, TFT_ORANGE, 0);
traceTitreCadre("Eclairage", 5, 90, bleu1, 1);
traceTitreCadre("Orientation", 5, 290, bleu1, 1);
traceTitreCadre(">> CAPTURE <<", 460, 460, TFT_YELLOW, 0);
lcd.setTextColor(TFT_WHITE);
lcd.setFont(&Roboto_Thin_24);
lcd.drawString("FLASH", 15, 150);
lcd.drawString("LASER", 15, 210);
// Rover - tracé
lcd.fillRect(5, 400, 50, 81, TFT_BLACK); //roue
lcd.drawRect(5, 400, 50, 81, TFT_GREEN); //roue
for (int i = 408; i <= 480; i += 12) { // dents
lcd.drawFastHLine(10, i, 40, TFT_GREEN);
}
lcd.fillRect(75, 360, 15, 65, 0xdedb); //bras1
lcd.drawRect(75, 360, 15, 65, TFT_GREEN); //bras1
lcd.fillRect(92, 360, 8, 35, 0xdedb); // bras2
lcd.drawRect(92, 360, 8, 35, TFT_GREEN); // bras2
lcd.drawRect(90, 362, 2, 4, TFT_GREEN); // pivot
lcd.fillRect(65, 426, 160, 73, 0xdedb);
lcd.drawRect(65, 426, 160, 73, TFT_GREEN);
lcd.fillRect(235, 400, 50, 81, TFT_BLACK); //roueD
lcd.drawRect(235, 400, 50, 81, TFT_GREEN); //roueD
for (int i = 408; i <= 480; i += 12) { // dentsD
lcd.drawFastHLine(240, i, 40, TFT_GREEN);
}
drawBtnOnOff(0, flash);
drawBtnOnOff(1, laser);
spriteTete.pushSprite(130, 350, 0x1000); // à réorienter selon 'rotation' — transparence sur 0x1000
lcd.pushImage(10, 320, 60, 60, turnL);
lcd.pushImage(240, 320, 60, 60, turnR);
}
void traceTitreCadre(String titre, int xpos, int ypos, uint16_t color, bool font) { // tracé facilité de texte encadré
if (!font) lcd.setFont(&fonts::Orbitron_Light_24);
else lcd.setFont(&FreeSansBold18pt7b);
lcd.setTextDatum(BL_DATUM);
int wtext = lcd.textWidth(titre);
int htext = lcd.fontHeight();
Serial0.printf("X1 : %d, Y1 : %d, X2 : %d, Y2 : %d\n", xpos - 5, ypos - htext - 3, wtext + 10, htext + 6);
lcd.fillRect(xpos - 5, ypos - htext - 3, wtext + 10, htext + 6, TFT_BLACK);
lcd.drawRect(xpos - 5, ypos - htext - 3, wtext + 10, htext + 6, color);
lcd.setTextColor(color);
lcd.drawString(titre, xpos, ypos);
}
void dynamicCaptureCallout(int x, int y, String callout) {
lcd.setTextFont(0);
lcd.fillRect(340, 390, 400, 26, TFT_BLACK);
lcd.setTextColor(TFT_GREEN);
if (callout == "ERREUR IMAGE") {
lcd.setTextColor(TFT_RED);
lcd.fillRect(315, 60, 482, 322, TFT_BLACK);
}
lcd.setCursor(x, y);
lcd.print(callout);
}
void drawBtnOnOff(bool select, bool onOff) { // select vaut 0 -> on affiche flash, select vaut 1-> on affiche laser
if (onOff) lcd.pushImage(120, 117 + select * 60, 80, 35, btnOn);
else lcd.pushImage(120, 117 + select * 60, 80, 35, btnOff);
}
void action_flash() {
flash = !flash;
graphReq = REDRAW_FLASH;
}
void action_laser() {
laser = !laser;
graphReq = REDRAW_LASER;
}
void action_turn(int vari) {
// rotation_step varie de -4 à +4
rotation_step += vari;
rotation_step = constrain(rotation_step, -4, 4);
lcd.fillRect(90, 320, 210, 159, bggrey);
lcd.pushImage(240, 320, 60, 60, turnR);
lcd.fillRect(90, 426, 135, 73, 0xdedb);
//lcd.fillRect(75, 360, 15, 65, 0xdedb); //bras1
//lcd.drawRect(75, 360, 15, 65, TFT_GREEN); //bras1
lcd.fillRect(92, 360, 8, 35, 0xdedb); // bras2
lcd.drawRect(92, 360, 8, 35, TFT_GREEN); // bras2
lcd.drawRect(90, 362, 2, 4, TFT_GREEN); // pivot
lcd.drawRect(65, 426, 160, 73, TFT_GREEN);
lcd.fillRect(235, 400, 50, 81, TFT_BLACK); //roueD
lcd.drawRect(235, 400, 50, 81, TFT_GREEN); //roueD
for (int i = 408; i <= 480; i += 12) { // dentsD
lcd.drawFastHLine(240, i, 40, TFT_GREEN);
}
int angle = rotation_step * 10;
Serial0.printf("Vari : %d, Rotation_step : %d, Angle : %d\n", vari, rotation_step, angle);
spriteTete.pushRotateZoom(180, 414, angle, 1.0, 1.0, 0x1000);
}
void action_captureWasClicked() {
if (millis() - lastFrameTime > MINIMAL_FRAME_INTERVAL) {
Serial0.println("Bouton pressé : Capture ");
graphReq = REDRAW_PRE_JPG;
lastFrameTime = millis();
} else {
Serial0.println("Trop tôt !!");
}
}
/* ======================= Taches FreeRTOS ================= */
void taskFetchCam(void* pv) {
for (;;) {
// Guard : ne rien faire si pas connecté
if (WiFi.status() != WL_CONNECTED) {
Serial0.println("WiFi perdu, attente...");
wifiOK = false;
vTaskDelay(pdMS_TO_TICKS(1000));
continue;
} else {
wifiOK = true;
}
if (rotationRequest) {
HTTPClient http;
WiFiClient client;
Serial0.print("rotation_request at time : ");
Serial0.println(millis());
String URL = rotUrl + String(90 + rotation_step * 15);
Serial0.println(URL);
//http.begin(URL);
http.begin(client, URL);
http.setTimeout(12000);
int httpCode = http.GET(); // C'est CETTE ligne qui envoie l'URL à l'ESPCAM
if (httpCode > 0) {
Serial0.printf("[HTTP] GET... code: %d\n", httpCode);
} else {
Serial0.printf("[HTTP] GET... failed, error: %s\n", http.errorToString(httpCode).c_str());
}
http.end();
rotationRequest = 0;
Serial0.print("rotation_request ENDED at time : ");
Serial0.println(millis());
}
if (fetchNewImage) {
HTTPClient http;
WiFiClient client;
Serial0.println("Commande de prise de vue");
String URL = camUrl + String(flash) + "&laser=" + String(laser);
Serial0.println(URL);
http.begin(client, URL);
http.setTimeout(12000);
int code = http.GET();
Serial0.printf("GETCODE : %d\n", code);
if (code == HTTP_CODE_OK) {
int len = http.getSize();
Serial0.printf("GETSIZE : %d\n", len);
if (len > 0) {
uint8_t* tmp = (uint8_t*)ps_malloc(len);
if (tmp) {
WiFiClient* stream = http.getStreamPtr();
int pos = 0;
unsigned long tStart = millis();
while (pos < len && millis() - tStart < 10000) {
int avail = stream->available();
if (avail > 0) {
pos += stream->read(tmp + pos, min(avail, len - pos));
}
vTaskDelay(1);
}
if (pos < len) {
Serial0.printf("Stream interrompu : %d/%d\n", pos, len);
free(tmp);
imageFailed = true; // ← stream coupé en cours de route
} else {
xSemaphoreTake(jpgMutex, portMAX_DELAY);
if (jpgBuf) free(jpgBuf);
jpgBuf = tmp;
jpgLen = len;
newFrame = true;
xSemaphoreGive(jpgMutex);
Serial0.println("Nouvelle image Recuperee");
}
} else {
Serial0.println("ps_malloc échoué");
imageFailed = true; // ← pas de mémoire disponible
}
} else {
Serial0.println("Taille image nulle");
imageFailed = true; // ← taille = 0
}
} else {
Serial0.printf("HTTP erreur : %d\n", code);
imageFailed = true; // ← code HTTP != 200
}
http.end();
fetchNewImage = 0;
}
vTaskDelay(100);
}
}
void taskTouchAndDisplay(void* pv) {
trace_ecran_accueil();
for (;;) {
// ======================================= Check Wifi ============================================
if (millis() > lastWifiCheck + 2000) {
wifiOK = (WiFi.status() == WL_CONNECTED);
lcd.setTextFont(0);
if (wifiOK) {
lcd.setTextColor(TFT_GREEN);
} else {
lcd.setTextColor(TFT_RED);
}
lcd.drawString("Wifi", 780, 10);
lastWifiCheck = millis();
}
//======================================== GESTION DU CLIC ========================================
int clic_x, clic_y;
if (lcd.getTouch(&clic_x, &clic_y) && !lastClic && (millis() - lastClicTime > 300)) {
//Serial0.printf("clic_x : %d -- clic_y : %d\n", clic_x, clic_y);
if (clic_x > 120 && clic_x < 200 && clic_y > 117 && clic_y < 152) {
action_flash();
} else if (clic_x > 120 && clic_x < 200 && clic_y > 177 && clic_y < 212) {
action_laser();
} else if (clic_x < 70 && clic_y > 320 && clic_y < 380) {
action_turn(-1);
} else if (clic_x > 240 && clic_x < 320 && clic_y > 320 && clic_y < 380) {
action_turn(1);
} else if (clic_x > 470 && clic_x < 630 && clic_y > 420 && clic_y < 465) {
action_captureWasClicked();
};
lastClic = 1;
lastClicTime = millis();
} else if (!lcd.getTouch(&clic_x, &clic_y)) {
lastClic = 0;
}
//======================================== GESTION DES ACTIONS DECLENCHEES ========================================
if (graphReq != REDRAW_NONE) {
switch (graphReq) {
case REDRAW_FLASH:
drawBtnOnOff(0, flash);
graphReq = REDRAW_NONE;
break;
case REDRAW_LASER:
drawBtnOnOff(1, laser);
graphReq = REDRAW_NONE;
break;
case REDRAW_PRE_JPG:
dynamicCaptureCallout(345, 400, "Positionnement camera...");
rotationRequest = 1;
rotationSentTime = millis();
graphReq = REDRAW_WAIT_ROT; // nouvel état
break;
case REDRAW_WAIT_ROT:
if (millis() - rotationSentTime > 2000) {
dynamicCaptureCallout(345, 400, "Acquisition image...");
fetchNewImage = 1;
redrawTimeout = millis() + 10000;
graphReq = REDRAW_JPG;
}
break;
// case REDRAW_JPG:
// if (millis() > redrawTimeout) {
// Serial0.println("TimeOut");
// graphReq = REDRAW_NONE;
// dynamicCaptureCallout(345, 400, "ERREUR IMAGE");
// }
// if (newFrame) { // On ne dessine QUE si l'image est prête
// xSemaphoreTake(jpgMutex, portMAX_DELAY);
// if (jpgBuf != nullptr && jpgLen > 0) {
// lcd.drawJpg(jpgBuf, jpgLen, 316, 61, 480, 320); // Précisez taille max si besoin
// }
// newFrame = false;
// xSemaphoreGive(jpgMutex);
// dynamicCaptureCallout(345, 400, "Image en affichage");
// graphReq = REDRAW_NONE; // On a fini le cycle
// }
// break;
case REDRAW_JPG:
// Timeout basé sur l'absence de signal, pas sur un délai fixe
if (imageFailed) {
imageFailed = false;
dynamicCaptureCallout(345, 400, "ERREUR IMAGE");
graphReq = REDRAW_NONE;
break;
}
if (newFrame) {
xSemaphoreTake(jpgMutex, portMAX_DELAY);
if (jpgBuf != nullptr && jpgLen > 0) {
lcd.drawJpg(jpgBuf, jpgLen, 316, 61, 480, 320);
}
newFrame = false;
xSemaphoreGive(jpgMutex);
dynamicCaptureCallout(345, 400, "Image affichée");
graphReq = REDRAW_NONE;
}
break;
}
}
vTaskDelay(50);
};
}
void setup() {
Serial0.begin(115200);
lcd.init();
lcd.setRotation(0);
lcd.setBrightness(255);
lcd.setColorDepth(16);
lcd.setSwapBytes(true); // images générées par image2cpp : couleurs aberrantes sinon !
spriteTete.createSprite(100, 128);
spriteTete.fillScreen(0x1000);
spriteTete.setSwapBytes(true);
spriteTete.pushImage(0, 0, 100, 100, tete_camera);
WiFi.begin(ssid, password);
WiFi.setSleep(false);
jpgMutex = xSemaphoreCreateMutex();
xTaskCreatePinnedToCore(taskFetchCam, "cam", 16384, NULL, 1, NULL, 0);
xTaskCreatePinnedToCore(taskTouchAndDisplay, "UI", 16384, NULL, 2, NULL, 1);
}
void loop() {
vTaskDelay(pdMS_TO_TICKS(1000)); // allègement CPU
}