feat(display): implement DisplayManager with raw MD_MAX72XX
- Add include/display_manager.h: DisplayManager class declaration - Two-zone layout: Zone 0 (top, laser time), Zone 1 (bottom, countdown/status) - showLaserTime(), showCountdown(), showIdle(), showStatus(), setBrightness() - rotateCCW() bitmap transformation for 90 deg physical module rotation - charBitmap() for 17-character set (0-9, space, dash, dot, special chars) - Add src/display_manager.cpp: full implementation - Double-init pattern for SPI power stability - showLaserTime() format: <10 -> ' x.x', <100 -> 'xx.x', <1000 -> ' xxx', else 'xxxx' - showCountdown() right-aligned 4-char format - All methods use writeZone() -> writeChar() -> rotateCCW() -> MD_MAX72XX - Add test_sketches/test_display_manager.cpp: 6-step verification test - allLedsOn/Off, showLaserTime (12 boundary values), showCountdown 5->0 - showIdle, showStatus (Err/AP/WiFi/oF), live simulation loop - Update platformio.ini: add test-display-mgr environment - Update src/main.cpp: integrate display.begin/showIdle/update - Update Implementation-Plan.md: mark Phase 4 tasks 4.1-4.3 complete Tested on hardware: all 6 test steps passed
This commit is contained in:
parent
c63cace1ae
commit
4349b37f05
|
|
@ -77,19 +77,29 @@
|
|||
|
||||
## Phase 4 – Dot-Matrix-Display (`DisplayManager`)
|
||||
|
||||
- [ ] **4.1** `include/display_manager.h` + `src/display_manager.cpp` erstellen
|
||||
Wrapper um `MD_Parola` mit zwei Zonen:
|
||||
- [x] **4.1** `include/display_manager.h` + `src/display_manager.cpp` erstellen
|
||||
Implementierung mit rohem `MD_MAX72XX` (nicht MD_Parola) für vollständige Rotationskontrolle:
|
||||
- Zone 0 (Module 0–3, obere Reihe): Laserzeit in Minuten
|
||||
- Zone 1 (Module 4–7, untere Reihe): Countdown / Statusmeldungen
|
||||
- `rotateCCW()` kompensiert physische 90°-CW-Verdrehung der Module
|
||||
- 17 Zeichen-Bitmaps definiert (0–9, Sonderzeichen, Buchstaben)
|
||||
|
||||
- [ ] **4.2** Methoden implementieren:
|
||||
- `showLaserTime(float minutes)` – obere Zeile
|
||||
- `showCountdown(int seconds)` – untere Zeile (während Gratiszeit)
|
||||
- `showIdle()` – `---` in unterer Zeile wenn kein Countdown
|
||||
- `showError(const char* msg)` – Fehlermeldung in unterer Zeile, Modul 1
|
||||
- `update()` – in `loop()` aufrufen (MD_Parola benötigt regelmäßigen Aufruf)
|
||||
- [x] **4.2** Methoden implementiert:
|
||||
- `showLaserTime(float minutes)` – obere Zeile (4 Formate je Wertebereich)
|
||||
- `showCountdown(int seconds)` – untere Zeile, rechtsbündig (während Gratiszeit)
|
||||
- `showIdle()` – ` --` in unterer Zeile wenn kein Countdown aktiv
|
||||
- `showStatus(const char* msg)` – max. 4-Zeichen-Statusmeldung (untere Zeile)
|
||||
- `setBrightness()`, `allLedsOn()`, `allLedsOff()`, `clear()`, `printToSerial()`
|
||||
- `update()` – in `loop()` aufrufen (no-op, Interface für künftige Animationen)
|
||||
- `main.cpp` integriert: `display.begin()`, `display.showIdle()`, `display.update()`
|
||||
|
||||
- [ ] **4.3** Test: Statische Zahlen und Scrolltext auf beiden Zonen anzeigen
|
||||
- [x] **4.3** Test: `test_sketches/test_display_manager.cpp`, Environment `test-display-mgr`
|
||||
- Alle LEDs EIN/AUS ✅
|
||||
- `showLaserTime()` alle 12 Grenzwerte (0.0 – 9999.0 min) ✅
|
||||
- `showCountdown()` 5→0 ✅
|
||||
- `showIdle()` → ` --` ✅
|
||||
- `showStatus()` mit „Err ", „AP ", „WiFi", „ oF" ✅
|
||||
- Realistischer Loop: Laserzeit steigt, Countdown 20→0, dann Idle ✅
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
97
include/display_manager.h
Normal file
97
include/display_manager.h
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
#pragma once
|
||||
|
||||
// =============================================================================
|
||||
// display_manager.h – Dot-Matrix-Display Ausgabe (8× GYMAX7219, 4×2 Layout)
|
||||
// Projekt: MQTT-Display LaserCutter
|
||||
//
|
||||
// Implementierung mit rohem MD_MAX72XX (kein MD_Parola), damit die physische
|
||||
// 90°-CW Verdrehung der Module per Software (rotateCCW) zuverlässig kompensiert
|
||||
// werden kann – ohne dass MD_Parola Zonenlogik interferiert.
|
||||
//
|
||||
// Zone-Aufteilung:
|
||||
// Zone 0 (top): Module 0–3 → akkumulierte Laserzeit (Float, Minuten)
|
||||
// Zone 1 (bottom): Module 4–7 → Countdown / Status
|
||||
//
|
||||
// Verwendung:
|
||||
// display.begin(); // einmalig in setup()
|
||||
// display.showLaserTime(42.5f);
|
||||
// display.showCountdown(18);
|
||||
// display.showIdle();
|
||||
// display.update(); // in loop() aufrufen (Pflicht)
|
||||
// =============================================================================
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <MD_MAX72xx.h>
|
||||
#include <SPI.h>
|
||||
#include "config.h"
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Maximale Textlänge je Zone (= Anzahl Module pro Zone)
|
||||
// ---------------------------------------------------------------------------
|
||||
#define DISP_CHARS_PER_ZONE DISPLAY_MODULES_PER_ZONE // 4
|
||||
|
||||
class DisplayManager {
|
||||
public:
|
||||
DisplayManager();
|
||||
|
||||
// Initialisierung (einmalig in setup())
|
||||
void begin();
|
||||
|
||||
// Helligkeit setzen 0–15
|
||||
void setBrightness(uint8_t level);
|
||||
|
||||
// ---- Anzeige-Methoden ---------------------------------------------------
|
||||
|
||||
// Obere Zone: Laserzeit in Minuten
|
||||
// < 10 → " x.x" z.B. " 9.5"
|
||||
// < 100 → "xx.x" z.B. "42.5"
|
||||
// < 1000 → " xxx" z.B. " 123"
|
||||
// < 10000 → "xxxx" z.B. "1234"
|
||||
// >= 10000 → "!!!!" (Überlauf)
|
||||
void showLaserTime(float minutes);
|
||||
|
||||
// Untere Zone: Countdown-Sekunden (rechtsbündig)
|
||||
// 0–999s werden dargestellt, > 999 → " !!!"
|
||||
void showCountdown(int seconds);
|
||||
|
||||
// Untere Zone: Leerlauf-Anzeige (" --")
|
||||
void showIdle();
|
||||
|
||||
// Untere Zone: Statustext (max. 4 Zeichen, wird abgeschnitten / aufgefüllt)
|
||||
void showStatus(const char* msg);
|
||||
|
||||
// Beide Zonen löschen
|
||||
void clear();
|
||||
|
||||
// Muss in loop() aufgerufen werden (reserviert für zukünftige Scroll-Animation)
|
||||
void update() {}
|
||||
|
||||
// Alle LEDs ein / aus (Testfunktion)
|
||||
void allLedsOn();
|
||||
void allLedsOff();
|
||||
|
||||
// Debug-Ausgabe auf Serial
|
||||
void printToSerial() const;
|
||||
|
||||
private:
|
||||
MD_MAX72XX _mx;
|
||||
|
||||
// Schreibt genau DISP_CHARS_PER_ZONE Zeichen auf eine Zone
|
||||
// zone 0 → Module 0..3 (oben)
|
||||
// zone 1 → Module 4..7 (unten)
|
||||
// str muss exakt DISP_CHARS_PER_ZONE Zeichen enthalten (kein Null-Terminator nötig)
|
||||
void writeZone(uint8_t zone, const char* str);
|
||||
|
||||
// Schreibt ein Zeichen auf ein einzelnes Modul
|
||||
void writeChar(uint8_t moduleIdx, char c);
|
||||
|
||||
// 90° CCW Rotation – kompensiert physische 90° CW Verdrehung der Module
|
||||
static void rotateCCW(const uint8_t src[8], uint8_t dst[8]);
|
||||
|
||||
// Gibt Zeiger auf 8-Byte-Bitmap für ASCII-Zeichen zurück
|
||||
// Unterstützt: '0'–'9', ' ', '-', '.', '!', 'E', 'r', 'o', 'n', 'A', 'P'
|
||||
static const uint8_t* charBitmap(char c);
|
||||
};
|
||||
|
||||
// Globale Instanz
|
||||
extern DisplayManager display;
|
||||
|
|
@ -108,3 +108,22 @@ build_flags =
|
|||
lib_deps =
|
||||
tzapu/WiFiManager @ ^2.0.17
|
||||
majicDesigns/MD_MAX72XX @ ^3.5.1
|
||||
|
||||
; =============================================================================
|
||||
; TEST ENVIRONMENT 4.3 – DisplayManager Verifikation
|
||||
; Flash: pio run -e test-display-mgr --target upload
|
||||
; Monitor: pio device monitor -e test-display-mgr
|
||||
; =============================================================================
|
||||
[env:test-display-mgr]
|
||||
platform = espressif32
|
||||
board = az-delivery-devkit-v4
|
||||
framework = arduino
|
||||
upload_port = COM3
|
||||
monitor_speed = 115200
|
||||
monitor_echo = yes
|
||||
build_src_filter = -<*> +<../test_sketches/test_display_manager.cpp> +<../src/display_manager.cpp>
|
||||
build_flags =
|
||||
-DCORE_DEBUG_LEVEL=1
|
||||
-Wno-cpp
|
||||
lib_deps =
|
||||
majicDesigns/MD_MAX72XX @ ^3.5.1
|
||||
|
|
|
|||
330
src/display_manager.cpp
Normal file
330
src/display_manager.cpp
Normal file
|
|
@ -0,0 +1,330 @@
|
|||
// =============================================================================
|
||||
// display_manager.cpp – Dot-Matrix-Display Ausgabe
|
||||
// Verwendet rohen MD_MAX72XX mit manuellem 90° CCW Bitmap-Rotations-
|
||||
// ausgleich für die physisch 90° CW verbauten GYMAX7219 Module.
|
||||
// =============================================================================
|
||||
|
||||
#include "display_manager.h"
|
||||
#include <string.h>
|
||||
|
||||
// Globale Instanz
|
||||
DisplayManager display;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Zeichensatz-Bitmaps
|
||||
// Darstellung in AUFRECHTER (normaler) Orientierung. rotateCCW() kompensiert
|
||||
// die physische 90° CW Verdrehung der Module beim Schreiben auf die Hardware.
|
||||
// Je Eintrag: 8 Bytes = 8 Pixelzeilen von oben (Index 0) nach unten (Index 7),
|
||||
// Bit 7 = linke Spalte, Bit 0 = rechte Spalte.
|
||||
// ---------------------------------------------------------------------------
|
||||
static const uint8_t BMP_0[8] = {
|
||||
0b00111100, 0b01000010, 0b01000110, 0b01001010,
|
||||
0b01010010, 0b01100010, 0b00111100, 0b00000000
|
||||
};
|
||||
static const uint8_t BMP_1[8] = {
|
||||
0b00010000, 0b00110000, 0b00010000, 0b00010000,
|
||||
0b00010000, 0b00010000, 0b00111000, 0b00000000
|
||||
};
|
||||
static const uint8_t BMP_2[8] = {
|
||||
0b00111100, 0b01000010, 0b00000010, 0b00001100,
|
||||
0b00010000, 0b00100000, 0b01111110, 0b00000000
|
||||
};
|
||||
static const uint8_t BMP_3[8] = {
|
||||
0b00111100, 0b01000010, 0b00000010, 0b00011100,
|
||||
0b00000010, 0b01000010, 0b00111100, 0b00000000
|
||||
};
|
||||
static const uint8_t BMP_4[8] = {
|
||||
0b00001000, 0b00011000, 0b00101000, 0b01001000,
|
||||
0b01111110, 0b00001000, 0b00001000, 0b00000000
|
||||
};
|
||||
static const uint8_t BMP_5[8] = {
|
||||
0b01111110, 0b01000000, 0b01111100, 0b00000010,
|
||||
0b00000010, 0b01000010, 0b00111100, 0b00000000
|
||||
};
|
||||
static const uint8_t BMP_6[8] = {
|
||||
0b00111100, 0b01000000, 0b01111100, 0b01000010,
|
||||
0b01000010, 0b01000010, 0b00111100, 0b00000000
|
||||
};
|
||||
static const uint8_t BMP_7[8] = {
|
||||
0b01111110, 0b00000010, 0b00000100, 0b00001000,
|
||||
0b00010000, 0b00100000, 0b00100000, 0b00000000
|
||||
};
|
||||
static const uint8_t BMP_8[8] = {
|
||||
0b00111100, 0b01000010, 0b01000010, 0b00111100,
|
||||
0b01000010, 0b01000010, 0b00111100, 0b00000000
|
||||
};
|
||||
static const uint8_t BMP_9[8] = {
|
||||
0b00111100, 0b01000010, 0b01000010, 0b00111110,
|
||||
0b00000010, 0b01000010, 0b00111100, 0b00000000
|
||||
};
|
||||
// Sonderzeichen
|
||||
static const uint8_t BMP_SPACE[8] = {
|
||||
0b00000000, 0b00000000, 0b00000000, 0b00000000,
|
||||
0b00000000, 0b00000000, 0b00000000, 0b00000000
|
||||
};
|
||||
static const uint8_t BMP_MINUS[8] = {
|
||||
0b00000000, 0b00000000, 0b00000000, 0b01111110,
|
||||
0b00000000, 0b00000000, 0b00000000, 0b00000000
|
||||
};
|
||||
static const uint8_t BMP_DOT[8] = { // Dezimalpunkt '.'
|
||||
0b00000000, 0b00000000, 0b00000000, 0b00000000,
|
||||
0b00000000, 0b00000000, 0b00110000, 0b00000000
|
||||
};
|
||||
static const uint8_t BMP_EXCLAM[8] = { // '!' Überlauf-Anzeige
|
||||
0b00010000, 0b00010000, 0b00010000, 0b00010000,
|
||||
0b00010000, 0b00000000, 0b00010000, 0b00000000
|
||||
};
|
||||
static const uint8_t BMP_E[8] = {
|
||||
0b01111110, 0b01000000, 0b01000000, 0b01111100,
|
||||
0b01000000, 0b01000000, 0b01111110, 0b00000000
|
||||
};
|
||||
static const uint8_t BMP_r[8] = { // Kleinbuchstabe 'r'
|
||||
0b00000000, 0b00000000, 0b01011100, 0b01100000,
|
||||
0b01000000, 0b01000000, 0b01000000, 0b00000000
|
||||
};
|
||||
static const uint8_t BMP_o[8] = { // Kleinbuchstabe 'o'
|
||||
0b00000000, 0b00000000, 0b00111100, 0b01000010,
|
||||
0b01000010, 0b01000010, 0b00111100, 0b00000000
|
||||
};
|
||||
static const uint8_t BMP_n[8] = {
|
||||
0b00000000, 0b00000000, 0b01111000, 0b01000100,
|
||||
0b01000010, 0b01000010, 0b01000010, 0b00000000
|
||||
};
|
||||
static const uint8_t BMP_A[8] = {
|
||||
0b00011000, 0b00100100, 0b01000010, 0b01111110,
|
||||
0b01000010, 0b01000010, 0b01000010, 0b00000000
|
||||
};
|
||||
static const uint8_t BMP_P[8] = {
|
||||
0b01111100, 0b01000010, 0b01000010, 0b01111100,
|
||||
0b01000000, 0b01000000, 0b01000000, 0b00000000
|
||||
};
|
||||
static const uint8_t BMP_W[8] = {
|
||||
0b01000001, 0b01000001, 0b01000001, 0b01010101,
|
||||
0b01010101, 0b01100011, 0b01000001, 0b00000000
|
||||
};
|
||||
static const uint8_t BMP_i[8] = {
|
||||
0b00001000, 0b00000000, 0b00001000, 0b00001000,
|
||||
0b00001000, 0b00001000, 0b00001110, 0b00000000
|
||||
};
|
||||
static const uint8_t BMP_F[8] = {
|
||||
0b01111110, 0b01000000, 0b01000000, 0b01111100,
|
||||
0b01000000, 0b01000000, 0b01000000, 0b00000000
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Lookup: ASCII-Zeichen → Bitmap-Zeiger
|
||||
// ---------------------------------------------------------------------------
|
||||
const uint8_t* DisplayManager::charBitmap(char c) {
|
||||
switch (c) {
|
||||
case '0': return BMP_0;
|
||||
case '1': return BMP_1;
|
||||
case '2': return BMP_2;
|
||||
case '3': return BMP_3;
|
||||
case '4': return BMP_4;
|
||||
case '5': return BMP_5;
|
||||
case '6': return BMP_6;
|
||||
case '7': return BMP_7;
|
||||
case '8': return BMP_8;
|
||||
case '9': return BMP_9;
|
||||
case ' ': return BMP_SPACE;
|
||||
case '-': return BMP_MINUS;
|
||||
case '.': return BMP_DOT;
|
||||
case '!': return BMP_EXCLAM;
|
||||
case 'E': return BMP_E;
|
||||
case 'r': return BMP_r;
|
||||
case 'o': return BMP_o;
|
||||
case 'n': return BMP_n;
|
||||
case 'A': return BMP_A;
|
||||
case 'P': return BMP_P;
|
||||
case 'W': return BMP_W;
|
||||
case 'i': return BMP_i;
|
||||
case 'F': return BMP_F;
|
||||
default: return BMP_SPACE;
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 90° CCW Rotation – kompensiert physische 90° CW Verdrehung der Module
|
||||
// ---------------------------------------------------------------------------
|
||||
void DisplayManager::rotateCCW(const uint8_t src[8], uint8_t dst[8]) {
|
||||
memset(dst, 0, 8);
|
||||
for (uint8_t r = 0; r < 8; r++) {
|
||||
for (uint8_t c = 0; c < 8; c++) {
|
||||
if (src[c] & (1 << r)) {
|
||||
dst[r] |= (1 << (7 - c));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Konstruktor
|
||||
// ---------------------------------------------------------------------------
|
||||
DisplayManager::DisplayManager()
|
||||
: _mx(DISPLAY_HW_TYPE, DISPLAY_CS_PIN, DISPLAY_MODULE_COUNT)
|
||||
{}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Doppelte Initialisierung – holt Module nach die beim ersten begin() noch
|
||||
// nicht bereit waren (Power-On Anlaufverzögerung bei ext. Netzteil)
|
||||
// ---------------------------------------------------------------------------
|
||||
static void initMx(MD_MAX72XX& mx, uint8_t brightness) {
|
||||
mx.begin();
|
||||
mx.control(MD_MAX72XX::INTENSITY, brightness);
|
||||
mx.control(MD_MAX72XX::TEST, MD_MAX72XX::OFF);
|
||||
mx.clear();
|
||||
}
|
||||
|
||||
void DisplayManager::begin() {
|
||||
initMx(_mx, DISPLAY_BRIGHTNESS);
|
||||
delay(200);
|
||||
initMx(_mx, DISPLAY_BRIGHTNESS);
|
||||
LOG_I("DISP", "DisplayManager initialisiert (GENERIC_HW, %d Module)", DISPLAY_MODULE_COUNT);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
void DisplayManager::setBrightness(uint8_t level) {
|
||||
if (level > 15) level = 15;
|
||||
_mx.control(MD_MAX72XX::INTENSITY, level);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Einzelnes Zeichen auf ein Modul schreiben (mit CCW Rotation)
|
||||
// ---------------------------------------------------------------------------
|
||||
void DisplayManager::writeChar(uint8_t moduleIdx, char c) {
|
||||
const uint8_t* bmp = charBitmap(c);
|
||||
uint8_t rotated[8];
|
||||
rotateCCW(bmp, rotated);
|
||||
for (uint8_t row = 0; row < 8; row++) {
|
||||
_mx.setRow(moduleIdx, row, rotated[row]);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 4-Zeichen-String auf eine Zone schreiben
|
||||
// zone 0 → Module 0–3, zone 1 → Module 4–7
|
||||
// str muss genau DISP_CHARS_PER_ZONE Zeichen enthalten
|
||||
// ---------------------------------------------------------------------------
|
||||
void DisplayManager::writeZone(uint8_t zone, const char* str) {
|
||||
uint8_t base = zone * DISPLAY_MODULES_PER_ZONE;
|
||||
for (uint8_t i = 0; i < DISPLAY_MODULES_PER_ZONE; i++) {
|
||||
writeChar(base + i, str[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Laserzeit oben anzeigen (Zone 0)
|
||||
// ---------------------------------------------------------------------------
|
||||
void DisplayManager::showLaserTime(float minutes) {
|
||||
char buf[DISP_CHARS_PER_ZONE + 1];
|
||||
|
||||
if (minutes < 0.0f) minutes = 0.0f;
|
||||
|
||||
if (minutes < 10.0f) {
|
||||
// " x.x" → Trennzeichen in Position 2
|
||||
int whole = (int)minutes;
|
||||
int frac = (int)((minutes - whole) * 10.0f + 0.5f);
|
||||
if (frac >= 10) { whole++; frac = 0; }
|
||||
snprintf(buf, sizeof(buf), " %d.%d", whole, frac);
|
||||
} else if (minutes < 100.0f) {
|
||||
// "xx.x"
|
||||
int whole = (int)minutes;
|
||||
int frac = (int)((minutes - whole) * 10.0f + 0.5f);
|
||||
if (frac >= 10) { whole++; frac = 0; }
|
||||
snprintf(buf, sizeof(buf), "%2d.%d", whole, frac);
|
||||
} else if (minutes < 1000.0f) {
|
||||
// " xxx"
|
||||
snprintf(buf, sizeof(buf), "%4d", (int)(minutes + 0.5f));
|
||||
} else if (minutes < 10000.0f) {
|
||||
// "xxxx"
|
||||
snprintf(buf, sizeof(buf), "%4d", (int)(minutes + 0.5f));
|
||||
} else {
|
||||
strlcpy(buf, "!!!!", sizeof(buf));
|
||||
}
|
||||
|
||||
// snprintf kann Länge > 4 produzieren wenn Rounding unerwartet → absichern
|
||||
if (strlen(buf) != DISP_CHARS_PER_ZONE) {
|
||||
strlcpy(buf, "!!!!", sizeof(buf));
|
||||
}
|
||||
|
||||
writeZone(DISPLAY_ZONE_TOP, buf);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Countdown-Sekunden unten anzeigen (Zone 1), rechtsbündig
|
||||
// ---------------------------------------------------------------------------
|
||||
void DisplayManager::showCountdown(int seconds) {
|
||||
char buf[DISP_CHARS_PER_ZONE + 1];
|
||||
|
||||
if (seconds < 0) seconds = 0;
|
||||
|
||||
if (seconds < 10) {
|
||||
snprintf(buf, sizeof(buf), " %d", seconds);
|
||||
} else if (seconds < 100) {
|
||||
snprintf(buf, sizeof(buf), " %d", seconds);
|
||||
} else if (seconds < 1000) {
|
||||
snprintf(buf, sizeof(buf), " %d", seconds);
|
||||
} else {
|
||||
strlcpy(buf, " !!!", sizeof(buf));
|
||||
}
|
||||
|
||||
if (strlen(buf) != DISP_CHARS_PER_ZONE) {
|
||||
strlcpy(buf, " !!!", sizeof(buf));
|
||||
}
|
||||
|
||||
writeZone(DISPLAY_ZONE_BOTTOM, buf);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Leerlauf-Anzeige unten (" --")
|
||||
// ---------------------------------------------------------------------------
|
||||
void DisplayManager::showIdle() {
|
||||
writeZone(DISPLAY_ZONE_BOTTOM, " --");
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Statustext unten (max. 4 Zeichen)
|
||||
// ---------------------------------------------------------------------------
|
||||
void DisplayManager::showStatus(const char* msg) {
|
||||
char buf[DISP_CHARS_PER_ZONE + 1] = " "; // mit Leerzeichen vorbelegen
|
||||
size_t len = strlen(msg);
|
||||
if (len > DISP_CHARS_PER_ZONE) len = DISP_CHARS_PER_ZONE;
|
||||
memcpy(buf, msg, len);
|
||||
buf[DISP_CHARS_PER_ZONE] = '\0';
|
||||
writeZone(DISPLAY_ZONE_BOTTOM, buf);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Beide Zonen löschen
|
||||
// ---------------------------------------------------------------------------
|
||||
void DisplayManager::clear() {
|
||||
_mx.clear();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Alle LEDs ein
|
||||
// ---------------------------------------------------------------------------
|
||||
void DisplayManager::allLedsOn() {
|
||||
for (uint8_t m = 0; m < DISPLAY_MODULE_COUNT; m++) {
|
||||
for (uint8_t r = 0; r < 8; r++) {
|
||||
_mx.setRow(m, r, 0xFF);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Alle LEDs aus
|
||||
// ---------------------------------------------------------------------------
|
||||
void DisplayManager::allLedsOff() {
|
||||
_mx.clear();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
void DisplayManager::printToSerial() const {
|
||||
LOG_I("DISP", "DisplayManager: %d Module, HW: GENERIC_HW, CS: GPIO%d",
|
||||
DISPLAY_MODULE_COUNT, DISPLAY_CS_PIN);
|
||||
LOG_I("DISP", "Zone 0 (oben): Module 0-%d = Laserzeit",
|
||||
DISPLAY_MODULES_PER_ZONE - 1);
|
||||
LOG_I("DISP", "Zone 1 (unten): Module %d-%d = Countdown/Status",
|
||||
DISPLAY_MODULES_PER_ZONE, DISPLAY_MODULE_COUNT - 1);
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@
|
|||
#include "config.h"
|
||||
#include "settings.h"
|
||||
#include "wifi_connector.h"
|
||||
#include "display_manager.h"
|
||||
|
||||
void setup() {
|
||||
Serial.begin(SERIAL_BAUD_RATE);
|
||||
|
|
@ -10,11 +11,16 @@ void setup() {
|
|||
LOG_I("MAIN", "Laser GPIO: %d, Display CS: %d", LASER_SIGNAL_PIN, DISPLAY_CS_PIN);
|
||||
settings.printToSerial();
|
||||
|
||||
display.begin();
|
||||
display.printToSerial();
|
||||
display.showIdle();
|
||||
|
||||
wifiConnector.begin();
|
||||
wifiConnector.printToSerial();
|
||||
}
|
||||
|
||||
void loop() {
|
||||
wifiConnector.loop();
|
||||
display.update();
|
||||
delay(100);
|
||||
}
|
||||
|
|
|
|||
131
test_sketches/test_display_manager.cpp
Normal file
131
test_sketches/test_display_manager.cpp
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
/**
|
||||
* TEST SKETCH 4.3 – DisplayManager Verifikation
|
||||
*
|
||||
* Testet alle Methoden des DisplayManagers:
|
||||
* 1. Alle LEDs EIN/AUS (Modul-Check)
|
||||
* 2. showLaserTime() mit verschiedenen Werten (Grenzwerttest)
|
||||
* 3. showCountdown() hochzählend
|
||||
* 4. showIdle()
|
||||
* 5. showStatus() mit "Err", "AP", "WiFi"
|
||||
* 6. Realistischer Loop: laufende Laserzeit + Countdown simuliert
|
||||
*
|
||||
* Flash: pio run -e test-display-mgr --target upload
|
||||
* Monitor: pio device monitor -e test-display-mgr
|
||||
*/
|
||||
|
||||
#include <Arduino.h>
|
||||
#include "display_manager.h"
|
||||
|
||||
// Pause zwischen Testschritten
|
||||
#define STEP_MS 2000
|
||||
|
||||
static void step(const char* desc) {
|
||||
Serial.printf("[STEP] %s\n", desc);
|
||||
delay(STEP_MS);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
delay(500);
|
||||
|
||||
Serial.println("\n========================================");
|
||||
Serial.println(" TEST 4.3 – DisplayManager");
|
||||
Serial.println("========================================");
|
||||
|
||||
display.begin();
|
||||
display.printToSerial();
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// 1. Alle LEDs EIN → alle 8 Module müssen leuchten
|
||||
// ------------------------------------------------------------------
|
||||
Serial.println("\n[1] Alle LEDs EIN (2s) – alle 8 Module pruefen");
|
||||
display.allLedsOn();
|
||||
delay(2000);
|
||||
display.allLedsOff();
|
||||
delay(500);
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// 2. showLaserTime() – Grenzwerttests
|
||||
// ------------------------------------------------------------------
|
||||
Serial.println("\n[2] showLaserTime() Grenzwerte:");
|
||||
|
||||
float testTimes[] = { 0.0f, 0.5f, 1.0f, 9.9f, 10.0f, 42.5f, 99.9f,
|
||||
100.0f, 123.0f, 999.0f, 1000.0f, 9999.0f };
|
||||
for (float t : testTimes) {
|
||||
Serial.printf(" %.1f min → oben\n", t);
|
||||
display.showLaserTime(t);
|
||||
delay(1500);
|
||||
}
|
||||
display.clear();
|
||||
delay(500);
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// 3. showCountdown() von 120 → 0 (jede Sekunde)
|
||||
// ------------------------------------------------------------------
|
||||
Serial.println("\n[3] showCountdown() 5..0:");
|
||||
for (int s = 5; s >= 0; s--) {
|
||||
Serial.printf(" %d s\n", s);
|
||||
display.showCountdown(s);
|
||||
delay(1000);
|
||||
}
|
||||
delay(500);
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// 4. showIdle()
|
||||
// ------------------------------------------------------------------
|
||||
Serial.println("\n[4] showIdle() (2s)");
|
||||
display.showIdle();
|
||||
step("Erwarte ' --' auf unterer Reihe");
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// 5. showStatus() mit verschiedenen Strings
|
||||
// ------------------------------------------------------------------
|
||||
Serial.println("\n[5] showStatus() Tests:");
|
||||
const char* statMsgs[] = { "Err ", "AP ", "WiFi", " oF" };
|
||||
for (const char* m : statMsgs) {
|
||||
Serial.printf(" '%s'\n", m);
|
||||
display.showStatus(m);
|
||||
delay(STEP_MS);
|
||||
}
|
||||
display.clear();
|
||||
delay(500);
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// 6. Realistischer Betrieb: Laserzeit wächst, Countdown läuft
|
||||
// ------------------------------------------------------------------
|
||||
Serial.println("\n[6] Realistischer Betrieb (30s): Laserzeit + Countdown");
|
||||
Serial.println(" Erwarte: oben steigt, unten zaehlt runter, dann '--'");
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
void loop() {
|
||||
static float totalMin = 42.3f;
|
||||
static int countdown = 20;
|
||||
static bool counting = true;
|
||||
static uint32_t lastSec = 0;
|
||||
static uint32_t lastTenth = 0;
|
||||
|
||||
uint32_t now = millis();
|
||||
|
||||
// Jede 100ms: Laserzeit um ~0.001 min erhöhen (0.06 min/min = 1x Normal)
|
||||
if (now - lastTenth >= 100) {
|
||||
lastTenth = now;
|
||||
totalMin += 0.001f;
|
||||
display.showLaserTime(totalMin);
|
||||
}
|
||||
|
||||
// Jede Sekunde: Countdown herunterzählen
|
||||
if (now - lastSec >= 1000) {
|
||||
lastSec = now;
|
||||
if (counting && countdown > 0) {
|
||||
display.showCountdown(countdown);
|
||||
countdown--;
|
||||
} else if (counting && countdown == 0) {
|
||||
counting = false;
|
||||
display.showIdle();
|
||||
Serial.println("[LIVE] Countdown abgelaufen → Idle");
|
||||
}
|
||||
Serial.printf("[LIVE] %.2f min, countdown=%d\n", totalMin, countdown);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user