« ESP32 écran, affichage d'un fond de carte » : différence entre les versions

De Wiki de Mémoire Vive
Aller à la navigation Aller à la recherche
Aucun résumé des modifications
Aucun résumé des modifications
 
(2 versions intermédiaires par le même utilisateur non affichées)
Ligne 1 : Ligne 1 :
L'objectif serait de pouvoir afficher un fond de carte sur un module ESP32 écran, par exemple pour suivre des positions GPS.
L'objectif serait de pouvoir afficher un fond de carte sur un module ESP32 écran, par exemple pour suivre des positions GPS.


Pour l'instant il ne s'agit que de la partie affichage, mais qui ne fonctionne pas. Le test porte sur la récupération d'une tuile (un morceau de carte), c'est à dire d'un fichier d'une image en format PNG. La récupération se fait bien, mais pas l'affichage. C'est la bibliothèque PNGdec qui est utilisée.
Pour l'instant il ne s'agit que de la partie affichage, et qui ne fonctionne. Le test porte sur la récupération d'une tuile (un morceau de carte), c'est à dire d'un fichier d'une image en format PNG. La récupération et l'affichage se font bien. C'est la bibliothèque PNGdec qui est utilisée.


Exemple d'URL de récupération de tuile :  
Exemple d'URL de récupération de tuile :  
Ligne 17 : Ligne 17 :


const char* ssid = "2.4GHz-MEMOIRE-VIVE";
const char* ssid = "2.4GHz-MEMOIRE-VIVE";
const char* password = "************";
const char* password = "xxxxxxxxxxx";
TFT_eSPI tft = TFT_eSPI();
TFT_eSPI tft = TFT_eSPI();
PNG png;
PNG png;


// === Callback appelé par PNGdec pour chaque ligne de pixels ===
int pngDraw(PNGDRAW *pDraw) {
int pngDraw(PNGDRAW *pDraw) {
   static int offsetX = 0;
   static int offsetX = 0;
   static int offsetY = 0;
   static int offsetY = 0;
   if (pDraw->y == 0) {
   if (pDraw->y == 0) {
     int width = pDraw->iWidth;
     int width = pDraw->iWidth;
Ligne 29 : Ligne 31 :
     offsetX = (tft.width() - width) / 2;
     offsetX = (tft.width() - width) / 2;
     offsetY = (tft.height() - height) / 2;
     offsetY = (tft.height() - height) / 2;
     Serial.printf("Image %dx%d centrée à (%d,%d)\n", width, height, offsetX, offsetY);
 
     Serial.printf("\n=== Début du rendu PNG ===\n");
    Serial.printf("Dimensions PNG : %d x %d\n", width, height);
    Serial.printf("Offset calculé : (%d, %d)\n", offsetX, offsetY);
    Serial.printf("Heap libre avant rendu : %d\n", ESP.getFreeHeap());
  }
 
  if (pDraw->y % 10 == 0)
    Serial.printf("Décodage ligne %d / %d\n", pDraw->y, png.getHeight());
 
  if (pDraw->iWidth > 512) {
    Serial.printf("⚠️ Ligne trop large (%d px)\n", pDraw->iWidth);
    return 0;
   }
   }
   static uint16_t lineBuffer[512]; // en principe l'image est <= 256
 
   static uint16_t lineBuffer[512];
   png.getLineAsRGB565(pDraw, lineBuffer, PNG_RGB565_LITTLE_ENDIAN, 0xFFFF);
   png.getLineAsRGB565(pDraw, lineBuffer, PNG_RGB565_LITTLE_ENDIAN, 0xFFFF);
  if (pDraw->y < 3) {
    Serial.printf("Premiers pixels ligne %d : ", pDraw->y);
    for (int i = 0; i < min(4, pDraw->iWidth); i++)
      Serial.printf("%04X ", lineBuffer[i]);
    Serial.println();
  }
   tft.pushImage(offsetX, offsetY + pDraw->y, pDraw->iWidth, 1, lineBuffer);
   tft.pushImage(offsetX, offsetY + pDraw->y, pDraw->iWidth, 1, lineBuffer);
  if (pDraw->y == png.getHeight() - 1) {
    Serial.printf("=== Fin du rendu PNG ===\n");
    Serial.printf("Heap libre après rendu : %d\n", ESP.getFreeHeap());
  }
   return 1;
   return 1;
}
}


// --- Télécharge et affiche une tuile OSM avec debug complet ---
void showTile(int zoom, int x, int y) {
void showTile(int zoom, int x, int y) {
   Serial.printf("Free heap avant téléchargement: %d\n", ESP.getFreeHeap());
   Serial.printf("Heap avant téléchargement: %d\n", ESP.getFreeHeap());


   WiFiClientSecure client;
   WiFiClientSecure client;
   client.setInsecure();
   client.setInsecure();
   HTTPClient https;
   HTTPClient https;
   char url[128];
   char url[128];
   sprintf(url, "https://tile.openstreetmap.org/%d/%d/%d.png", zoom, x, y);
   sprintf(url, "https://tile.openstreetmap.org/%d/%d/%d.png", zoom, x, y);
Ligne 52 : Ligne 83 :
   }
   }


   https.addHeader("User-Agent", "Mozilla/5.0 (ESP32) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0 Safari/604.1");
   https.addHeader("User-Agent", "Mozilla/5.0 (ESP32)");
 
   int httpCode = https.GET();
   int httpCode = https.GET();
   if (httpCode != HTTP_CODE_OK) {
   if (httpCode != HTTP_CODE_OK) {
Ligne 61 : Ligne 93 :
   }
   }


   int len = https.getSize();
   WiFiClient *stream = https.getStreamPtr();
  if (len <= 0 || len >= 50000) {
    Serial.println("Taille invalide !");
    https.end();
    return;
  }


   uint8_t *buf = (uint8_t*)malloc(len);
   // --- Lecture dynamique du flux (chunked safe) ---
  if (!buf) {
  std::vector<uint8_t> buf;
    Serial.println("Erreur malloc !");
  unsigned long t0 = millis();
     https.end();
  while (https.connected()) {
    return;
    while (stream->available()) {
      buf.push_back(stream->read());
    }
    if (millis() - t0 > 10000) {
      Serial.println("⏱ Timeout lecture !");
      break;
    }
     delay(1);
   }
   }


  WiFiClient *stream = https.getStreamPtr();
   int index = buf.size();
   int index = 0;
  while (https.connected() && index < len) {
    int bytes = stream->read(buf + index, len - index);
    if (bytes <= 0) break;
    index += bytes;
  }
   Serial.printf("Téléchargé %d octets\n", index);
   Serial.printf("Téléchargé %d octets\n", index);


   // Calculer un checksum simple pour vérifier l'intégrité
   // --- Vérification du magic number PNG ---
   uint32_t checksum = 0;
   if (index >= 8) {
  for (int i = 0; i < index; i++) {
    Serial.print("Magic number début : ");
     checksum += buf[i];
    for (int i = 0; i < 8; i++) Serial.printf("%02X ", buf[i]);
    Serial.println();
 
    Serial.print("Magic number fin : ");
     for (int i = max(0, index - 8); i < index; i++) Serial.printf("%02X ", buf[i]);
    Serial.println();
   }
   }
  Serial.printf("Checksum du buffer: %u\n", checksum);


   // Afficher le "magic number"
   // --- Vérifier IEND ---
   Serial.print("Magic number: ");
   bool hasIEND = false;
  for (int i = 0; i < 8; i++) {
  if (index >= 8) {
     Serial.printf("%02X ", buf[i]);
    uint8_t tail[8];
    for (int i = 0; i < 8; i++) tail[i] = buf[index - 8 + i];
     hasIEND = (tail[0] == 0x49 && tail[1] == 0x45 && tail[2] == 0x4E &&
              tail[3] == 0x44 && tail[4] == 0xAE && tail[5] == 0x42 &&
              tail[6] == 0x60 && tail[7] == 0x82);
   }
   }
   Serial.println();
   if (!hasIEND) Serial.println("⚠️ PNG incomplet (IEND manquant)");


   // Essayer de décoder le PNG
   // --- Calcul checksum simple ---
   int rc = png.openRAM(buf, index, pngDraw);
  uint32_t checksum = 0;
  for (uint8_t c : buf) checksum += c;
  Serial.printf("Checksum buffer: %u\n", checksum);
 
  // --- Décodage PNG ---
   int rc = png.openRAM(buf.data(), index, pngDraw);
   Serial.printf("Résultat openRAM = %d\n", rc);
   Serial.printf("Résultat openRAM = %d\n", rc);


   if (rc == PNG_SUCCESS) {
   if (rc == PNG_SUCCESS) {
    Serial.println("✅ PNG ouvert avec succès");
     tft.startWrite();
     tft.startWrite();
     tft.fillScreen(TFT_BLACK);
     tft.fillScreen(TFT_BLACK);
     png.decode(NULL, 0);
     int decRes = png.decode(NULL, 0);
     tft.endWrite();
     tft.endWrite();
     Serial.println("Tuile affichée !");
     Serial.printf("Résultat decode() = %d\n", decRes);
   } else {
   } else {
     Serial.printf("Erreur PNG (%d)\n", rc);
     Serial.println("Erreur PNGdec :");
     // Afficher le début du buffer comme texte (pour voir s'il y a une erreur cachée)
     switch (rc) {
    buf[min(index, len - 1)] = '\0';
      case PNG_MEM_ERROR: Serial.println(-> Erreur mémoire"); break;
    Serial.println("Contenu du buffer (début, 100 premiers octets):");
      case PNG_INVALID_PARAMETER: Serial.println(" -> Paramètres invalides"); break;
    for (int i = 0; i < min(100, index); i++) {
      case PNG_INVALID_FILE: Serial.println("  -> Fichier PNG invalide ou corrompu"); break;
       Serial.printf("%02X ", buf[i]);
       case PNG_TOO_BIG: Serial.println(" -> PNG trop grand"); break;
       if ((i + 1) % 20 == 0) Serial.println();
       default: Serial.printf("  -> Code inconnu (%d)\n", rc); break;
     }
     }
    Serial.println();
   }
   }


  free(buf);
   https.end();
   https.end();
   png.close();
   png.close();
   Serial.printf("Free heap après libération: %d\n", ESP.getFreeHeap());
   Serial.printf("Heap après libération: %d\n", ESP.getFreeHeap());
}
}


void setup() {
void setup() {
Ligne 142 : Ligne 181 :
   Serial.println(WiFi.localIP());
   Serial.println(WiFi.localIP());


  // Affichage du message de chargement
  tft.fillScreen(TFT_BLUE);
  tft.setTextColor(TFT_WHITE, TFT_BLUE);
  delay(1000);
  tft.fillScreen(TFT_RED);
  delay(1000);
  tft.fillScreen(TFT_GREEN);
  delay(1000);
  tft.fillScreen(TFT_WHITE);
  delay(1000);
   tft.setTextDatum(MC_DATUM);
   tft.setTextDatum(MC_DATUM);
   tft.drawString("Chargement carte...", tft.width()/2, tft.height()/2);
   tft.drawString("Chargement carte...", tft.width()/2, tft.height()/2);
Ligne 158 : Ligne 187 :
}
}


void loop() {
void loop() {}
}
 
</pre>
</pre>


Et la sortie de la console,
Et la sortie de la console,
<pre>
<pre>
Connexion WiFi à 2.4GHz-MEMOIRE-VIVE......
WiFi connecté !
WiFi connecté !
192.168.1.221
192.168.1.221
Free heap avant téléchargement: 183400
Heap avant téléchargement: 183556
https://tile.openstreetmap.org/15/16614/11304.png
https://tile.openstreetmap.org/15/16614/11304.png
Téléchargé 4113 octets
⏱ Timeout lecture !
Checksum du buffer: 530546
Téléchargé 9638 octets
Magic number: 89 50 4E 47 0D 0A 1A 0A  
Magic number début : 89 50 4E 47 0D 0A 1A 0A  
Magic number fin : 49 45 4E 44 AE 42 60 82
Checksum buffer: 1171860
Résultat openRAM = 0
Résultat openRAM = 0
Tuile affichée !
✅ PNG ouvert avec succès
Free heap après libération: 127544
 
</pre>
=== Début du rendu PNG ===
Dimensions PNG : 256 x 256
Offset calculé : (112, 32)
Heap libre avant rendu : 120428</pre>

Dernière version du 15 octobre 2025 à 10:01

L'objectif serait de pouvoir afficher un fond de carte sur un module ESP32 écran, par exemple pour suivre des positions GPS.

Pour l'instant il ne s'agit que de la partie affichage, et qui ne fonctionne. Le test porte sur la récupération d'une tuile (un morceau de carte), c'est à dire d'un fichier d'une image en format PNG. La récupération et l'affichage se font bien. C'est la bibliothèque PNGdec qui est utilisée.

Exemple d'URL de récupération de tuile :

https://tile.openstreetmap.org/15/16614/11304.png

Voici le code, à ce stade,

#include <WiFi.h>
#include <WiFiClientSecure.h>
#include <HTTPClient.h>
#include <TFT_eSPI.h>
#include <PNGdec.h>

const char* ssid = "2.4GHz-MEMOIRE-VIVE";
const char* password = "xxxxxxxxxxx";
TFT_eSPI tft = TFT_eSPI();
PNG png;

// === Callback appelé par PNGdec pour chaque ligne de pixels ===
int pngDraw(PNGDRAW *pDraw) {
  static int offsetX = 0;
  static int offsetY = 0;

  if (pDraw->y == 0) {
    int width = pDraw->iWidth;
    int height = png.getHeight();
    offsetX = (tft.width() - width) / 2;
    offsetY = (tft.height() - height) / 2;

    Serial.printf("\n=== Début du rendu PNG ===\n");
    Serial.printf("Dimensions PNG : %d x %d\n", width, height);
    Serial.printf("Offset calculé : (%d, %d)\n", offsetX, offsetY);
    Serial.printf("Heap libre avant rendu : %d\n", ESP.getFreeHeap());
  }

  if (pDraw->y % 10 == 0)
    Serial.printf("Décodage ligne %d / %d\n", pDraw->y, png.getHeight());

  if (pDraw->iWidth > 512) {
    Serial.printf("⚠️ Ligne trop large (%d px)\n", pDraw->iWidth);
    return 0;
  }

  static uint16_t lineBuffer[512];
  png.getLineAsRGB565(pDraw, lineBuffer, PNG_RGB565_LITTLE_ENDIAN, 0xFFFF);

  if (pDraw->y < 3) {
    Serial.printf("Premiers pixels ligne %d : ", pDraw->y);
    for (int i = 0; i < min(4, pDraw->iWidth); i++)
      Serial.printf("%04X ", lineBuffer[i]);
    Serial.println();
  }

  tft.pushImage(offsetX, offsetY + pDraw->y, pDraw->iWidth, 1, lineBuffer);

  if (pDraw->y == png.getHeight() - 1) {
    Serial.printf("=== Fin du rendu PNG ===\n");
    Serial.printf("Heap libre après rendu : %d\n", ESP.getFreeHeap());
  }

  return 1;
}

// --- Télécharge et affiche une tuile OSM avec debug complet ---
void showTile(int zoom, int x, int y) {
  Serial.printf("Heap avant téléchargement: %d\n", ESP.getFreeHeap());

  WiFiClientSecure client;
  client.setInsecure();
  HTTPClient https;

  char url[128];
  sprintf(url, "https://tile.openstreetmap.org/%d/%d/%d.png", zoom, x, y);
  Serial.println(url);

  if (!https.begin(client, url)) {
    Serial.println("Erreur HTTPS.begin()");
    return;
  }

  https.addHeader("User-Agent", "Mozilla/5.0 (ESP32)");

  int httpCode = https.GET();
  if (httpCode != HTTP_CODE_OK) {
    Serial.printf("Erreur HTTP: %d\n", httpCode);
    Serial.printf("Réponse: %s\n", https.getString().c_str());
    https.end();
    return;
  }

  WiFiClient *stream = https.getStreamPtr();

  // --- Lecture dynamique du flux (chunked safe) ---
  std::vector<uint8_t> buf;
  unsigned long t0 = millis();
  while (https.connected()) {
    while (stream->available()) {
      buf.push_back(stream->read());
    }
    if (millis() - t0 > 10000) {
      Serial.println("⏱ Timeout lecture !");
      break;
    }
    delay(1);
  }

  int index = buf.size();
  Serial.printf("Téléchargé %d octets\n", index);

  // --- Vérification du magic number PNG ---
  if (index >= 8) {
    Serial.print("Magic number début : ");
    for (int i = 0; i < 8; i++) Serial.printf("%02X ", buf[i]);
    Serial.println();

    Serial.print("Magic number fin : ");
    for (int i = max(0, index - 8); i < index; i++) Serial.printf("%02X ", buf[i]);
    Serial.println();
  }

  // --- Vérifier IEND ---
  bool hasIEND = false;
  if (index >= 8) {
    uint8_t tail[8];
    for (int i = 0; i < 8; i++) tail[i] = buf[index - 8 + i];
    hasIEND = (tail[0] == 0x49 && tail[1] == 0x45 && tail[2] == 0x4E &&
               tail[3] == 0x44 && tail[4] == 0xAE && tail[5] == 0x42 &&
               tail[6] == 0x60 && tail[7] == 0x82);
  }
  if (!hasIEND) Serial.println("⚠️ PNG incomplet (IEND manquant)");

  // --- Calcul checksum simple ---
  uint32_t checksum = 0;
  for (uint8_t c : buf) checksum += c;
  Serial.printf("Checksum buffer: %u\n", checksum);

  // --- Décodage PNG ---
  int rc = png.openRAM(buf.data(), index, pngDraw);
  Serial.printf("Résultat openRAM = %d\n", rc);

  if (rc == PNG_SUCCESS) {
    Serial.println("✅ PNG ouvert avec succès");
    tft.startWrite();
    tft.fillScreen(TFT_BLACK);
    int decRes = png.decode(NULL, 0);
    tft.endWrite();
    Serial.printf("Résultat decode() = %d\n", decRes);
  } else {
    Serial.println("❌ Erreur PNGdec :");
    switch (rc) {
      case PNG_MEM_ERROR: Serial.println("  -> Erreur mémoire"); break;
      case PNG_INVALID_PARAMETER: Serial.println("  -> Paramètres invalides"); break;
      case PNG_INVALID_FILE: Serial.println("  -> Fichier PNG invalide ou corrompu"); break;
      case PNG_TOO_BIG: Serial.println("  -> PNG trop grand"); break;
      default: Serial.printf("  -> Code inconnu (%d)\n", rc); break;
    }
  }

  https.end();
  png.close();
  Serial.printf("Heap après libération: %d\n", ESP.getFreeHeap());
}

void setup() {
  Serial.begin(115200);
  tft.init();
  tft.setRotation(1);
  tft.fillScreen(TFT_BLACK);

  Serial.printf("Connexion WiFi à %s...", ssid);
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("\nWiFi connecté !");
  Serial.println(WiFi.localIP());

  tft.setTextDatum(MC_DATUM);
  tft.drawString("Chargement carte...", tft.width()/2, tft.height()/2);

  showTile(15, 16614, 11304);
}

void loop() {}

Et la sortie de la console,

Connexion WiFi à 2.4GHz-MEMOIRE-VIVE......
WiFi connecté !
192.168.1.221
Heap avant téléchargement: 183556
https://tile.openstreetmap.org/15/16614/11304.png
⏱ Timeout lecture !
Téléchargé 9638 octets
Magic number début : 89 50 4E 47 0D 0A 1A 0A 
Magic number fin : 49 45 4E 44 AE 42 60 82 
Checksum buffer: 1171860
Résultat openRAM = 0
✅ PNG ouvert avec succès

=== Début du rendu PNG ===
Dimensions PNG : 256 x 256
Offset calculé : (112, 32)
Heap libre avant rendu : 120428