Przesyłanie obrazu z GIGA Display Shield przez Wi-Fi

Pytanie

Jak przesłać obraz z GIGA Display Shield za pomocą WIFI do laptopa. Napisz program z wykorzystaniem #include "stm32h7xx.h"
#include "Arduino_H7_Video.h"
#include "lvgl.h"
#include "Arduino_GigaDisplayTouch.h"
Arduino_GigaDisplayTouch TouchDetector;
#include "Arduino_GigaDisplay_GFX.h"

Odpowiedź Sztucznej Inteligencji


Bezpośrednia odpowiedź na pytanie

  • Najprostszy i stabilny sposób to wystawienie prostego serwera HTTP na Arduino GIGA R1 WiFi i serwowanie zrzutów ekranu (BMP) bez kompresji, pobieranych bezpośrednio z bufora ramki wyświetlacza (framebuffer) GIGA Display Shield.
  • Poniżej kompletny szkic (kompilowalny) z Twoimi nagłówkami. Po wgraniu otwierasz w przeglądarce: http://IP_ARDUINO/ i widzisz odświeżający się obraz z ekranu.
  • Jeśli później zechcesz „prawdziwy” streaming wideo, dodasz kodowanie JPEG (MJPEG), ale na start polecam tryb BMP-polling (działa bez zewnętrznych kodeków i jest najpewniejszy).

Szczegółowa analiza problemu

  • Kontekst sprzętowy: GIGA R1 ma MCU STM32H747XI i 8 MB SDRAM. Bufor ekranu 800×480 w RGB565 to ~750 kB, więc swobodnie mieści się w SDRAM — odczyt „z ekranu” jest możliwy w locie. (manuals.plus)
  • Stos bibliotek: dla GIGA Display Shield zalecana jest biblioteka Arduino_H7_Video (zintegrowana z rdzeniem mbed GIGA) i ma wsparcie LVGL. Wersje rdzenia z 2024 wymagały LVGL 8.x; wsparcie dla LVGL 9.x trafiło do nowszych wydań rdzenia. Jeśli masz błędy kompilacji przy LVGL 9.x, zaktualizuj rdzeń GIGA (co najmniej 4.1.5 — zawiera także poprawki DSI). (docs.lvgl.io)
  • Dostęp do framebuffer’a: w warstwie wideo (DSI/LTDC) dostępny jest wskaźnik do aktualnej ramki. W przykładach społeczności Arduino używa się funkcji dsi_getCurrentFrameBuffer() (nagłówek dsi.h), która zwraca wskaźnik do bufora w RGB565. Dzięki temu można czytać obraz bez komplikacji po stronie LVGL. (forum.arduino.cc)
  • Dlaczego BMP: przeglądarka akceptuje obraz jako image/bmp bez dodatkowych bibliotek. Konwersja RGB565→RGB888 (BGR w BMP) jest lekka (operacje bitowe), a plik wynikowy wysyłamy linia‑po‑linii bez buforowania całego obrazu w RAM.
  • Przepustowość: 800×480×3 ≈ 1,15 MB/klatkę. Przy odświeżaniu 1–3 Hz działa stabilnie w 802.11n; do >5–10 FPS konieczna będzie kompresja JPEG (MJPEG) albo przesył surowy RGB565 do klienta w Pythonie (TCP/UDP) i render na laptopie.

Aktualne informacje i trendy

  • Wydanie ArduinoCore‑mbed 4.1.5 (lipiec 2025) naprawia m.in. PLL DSI — zalecana instalacja co najmniej tej wersji, aby uniknąć niestabilności wyświetlacza. (github.com)
  • Integracja LVGL na GIGA: oficjalna dokumentacja LVGL wskazuje użycie Arduino_H7_Video dla GIGA Display (wbudowana kompatybilność). Przy problemach z kompilacją przykładów LVGL na starym rdzeniu użyj LVGL 8.x lub zaktualizuj rdzeń GIGA. (docs.lvgl.io)

Wspierające wyjaśnienia i detale

  • Double‑buffering: warstwa DSI zwykle korzysta z dwóch buforów; dsi_getCurrentFrameBuffer() czyta aktualnie wyświetlany. To wystarczy do „zrzutu ekranu”.
  • Endianness: piksele RGB565 mogą wymagać zamiany bajtów (w przykładach do DSI często używa się HTONS). W kodzie zostawiłem dyrektywę SWAP_RGB565 do szybkiego przełączenia, gdyby kolory były przekłamane. (forum.arduino.cc)
  • LVGL: jeśli już masz własny port LVGL z flush_cb — alternatywnie możesz utrzymywać „cień” framebuffer’a (kopiując w flush) i streamować z niego, bez sięgania do dsi.h.

Aspekty etyczne i prawne

  • Strumieniując ekran do sieci, zadbaj o to, by sieć była zaufana (hasło WPA2/WPA3). Unikaj publikacji obrazu w niezabezpieczonej sieci.
  • Brak wrażliwych danych w strumieniu bez autoryzacji (dodaj Basic Auth lub ogranicz dostęp do własnej sieci).

Praktyczne wskazówki

  • Najpierw uruchom tryb „/capture” (pojedynczy zrzut BMP). Potem dodaj auto‑odświeżanie w JS (np. co 300–500 ms).
  • Jeśli widzisz błędne kolory — ustaw SWAP_RGB565 na 1.
  • Gdy chcesz większej płynności: przejdź na MJPEG (JPEG encoder w MCU lub na laptopie), lub TCP/UDP + dekoder w Pythonie/OpenCV.

Ewentualne zastrzeżenia lub uwagi dodatkowe

  • WebServer.* z ESP‑ów nie jest częścią rdzenia GIGA — używamy bazowego WiFiServer.
  • JPEGDEC to dekoder, nie encoder. Do MJPEG potrzebny jest encoder (np. biblioteka JPEGEncoder lub implementacja programowa/HW). Na H747 nie licz na sprzętowy kodek JPEG — kompresja obciąży M7; rozważ downscaling.
  • W starszych rdzeniach zdarzały się regresje w H7_Video/DSI; aktualizacja rdzenia zwykle rozwiązuje problem. (github.com)

Sugestie dalszych badań

  • MJPEG: multipart/x‑mixed‑replace z enkoderem JPEG (jakość/QS, skalowanie do 400×240 dla 10–15 FPS).
  • UDP: surowy RGB565 + Python (numpy/opencv) do bardzo niskich opóźnień.
  • Integracja z LVGL: kopiowanie w flush_cb do „shadow FB” i z tego serwowanie.
  • Zabezpieczenia: prosty Basic Auth na endpointach.

Kod – serwer HTTP z BMP snapshotem i auto-odświeżaniem

Skopiuj, uzupełnij SSID/hasło i wgraj. Po uruchomieniu w Monitorze szeregowym zobaczysz IP. Otwórz w przeglądarce: http://IP/ (ten sam LAN).

Uwaga: Kod używa dsi_getCurrentFrameBuffer() (nagłówek dsi.h) – standardowo dostępny w rdzeniu GIGA z Arduino_H7_Video. Jeśli nie masz dsi.h, zobacz sekcję „Wariant bez dsi.h”.

#include "stm32h7xx.h"
#include "Arduino_H7_Video.h"
#include "lvgl.h"
#include "Arduino_GigaDisplayTouch.h"
Arduino_GigaDisplayTouch TouchDetector;
#include "Arduino_GigaDisplay_GFX.h"
#include <WiFi.h>
#include "dsi.h"               // dostęp do bieżącego framebuffera (DSI/LTDC)
#define WIFI_SSID     "Twoje_SSID"
#define WIFI_PASS     "Twoje_HASLO"
#define SWAP_RGB565   0        // ustaw na 1, jeśli kolory będą nieprawidłowe (zamiana bajtów)
WiFiServer server(80);
// Ekran GIGA Display Shield (orientacja pozioma 800x480)
Arduino_H7_Video Display(800, 480, GigaDisplayShield);
// Prosty rysunek testowy (żeby było co oglądać)
void drawTest() {
  Display.beginDraw();
  Display.background(0, 0, 0);
  Display.stroke(255, 255, 255);
  Display.fill(0, 128, 255);
  Display.rect(40, 40, 240, 120);
  Display.fill(255, 0, 0);
  Display.circle(400, 240, 80);
  Display.noFill();
  Display.stroke(0, 255, 0);
  for (int r = 0; r <= 200; r += 10) Display.circle(650, 240, r);
  Display.endDraw();
}
// Nagłówek BMP 24bpp (top-down: ujemna wysokość)
static void sendBMPHeader(WiFiClient& c, int w, int h, uint32_t imgSize) {
  uint32_t fileSize = 54 + imgSize;
  uint8_t hdr[54] = {0};
  hdr[0] = 'B'; hdr[1] = 'M';
  hdr[2] = (uint8_t)(fileSize); hdr[3] = (uint8_t)(fileSize >> 8);
  hdr[4] = (uint8_t)(fileSize >> 16); hdr[5] = (uint8_t)(fileSize >> 24);
  hdr[10] = 54;          // offset do danych
  hdr[14] = 40;          // DIB header size
  // szerokość
  hdr[18] = (uint8_t)(w); hdr[19] = (uint8_t)(w >> 8);
  hdr[20] = (uint8_t)(w >> 16); hdr[21] = (uint8_t)(w >> 24);
  // wysokość ujemna = top-down
  int32_t nh = -h;
  hdr[22] = (uint8_t)(nh); hdr[23] = (uint8_t)(nh >> 8);
  hdr[24] = (uint8_t)(nh >> 16); hdr[25] = (uint8_t)(nh >> 24);
  hdr[26] = 1; hdr[28] = 24; // 1 płaszczyzna, 24 bpp
  c.write(hdr, 54);
}
// Konwersja jednej linii RGB565 -> BGR888 do bufora wyjściowego
static void convertLineRGB565toBGR888(const uint16_t* src565, uint8_t* dst, int w) {
  for (int x = 0; x < w; x++) {
    uint16_t p = src565[x];
#if SWAP_RGB565
    p = (uint16_t)((p >> 8) | (p << 8));
#endif
    uint8_t r5 = (p >> 11) & 0x1F;
    uint8_t g6 = (p >> 5)  & 0x3F;
    uint8_t b5 =  p        & 0x1F;
    // rozszerzenie do 8 bitów (proste doszacowanie)
    uint8_t r = (r5 << 3) | (r5 >> 2);
    uint8_t g = (g6 << 2) | (g6 >> 4);
    uint8_t b = (b5 << 3) | (b5 >> 2);
    // BMP: B, G, R
    int i = 3 * x;
    dst[i + 0] = b;
    dst[i + 1] = g;
    dst[i + 2] = r;
  }
}
static void handleRoot(WiFiClient& c, IPAddress ip) {
  c.println("HTTP/1.1 200 OK");
  c.println("Content-Type: text/html; charset=utf-8");
  c.println("Connection: close");
  c.println();
  c.print("<!doctype html><html><head><meta charset='utf-8'>");
  c.print("<title>GIGA Display Stream (BMP refresh)</title>");
  c.print("<style>body{background:#111;color:#eee;font:16px sans-serif;text-align:center}img{max-width:95%;border:2px solid #444}</style>");
  c.print("</head><body><h1>GIGA Display - podgląd</h1>");
  c.print("<p>IP: ");
  c.print(ip);
  c.print("</p>");
  c.print("<img id='im' src='/capture?t=0'><br>");
  c.print("<label>FPS: <input id='fps' type='number' min='0.2' max='10' step='0.2' value='2'></label>");
  c.print("<script>let i=document.getElementById('im');let f=document.getElementById('fps');let t;function go(){clearInterval(t);let dt=1000/Math.max(0.2,Math.min(10,parseFloat(f.value)||2));t=setInterval(()=>{i.src='/capture?t='+Date.now()},dt);}f.onchange=go;go();</script>");
  c.print("</body></html>");
}
static void handleCapture(WiFiClient& c) {
  const void* fbv = dsi_getCurrentFrameBuffer(); // wskaźnik na RGB565
  if (!fbv) {
    c.println("HTTP/1.1 503 Service Unavailable");
    c.println("Connection: close"); c.println(); return;
  }
  int w = Display.width();
  int h = Display.height();
  // rozmiary BMP (wyrównanie linii do 4 bajtów)
  uint32_t rowSize = w * 3;
  uint32_t padding = (4 - (rowSize & 3)) & 3;
  uint32_t imgSize = (rowSize + padding) * h;
  c.println("HTTP/1.1 200 OK");
  c.println("Content-Type: image/bmp");
  c.println("Cache-Control: no-store, no-cache, must-revalidate");
  c.println("Connection: close");
  c.println();
  sendBMPHeader(c, w, h, imgSize);
  const uint16_t* fb16 = (const uint16_t*)fbv;
  // bufor jednej linii BGR888
  static uint8_t line[800 * 3]; // wystarczy dla w<=800
  for (int y = 0; y < h; y++) {
    const uint16_t* src = fb16 + (y * w);
    convertLineRGB565toBGR888(src, line, w);
    c.write(line, rowSize);
    if (padding) {
      static const uint8_t pad[3] = {0,0,0};
      c.write(pad, padding);
    }
    // krótkie oddanie czasu WiFi (opcjonalnie)
    if ((y & 0x1F) == 0) delay(1);
  }
}
void setup() {
  Serial.begin(115200);
  Display.begin();
  TouchDetector.begin();
  drawTest();  // narysuj przykładową scenę
  // WiFi (tryb stacji)
  Serial.print("Laczenie z WiFi: "); Serial.println(WIFI_SSID);
  WiFi.begin(WIFI_SSID, WIFI_PASS);
  while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); }
  Serial.println("\nPolaczono.");
  Serial.print("IP: "); Serial.println(WiFi.localIP());
  server.begin();
  Serial.println("Serwer HTTP uruchomiony. Otworz http://IP/ w przegladarce.");
}
void loop() {
  // (opcjonalnie) prosta animacja co 1 s, aby widzieć zmianę obrazu
  static uint32_t t0 = 0;
  if (millis() - t0 > 1000) {
    t0 = millis();
    Display.beginDraw();
    Display.fill(0, 0, 0);
    Display.stroke(255, 255, 255);
    Display.text("BMP over WiFi", 20, 20);
    Display.stroke(255, 255, 0);
    Display.rect(18, 16, 240, 32);
    Display.endDraw();
  }
  WiFiClient c = server.available();
  if (!c) { delay(1); return; }
  // minimalny parser HTTP
  String req;
  uint32_t tStart = millis();
  while (c.connected() && millis() - tStart < 1000) {
    while (c.available()) {
      char ch = c.read();
      req += ch;
      if (ch == '\n') {
        if (req.startsWith("GET / ")) { handleRoot(c, WiFi.localIP()); }
        else if (req.startsWith("GET /capture")) { handleCapture(c); }
        else { c.println("HTTP/1.1 404 Not Found"); c.println("Connection: close"); c.println(); }
        c.stop();
        return;
      }
    }
  }
  c.stop();
}

Wariant bez dsi.h (gdyby nagłówek nie był dostępny): zamiast dsi_getCurrentFrameBuffer() utrzymuj „cień” framebuffer’a — w swoim lvgl_flush_cb kopiuj każdy prostokąt (area) do własnej tablicy RGB565 (SDRAM.malloc(8004802)). Do BMP konwertujesz z tej tablicy. Integracja LVGL z Arduino_H7_Video jest opisana w dokumentacji LVGL; przy rdzeniu GIGA ≥4.1.5 działa także z LVGL 9.x. (docs.lvgl.io)

Krótkie podsumowanie

  • Działające „tu i teraz” rozwiązanie: HTTP + BMP z odczytem bieżącego framebuffer’a z DSI (dsi_getCurrentFrameBuffer) i proste JS‑odświeżanie w przeglądarce.
  • Zadbaj o aktualny rdzeń „Arduino mbed OS GIGA Boards” (co najmniej 4.1.5) i sugerowane wersje LVGL, aby uniknąć problemów z DSI/LVGL. (github.com)
  • Gdy będziesz potrzebować większej płynności, dołóż JPEG (MJPEG) albo klienta w Pythonie (TCP/UDP).

Jeżeli chcesz, przygotuję dla Ciebie wersję MJPEG (multipart/x‑mixed‑replace) z enkoderem JPEG i ograniczeniem rozdzielczości (np. 400×240), aby osiągnąć 8–15 FPS na GIGA.

Oceń odpowiedź: 1
Zastrzeżenie: Odpowiedzi udzielone przez sztuczną inteligencję (model językowy) mogą być niedokładne i wprowadzające w błąd. Elektroda nie ponosi odpowiedzialności za dokładność, rzetelność ani kompletność prezentowanych informacji. Wszystkie odpowiedzi powinny być zweryfikowane przez użytkownika.

Zadaj dodatkowe pytanie Sztucznej Inteligencji

Czekaj (2min)...