feat(phase1): complete hardware setup, display and button test sketches

- platformio.ini: add all 8 libraries via Git URLs, add test-display and
  test-button environments, reduce SPI clock to 1 MHz for stability
- include/config.h: add pin definitions, DISPLAY_HW_TYPE=GENERIC_HW,
  MQTT topics, NVS keys and logging macros
- src/main.cpp: add minimal startup skeleton with config.h include
- test_sketches/test_display.cpp: diagnostic sketch showing 0-based module
  index; GENERIC_HW verified with 90 deg CCW software rotation
- test_sketches/test_button.cpp: GPIO INPUT_PULLUP test; LOW_ACTIVE confirmed
- test_sketches/README.md: add wiring tables, flash commands, results table
- README.md: update HW type to GENERIC_HW, add power supply note (0.5A /
  2.5W measured, external 5V PSU required), add Conventional Commits section
- Implementation-Plan.md: mark tasks 1.1-1.5 as complete

BREAKING CHANGE: none
This commit is contained in:
MaPaLo76 2026-02-22 13:20:52 +01:00
parent d84dfd2119
commit 6ac33f459d
8 changed files with 775 additions and 44 deletions

View File

@ -7,7 +7,7 @@
## Phase 1 Projekt-Setup & Konfiguration
- [ ] **1.1** `platformio.ini` mit allen benötigten Bibliotheken erweitern
- [x] **1.1** `platformio.ini` mit allen benötigten Bibliotheken erweitern
- `MD_Parola`, `MD_MAX72XX`
- `PubSubClient`
- `WiFiManager` (tzapu/WiFiManager)
@ -16,13 +16,24 @@
- `ElegantOTA`
- `Preferences` (ESP32 built-in, kein Extra-Eintrag nötig)
- [ ] **1.2** `include/config.h` erstellen zentrale Pin-Definitionen und Konstanten
- [x] **1.2** `include/config.h` erstellen zentrale Pin-Definitionen und Konstanten
- GPIO-Pins (MAX7219 SPI, Laser-Signal)
- Anzahl der Module (8), Zonen-Aufteilung
- Standard-Werte (Gratiszeit 20s, MQTT Port 1883, etc.)
- MQTT-Topic-Konstanten
- [ ] **1.3** Build prüfen (leeres Projekt kompiliert fehlerfrei)
- [x] **1.3** Build prüfen (leeres Projekt kompiliert fehlerfrei)
- [x] **1.4** Test Verdrahtung Dot-Matrix-Display
- GYMAX7219-Module gemäß Pinbelegung angeschlossen (MOSI GPIO 23, CLK GPIO 18, CS GPIO 5)
- Hardware-Typ `FC16_HW` (nicht GENERIC_HW) verifiziert
- Modul 1 oben-links, Modul 4 oben-rechts, Modul 5 unten-links, Modul 8 unten-rechts
- Zone 0 (0-idx 03) = obere Reihe, Zone 1 (0-idx 47) = untere Reihe
- [x] **1.5** Test Verdrahtung Potentialfreier Schalter (Phase 1: Push Button)
- Push Button an GPIO 4 und GND angeschlossen (INPUT_PULLUP, kein externer Widerstand nötig)
- Pegel verifiziert: Button offen = HIGH, Button gedrückt = LOW → Polarität: LOW_ACTIVE
- Debounce 50 ms funktioniert
---

121
README.md
View File

@ -19,6 +19,7 @@ Dieses Projekt implementiert einen ESP32-basierten MQTT-Client mit Dot-Matrix-Di
11. [Fehlerverhalten](#fehlerverhalten)
12. [Bibliotheken](#bibliotheken)
13. [Build & Flash](#build--flash)
14. [Beitragen / Commits](#beitragen--commits)
---
@ -27,41 +28,90 @@ Dieses Projekt implementiert einen ESP32-basierten MQTT-Client mit Dot-Matrix-Di
| Komponente | Modell / Beschreibung |
|-------------------------|----------------------------------------------------|
| Mikrocontroller | AZ-Delivery ESP32 DevKit V4 |
| Dot-Matrix-Display | 8× GYMAX7219 Module (kompatibel zu MAX7219), 8×8 LEDs je Modul |
| Dot-Matrix-Display | 8× GYMAX7219 Module (kompatibel zu MAX7219), 8×8 LEDs je Modul, Typ: `GENERIC_HW` |
| Display-Anordnung | 4 Module nebeneinander × 2 Reihen = 32×16 LEDs |
| Display-Kaskadierung | Alle 8 Module in einer einzigen SPI-Kette |
| Stromversorgung Display | **Externes 5 V-Netzteil erforderlich** (gemessen: ~0,5 A / ~2,5 W bei 8 Modulen, Vollast bis ~2 A möglich) ESP32 USB-Port reicht nicht aus |
| Laser-Aktivsignal | Potentialfreier Ausgang des Laser Cutters (Optokoppler empfohlen) |
| Optional | Shelly PM Mini G3 als externer Leistungsmesser (separater MQTT-Service) |
| Optional | Shelly PM Mini G3 als externer Leistungszähler (separater MQTT-Service) |
---
## Pinbelegung
| Signal | ESP32 GPIO | Beschreibung |
|---------------|-----------|------------------------------------------------|
| MAX7219 MOSI | GPIO 23 | SPI Data (VSPI) |
| MAX7219 CLK | GPIO 18 | SPI Clock (VSPI) |
| MAX7219 CS | GPIO 5 | SPI Chip Select |
| Laser-Signal | GPIO 4 | Potentialfreier Eingang (INPUT_PULLUP, konfigurierbar invertierbar) |
| Signal | ESP32 GPIO | Beschreibung |
|---------------|-----------|-----------------------------------------------------------|
| MAX7219 MOSI | GPIO 23 | SPI Data (VSPI) DIN Modul 1 |
| MAX7219 CLK | GPIO 18 | SPI Clock (VSPI) |
| MAX7219 CS | GPIO 5 | SPI Chip Select (alle Module) |
| Laser-Signal | GPIO 4 | Potentialfreier Eingang (INPUT_PULLUP, konfigurierbar) |
> Die Polarität des Laser-Signals (HIGH = aktiv oder LOW = aktiv) ist über das Webinterface konfigurierbar.
> Die Polarität des Laser-Signals (`LOW_ACTIVE` oder `HIGH_ACTIVE`) ist über das Webinterface konfigurierbar.
### SPI-Ketten-Schaltbild
```
ESP32 OBERE REIHE UNTERE REIHE
GPIO 23 (MOSI) ─── DIN ┌──────┐ DOUT─DIN ┌──────┐ DOUT─DIN ┌──────┐ DOUT─DIN ┌──────┐
│ M 1 │ │ M 2 │ │ M 3 │ │ M 4 │
│oben │ │ │ │ │ │oben │
│links │ │ │ │ │ │rechts│
GPIO 18 (CLK) ─────────┤ CLK ├──────────┤ CLK ├──────────┤ CLK ├──────────┤ CLK ├──
GPIO 5 (CS) ─────────┤ CS ├──────────┤ CS ├──────────┤ CS ├──────────┤ CS ├──
└──────┘ └──────┘ └──────┘ └──┬───┘
│ DOUT
┌──────┐ DOUT─DIN ┌──────┐ DOUT─DIN ┌──────┐ DOUT─DIN ┌──┴───┐
│ M 8 │ │ M 7 │ │ M 6 │ │ M 5 │
│unten │ │ │ │ │ │unten │
│rechts│ │ │ │ │ │links │
GPIO 18 (CLK) ─────────┤ CLK ├──────────┤ CLK ├──────────┤ CLK ├──────────┤ CLK │
GPIO 5 (CS) ─────────┤ CS ├──────────┤ CS ├──────────┤ CS ├──────────┤ CS │
└──────┘ └──────┘ └──────┘ └──────┘
```
> **Hinweis:** Die untere Reihe läuft im Schaltbild von rechts nach links, weil DOUT von Modul 4
> direkt zu DIN von Modul 5 geführt wird. MD_Parola berücksichtigt das automatisch.
---
## Display
Das Display besteht aus 8 GYMAX7219-Modulen in einer 4×2-Anordnung (32×16 LEDs gesamt).
Es ist in **zwei Zonen** aufgeteilt, die unabhängig voneinander beschrieben werden:
Das Display besteht aus 8 GYMAX7219-Modulen in einer **4×2-Anordnung** (32×16 LEDs gesamt).
Es ist in **zwei Zonen** aufgeteilt, die unabhängig voneinander beschrieben werden.
| Zone | Module (Kette) | Anzeige-Inhalt |
|------|----------------|----------------------------------------|
| Oben | Module 03 | Akkumulierte aktive Laserzeit in **Minuten** (z.B. `42.5 min`) |
| Unten | Module 47 | Laufender **Countdown** in Sekunden der Gratiszeit, danach `---` |
### Physisches Layout
```
DIN ← ESP32 GPIO 23
┌─────────┬─────────┬─────────┬─────────┐
│ Modul 1 │ Modul 2 │ Modul 3 │ Modul 4 │ ← Zone 0 (oben) 0-idx: 03
│ oben │ │ │ oben │ Anzeige: Laserzeit (Minuten)
│ links │ │ │ rechts │
├─────────┼─────────┼─────────┼─────────┤
│ Modul 5 │ Modul 6 │ Modul 7 │ Modul 8 │ ← Zone 1 (unten) 0-idx: 47
│ unten │ │ │ unten │ Anzeige: Countdown / Status
│ links │ │ │ rechts │
└─────────┴─────────┴─────────┴─────────┘
SPI-Kette: ESP32 → M1 → M2 → M3 → M4 → M5 → M6 → M7 → M8
```
**Hardware-Typ:** `GENERIC_HW` (verifiziert durch Hardware-Test; physische Ausrichtung erfordert 90° CCW Software-Rotation)
> **Stromversorgung:** Die 8 Module müssen über ein **externes 5 V-Netzteil** versorgt werden. GND des Netzteils mit ESP32-GND verbinden. Gemessene Leistungsaufnahme im Betrieb: ca. **0,5 A / 2,5 W**; bei allen LEDs EIN (Testmuster) bis ca. 2 A möglich.
### Zonen-Belegung
| Zone | 0-Index-Bereich | Physisch | Anzeige-Inhalt |
|------|-----------------|----------------|---------------------------------------------------------|
| 0 | Modul 03 | Obere Reihe | Akkumulierte aktive Laserzeit in **Minuten** (z.B. `42.5`) |
| 1 | Modul 47 | Untere Reihe | **Countdown** Gratiszeit in Sekunden, danach `---` |
- Die Anzeige aktualisiert sich sekündlich, solange der Laser aktiv ist.
- Bei inaktivem Laser bleibt die letzte gemessene Zeit dauerhaft sichtbar.
- Fehlerzustände (WLAN, MQTT) werden in der **unteren Zeile, erstes Modul** angezeigt.
- Die Bibliotheken `MD_Parola` + `MD_MAX72XX` werden für die Ansteuerung verwendet.
- Fehlerzustände (WLAN, MQTT) werden in Zone 1 (untere Reihe) angezeigt.
- Bibliotheken: `MD_Parola` + `MD_MAX72XX`
---
@ -228,3 +278,40 @@ MQTT-Display-LaserCutter/
├── platformio.ini
└── README.md
```
---
## Beitragen / Commits
Dieses Projekt verwendet **[Conventional Commits](https://www.conventionalcommits.org/)** für alle Git-Commit-Nachrichten.
### Format
```
<type>(<scope>): <kurze Beschreibung>
[optionaler Body]
[optionaler Footer]
```
### Wichtigste Typen
| Typ | Verwendung |
|---|---|
| `feat` | Neues Feature |
| `fix` | Bugfix |
| `docs` | Nur Dokumentation |
| `refactor` | Code-Umstrukturierung ohne Feature/Fix |
| `test` | Test-Sketches / Test-Code |
| `chore` | Build-System, Abhängigkeiten, Konfiguration |
### Beispiele
```
feat(display): add GENERIC_HW rotation compensation
fix(nvs): prevent crash on empty broker string
docs(readme): add power supply requirements
chore(platformio): reduce SPI clock to 1 MHz for stability
```
Breaking Changes werden mit `!` nach dem Typ markiert oder im Footer mit `BREAKING CHANGE:` beschrieben.

104
include/config.h Normal file
View File

@ -0,0 +1,104 @@
#pragma once
// =============================================================================
// config.h Zentrale Pin-Definitionen und Projekt-Konstanten
// Projekt: MQTT-Display LaserCutter
// Board: AZ-Delivery ESP32 DevKit V4
// =============================================================================
// MD_MAX72XX wird hier für den Hardware-Typ-Enum benötigt
#include <MD_MAX72xx.h>
// -----------------------------------------------------------------------------
// MAX7219 / GYMAX7219 SPI-Pinbelegung (VSPI)
// -----------------------------------------------------------------------------
#define DISPLAY_MOSI_PIN 23 // SPI Data
#define DISPLAY_CLK_PIN 18 // SPI Clock
#define DISPLAY_CS_PIN 5 // SPI Chip Select
// Anzahl der MAX7219-Module in der Kette (4 Spalten × 2 Reihen)
#define DISPLAY_MODULE_COUNT 8
// Hardware-Typ: GENERIC_HW für GYMAX7219 Module (verifiziert durch Hardware-Test)
// FC16_HW und PAROLA_HW erzeugen kopfstehende Ziffern
#define DISPLAY_HW_TYPE MD_MAX72XX::GENERIC_HW
// Zonenaufteilung (MD_Parola zones, Zone 0 = obere Reihe, Zone 1 = untere Reihe)
#define DISPLAY_ZONE_TOP 0 // Module 03: Laserzeit in Minuten
#define DISPLAY_ZONE_BOTTOM 1 // Module 47: Countdown / Status
#define DISPLAY_ZONE_COUNT 2
// Module je Zone
#define DISPLAY_MODULES_PER_ZONE (DISPLAY_MODULE_COUNT / DISPLAY_ZONE_COUNT) // 4
// Helligkeit (015)
#define DISPLAY_BRIGHTNESS 7
// -----------------------------------------------------------------------------
// Laser-Signal GPIO
// -----------------------------------------------------------------------------
#define LASER_SIGNAL_PIN 4 // Digitaler Eingang, potentialfreier Schalter
// Debounce-Zeit in Millisekunden
#define LASER_DEBOUNCE_MS 50
// -----------------------------------------------------------------------------
// Gratiszeit (Default, überschreibbar aus NVS)
// -----------------------------------------------------------------------------
#define DEFAULT_GRATIS_SECONDS 20 // Sekunden
#define MIN_GRATIS_SECONDS 0
#define MAX_GRATIS_SECONDS 120
// -----------------------------------------------------------------------------
// MQTT Default-Konfiguration (überschreibbar aus NVS)
// -----------------------------------------------------------------------------
#define DEFAULT_MQTT_BROKER "192.168.1.1"
#define DEFAULT_MQTT_PORT 1883
#define DEFAULT_MQTT_USER ""
#define DEFAULT_MQTT_PASSWORD ""
#define MQTT_CLIENT_ID "lasercutter-display"
#define MQTT_RECONNECT_MS 10000 // Reconnect-Intervall in ms
#define MQTT_HEARTBEAT_MS 60000 // Heartbeat-Intervall in ms
// MQTT Topics
#define MQTT_TOPIC_SESSION "lasercutter/session" // Publish beim Session-Ende
#define MQTT_TOPIC_STATUS "lasercutter/status" // Publish Heartbeat
#define MQTT_TOPIC_RESET "lasercutter/reset" // Subscribe Reset-Befehl
// -----------------------------------------------------------------------------
// WiFiManager
// -----------------------------------------------------------------------------
#define WIFI_AP_NAME "LaserCutter-Setup"
#define WIFI_AP_TIMEOUT_S 120 // Sekunden bis AP-Timeout und Neustart
// -----------------------------------------------------------------------------
// NVS Namespace
// -----------------------------------------------------------------------------
#define NVS_NAMESPACE "lasercutter"
// NVS Keys
#define NVS_KEY_MQTT_BROKER "mqtt_broker"
#define NVS_KEY_MQTT_PORT "mqtt_port"
#define NVS_KEY_MQTT_USER "mqtt_user"
#define NVS_KEY_MQTT_PASSWORD "mqtt_pass"
#define NVS_KEY_GRATIS_SEC "gratis_sec"
#define NVS_KEY_SIGNAL_POL "signal_pol" // 0 = LOW_ACTIVE, 1 = HIGH_ACTIVE
#define NVS_KEY_TOTAL_MINUTES "total_min"
// Signal-Polarität Werte
#define SIGNAL_POL_LOW_ACTIVE 0 // LOW = Laser aktiv (INPUT_PULLUP Default)
#define SIGNAL_POL_HIGH_ACTIVE 1 // HIGH = Laser aktiv
// -----------------------------------------------------------------------------
// Serielles Debugging
// -----------------------------------------------------------------------------
#define SERIAL_BAUD_RATE 115200
// Log-Makros (nur wenn CORE_DEBUG_LEVEL >= 1 via build_flags)
#define LOG_I(tag, fmt, ...) Serial.printf("[I][%s] " fmt "\n", tag, ##__VA_ARGS__)
#define LOG_E(tag, fmt, ...) Serial.printf("[E][%s] " fmt "\n", tag, ##__VA_ARGS__)
// -----------------------------------------------------------------------------
// Watchdog
// -----------------------------------------------------------------------------
#define WDT_TIMEOUT_S 30 // Watchdog-Timeout in Sekunden

View File

@ -12,6 +12,61 @@
platform = espressif32
board = az-delivery-devkit-v4
framework = arduino
; Upload & Monitor
upload_port = COM3
monitor_speed = 9600
monitor_speed = 115200
monitor_echo = yes
monitor_filters = esp32_exception_decoder, default
; Partitionsschema: min_spiffs gibt mehr Platz für OTA (2x ~1.8 MB App)
board_build.partitions = min_spiffs.csv
; Build-Flags
build_flags =
-DCORE_DEBUG_LEVEL=1 ; 0=keine, 1=Fehler, 3=Info, 5=Verbose
-DARDUINO_LOOP_STACK_SIZE=8192
; Bibliotheken
lib_deps =
majicDesigns/MD_Parola @ ^3.7.3
majicDesigns/MD_MAX72XX @ ^3.5.1
knolleary/PubSubClient @ ^2.8
tzapu/WiFiManager @ ^2.0.17
https://github.com/me-no-dev/AsyncTCP.git
https://github.com/me-no-dev/ESPAsyncWebServer.git
bblanchon/ArduinoJson @ ^7.3.0
https://github.com/ayushsharma82/ElegantOTA.git
; =============================================================================
; TEST ENVIRONMENT 1.4 Dot-Matrix-Display Verdrahtungstest
; Flash: pio run -e test-display --target upload
; =============================================================================
[env:test-display]
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.cpp>
; SPI auf 1 MHz reduzieren: robuster bei 8 verketteten Modulen / langen Leitungen
build_flags =
-DMAX_SPI_CLOCK_SPEED=1000000L
-Wno-cpp ; unterdrückt "INFO: ARDUINO SPI interface selected"-Warning aus MD_MAX72XX
lib_deps =
majicDesigns/MD_Parola @ ^3.7.3
majicDesigns/MD_MAX72XX @ ^3.5.1
; =============================================================================
; TEST ENVIRONMENT 1.5 Push Button / Potentialfreier Schalter Verdrahtungstest
; Flash: pio run -e test-button --target upload
; =============================================================================
[env:test-button]
platform = espressif32
board = az-delivery-devkit-v4
framework = arduino
upload_port = COM3
monitor_speed = 115200
monitor_echo = yes
build_src_filter = -<*> +<../test_sketches/test_button.cpp>

View File

@ -1,30 +1,13 @@
#include <Arduino.h>
// Pin-Definition für die LED
const int LED_PIN = 2; // GPIO Pin 2 für die LED
#include "config.h"
void setup() {
// Serial Monitor für Debugging initialisieren
Serial.begin(9600);
// LED Pin als Ausgang konfigurieren
pinMode(LED_PIN, OUTPUT);
Serial.println("Einfaches LED-Programm gestartet");
Serial.println("LED Pin: " + String(LED_PIN));
Serial.println("LED leuchtet dauerhaft");
Serial.begin(SERIAL_BAUD_RATE);
LOG_I("MAIN", "LaserCutter Display gestartet");
LOG_I("MAIN", "Laser GPIO: %d, Display CS: %d", LASER_SIGNAL_PIN, DISPLAY_CS_PIN);
}
void loop() {
// LED einschalten (HIGH = 3.3V)
digitalWrite(LED_PIN, HIGH);
Serial.println("LED ist AN");
delay(100); // 1 Sekunde warten
// LED ausschalten (LOW = 0V)
digitalWrite(LED_PIN, LOW);
Serial.println("LED ist AUS");
delay(100); // 1 Sekunde warten
// Platzhalter wird in den folgenden Phasen implementiert
delay(1000);
}

121
test_sketches/README.md Normal file
View File

@ -0,0 +1,121 @@
# Test Sketches Hardware-Verdrahtungstests
Diese Verzeichnis enthält eigenständige Test-Sketches zur Verifikation der Hardware-Verdrahtung.
Sie laufen als separate PlatformIO-Environments und berühren `main.cpp` nicht.
---
## Physische Modul-Anordnung (GYMAX7219, 4×2)
```
DIN ← ESP32 GPIO 23
┌─────────┬─────────┬─────────┬─────────┐
│ Modul 1 │ Modul 2 │ Modul 3 │ Modul 4 │ ← Obere Reihe (Zone 0, 0-idx: 03)
│ links │ │ │ rechts │
├─────────┼─────────┼─────────┼─────────┤
│ Modul 5 │ Modul 6 │ Modul 7 │ Modul 8 │ ← Untere Reihe (Zone 1, 0-idx: 47)
│ links │ │ │ rechts │
└─────────┴─────────┴─────────┴─────────┘
DOUT Modul 4 → DIN Modul 5 (Reihen verbinden)
```
SPI-Datenfluss: ESP32 → Modul 1 → 2 → 3 → 4 → 5 → 6 → 7 → 8
**Hardware-Typ:** `GENERIC_HW` (verifiziert; 90° CCW Software-Rotation aktiv)
---
## Test 1.4 Dot-Matrix-Display (`test_display.cpp`)
### Zweck
- Alle 8 Module in korrekter Reihenfolge und Ausrichtung prüfen
- Zonentrennung (Zone 0 = oben, Zone 1 = unten) verifizieren
- Realistische Anzeige (Minuten / Countdown) demonstrieren
### Verdrahtung
| ESP32 GPIO | Modul-Pin | Beschreibung |
|-----------|-----------|---------------------|
| GPIO 23 | DIN | SPI Data (VSPI MOSI)|
| GPIO 18 | CLK | SPI Clock |
| GPIO 5 | CS / LOAD | SPI Chip Select |
| GND | GND | Masse (gemeinsam mit ESP32 GND) |
| **5V extern** | VCC | **Externes Netzteil ≥ 1 A** (USB-Port des ESP32 reicht nicht gemessen 0,5 A / 2,5 W) |
> Alle 8 Module teilen CLK und CS. DIN eines Moduls wird mit DOUT des Vorgängers verbunden.
### Flash & Monitor
```bash
pio run -e test-display --target upload
pio device monitor -e test-display
```
### Ablauf
1. **Alle LEDs EIN** (2 s) → prüfen ob alle 8 Module reagieren
2. **ZONE 0** auf oberer Reihe, `------` auf unterer Reihe
3. **ZONE 1** auf unterer Reihe, `------` auf oberer Reihe
4. **Realistische Anzeige**: `42.5` oben, `20` unten
5. **Laufende Zähler** im Loop: Minuten und Countdown-Sekunden
### Erwartetes Ergebnis
- Obere Reihe (Modul 14): zeigt `ZONE 0` bei Schritt 2
- Untere Reihe (Modul 58): zeigt `ZONE 1` bei Schritt 3
- Text erscheint lesbar von links nach rechts (nicht gespiegelt)
---
## Test 1.5 Potentialfreier Schalter / Push Button (`test_button.cpp`)
### Zweck
- GPIO-Eingang mit INPUT_PULLUP testen
- Pegel bei offenem und geschlossenen Schalter dokumentieren
- Debounce-Verhalten prüfen (50 ms)
- Betätigungsdauer (Basis für Gratiszeit-Logik) messen
### Verdrahtung Phase 1: Push Button
```
ESP32 GPIO 4 ────┤ Button ├──── GND
(NO-Kontakt)
```
Kein externer Widerstand nötig der interne Pull-Up (INPUT_PULLUP) ist aktiv.
### Verdrahtung Phase 2: Optokoppler (Laser-Signal)
```
ESP32 GPIO 4 ──── Kollektor
Optokoppler
GND ──── Emitter
```
Gleiche Logik wie Button Laser aktiv = Optokoppler zieht GPIO auf GND.
### Flash & Monitor
```bash
pio run -e test-button --target upload
pio device monitor -e test-button
```
### Erwartetes Ergebnis
| Zustand | GPIO-Pegel | Serial-Ausgabe |
|-------------------|-----------|------------------------|
| Button offen | HIGH | `HIGH (inaktiv)` |
| Button gedrückt | LOW | `LOW → LASER AKTIV` |
→ Polarität: `LOW_ACTIVE` (Standardwert in `config.h` / NVS)
Alle 5 Sekunden erscheint ein Heartbeat mit aktuellem Pegel und Betätigungszähler.
---
## Testergebnisse
| Task | Status | Ergebnis |
|------|--------|----------|
| 1.4 Display | ✅ | GYMAX7219 mit `GENERIC_HW` + 90° CCW Software-Rotation; Modul 1 oben-links, Modul 8 unten-rechts; externes 5 V-Netzteil notwendig |
| 1.5 Button | ✅ | `LOW_ACTIVE`; Debounce 50 ms funktioniert |

View File

@ -0,0 +1,139 @@
/**
* TEST SKETCH 1.5 Potentialfreier Schalter / Push Button Verdrahtungstest
*
* Zweck:
* - Prüft ob der Button/Schalter korrekt am GPIO angeschlossen ist
* - Zeigt HIGH/LOW-Pegel im Seriellen Monitor an
* - Dokumentiert Pegel bei gedrücktem und losgelassenem Zustand
* - Simuliert Debounce wie im späteren LaserTracker
*
* Verdrahtung:
* Schritt 1 (Push Button):
* GPIO 4 Button-Pin 1
* GND Button-Pin 2
* (kein externer Widerstand nötig INPUT_PULLUP ist aktiv)
*
* Erwartetes Verhalten mit INPUT_PULLUP:
* Button OFFEN GPIO = HIGH (3.3V über Pull-Up)
* Button GEDRÜCKT GPIO = LOW (direkt auf GND)
* Polarität: LOW_ACTIVE
*
* Schritt 2 (Optokoppler / potentialfreier Ausgang):
* Gleiche Verdrahtung Kollektor des Optokopplers an GPIO 4, Emitter an GND
*
* Serielle Ausgabe: 115200 Baud
*
* Flash: pio run -e test-button --target upload
* Monitor: pio device monitor -e test-button
*/
#include <Arduino.h>
// ---- Pin-Definition (aus config.h übernommen) ----
#define LASER_SIGNAL_PIN 4
#define DEBOUNCE_MS 50 // Software-Entprellzeit in Millisekunden
// ---- Zustandsvariablen ----
int rawState = HIGH; // aktuell gelesener Rohwert
int stableState = HIGH; // entprellter Zustand
int lastRaw = HIGH; // letzter Rohwert (für Flanken-Erkennung)
uint32_t debounceTimer = 0; // Zeitstempel letzter Flanke
uint32_t pressStart = 0; // Zeitstempel Tastendruck-Beginn
uint32_t pressCount = 0; // Anzahl erkannter Betätigungen
void printState(int s, bool stable) {
if (s == LOW) {
Serial.printf("[%8lu ms] GPIO %-3s = LOW (0V) %s → LASER AKTIV\n",
millis(), stable ? "ENT" : "RAW", stable ? "✓ STABIL" : "");
} else {
Serial.printf("[%8lu ms] GPIO %-3s = HIGH (3.3V) %s → LASER INAKTIV\n",
millis(), stable ? "ENT" : "RAW", stable ? "✓ STABIL" : "");
}
}
void setup() {
Serial.begin(115200);
delay(500);
Serial.println("================================================");
Serial.println(" TEST 1.5 Potentialfreier Schalter / Button");
Serial.println("================================================");
Serial.printf(" GPIO Pin: %d\n", LASER_SIGNAL_PIN);
Serial.printf(" Modus: INPUT_PULLUP\n");
Serial.printf(" Debounce: %d ms\n", DEBOUNCE_MS);
Serial.println("------------------------------------------------");
Serial.println(" Verdrahtung (Push Button):");
Serial.printf(" GPIO %d ──── Button-Pin 1\n", LASER_SIGNAL_PIN);
Serial.println(" GND ──── Button-Pin 2");
Serial.println("------------------------------------------------");
Serial.println(" Erwartetes Verhalten:");
Serial.println(" Button OFFEN → HIGH (kein Strom, Pull-Up aktiv)");
Serial.println(" Button GEDRÜCKT → LOW (GPIO direkt auf GND)");
Serial.println(" → Polarität: LOW_ACTIVE (Standard in config.h)");
Serial.println("================================================");
Serial.println(" Drücke den Button und beobachte die Ausgabe...");
Serial.println();
pinMode(LASER_SIGNAL_PIN, INPUT_PULLUP);
stableState = digitalRead(LASER_SIGNAL_PIN);
lastRaw = stableState;
Serial.printf("[INIT] Startzustand: GPIO = %s\n",
stableState == LOW ? "LOW (Button gedrückt?)" : "HIGH (offen OK)");
Serial.println();
}
void loop() {
rawState = digitalRead(LASER_SIGNAL_PIN);
// Flanke erkannt → Debounce-Timer (re)starten
if (rawState != lastRaw) {
debounceTimer = millis();
lastRaw = rawState;
}
// Nach Debounce-Zeit: stabilen Zustand aktualisieren
if ((millis() - debounceTimer) >= DEBOUNCE_MS) {
if (rawState != stableState) {
stableState = rawState;
if (stableState == LOW) {
// Steigende Flanke (Laser aktiv)
pressCount++;
pressStart = millis();
Serial.println("┌─────────────────────────────────────────┐");
Serial.printf( "│ BETÄTIGUNG #%3lu BEGINN │\n", pressCount);
printState(stableState, true);
Serial.println("└─────────────────────────────────────────┘");
} else {
// Fallende Flanke (Laser inaktiv)
uint32_t duration = millis() - pressStart;
Serial.println("┌─────────────────────────────────────────┐");
Serial.printf( "│ BETÄTIGUNG #%3lu ENDE │\n", pressCount);
printState(stableState, true);
Serial.printf( "│ Dauer: %lu ms (%lu s) │\n",
duration, duration / 1000);
Serial.println("└─────────────────────────────────────────┘");
Serial.println();
// Hinweis für kurze Betätigungen (< Gratiszeit)
if (duration < 20000) {
Serial.printf(" Dauer < 20 s → würde als GRATISZEIT gelten (kein Kostenanteil)\n\n");
}
}
}
}
// Alle 5 Sekunden Heartbeat ausgeben wenn kein Event
static uint32_t lastHeartbeat = 0;
if (millis() - lastHeartbeat >= 5000) {
lastHeartbeat = millis();
Serial.printf("[%8lu ms] Heartbeat | GPIO = %s | Betätigungen: %lu\n",
millis(),
stableState == LOW ? "LOW (AKTIV) " : "HIGH (inaktiv)",
pressCount);
}
}

View File

@ -0,0 +1,231 @@
/**
* TEST SKETCH 1.4 GYMAX7219 Modulnummern-Diagnose
*
* Jedes Modul zeigt seine eigene 0-basierte Index-Nummer (0..7).
* Direkte MD_MAX72XX API kein Parola, keine Zonen.
*
* Ablauf:
* 1. Alle Module zeigen dauerhaft ihre Indexnummer 07
* 2. Alle 3 s wird abwechselnd getestet:
* a) Alle LEDs EIN (volle Helligkeit Stromtest)
* b) Alle LEDs AUS
* c) Jedes Modul zeigt seine Nummer wieder
*
* Serial Monitor zeigt welcher hw-Typ gerade aktiv ist.
* Nummern richtig/lesbar + Reihenfolge notieren, dann in config.h eintragen.
*
* Flash: pio run -e test-display --target upload
* Monitor: pio device monitor -e test-display
*/
#include <Arduino.h>
#include <MD_MAX72xx.h>
#include <SPI.h>
// ---- Pins ----
#define CS_PIN 5
#define NUM_MOD 8
// ---- Zu testende Hardware-Typen ----
// Kommentiere einen aus und flashe erneut, bis die Zahlen AUFRECHT stehen.
// Dann diesen Typ in config.h als DISPLAY_HW_TYPE eintragen.
#define HW_TYPE MD_MAX72XX::GENERIC_HW
//#define HW_TYPE MD_MAX72XX::FC16_HW
//#define HW_TYPE MD_MAX72XX::PAROLA_HW
// Lesbare Bezeichnung für Serial (muss zur obigen Wahl passen)
#define HW_TYPE_NAME "GENERIC_HW"
//#define HW_TYPE_NAME "FC16_HW"
//#define HW_TYPE_NAME "PAROLA_HW"
MD_MAX72XX mx = MD_MAX72XX(HW_TYPE, CS_PIN, NUM_MOD);
// 5-spaltige 7-Segment-ähnliche Ziffern 08 (8 Zeilen, Spalten links→rechts)
// Jeder Eintrag: 8 Bytes = 8 Pixelzeilen von oben nach unten
static const uint8_t DIGIT[9][8] = {
// 0
{ 0b00111100,
0b01000010,
0b01000010,
0b01000010,
0b01000010,
0b01000010,
0b00111100,
0b00000000 },
// 1
{ 0b00010000,
0b00110000,
0b00010000,
0b00010000,
0b00010000,
0b00010000,
0b00111000,
0b00000000 },
// 2
{ 0b00111100,
0b01000010,
0b00000010,
0b00001100,
0b00110000,
0b01000000,
0b01111110,
0b00000000 },
// 3
{ 0b00111100,
0b01000010,
0b00000010,
0b00011100,
0b00000010,
0b01000010,
0b00111100,
0b00000000 },
// 4
{ 0b00001000,
0b00011000,
0b00101000,
0b01001000,
0b01111110,
0b00001000,
0b00001000,
0b00000000 },
// 5
{ 0b01111110,
0b01000000,
0b01000000,
0b01111100,
0b00000010,
0b01000010,
0b00111100,
0b00000000 },
// 6
{ 0b00111100,
0b01000000,
0b01000000,
0b01111100,
0b01000010,
0b01000010,
0b00111100,
0b00000000 },
// 7
{ 0b01111110,
0b00000010,
0b00000100,
0b00001000,
0b00010000,
0b00010000,
0b00010000,
0b00000000 },
// 8
{ 0b00111100,
0b01000010,
0b01000010,
0b00111100,
0b01000010,
0b01000010,
0b00111100,
0b00000000 },
};
// Dreht eine 8x8 Bitmap 90° gegen den Uhrzeigersinn
// Kompensiert die 90° CW physische Ausrichtung der Module
// Formel: new[r][c] = old[c][7-r]
// → new_row[r] |= (1<<(7-c)) wenn old_row[c] Bit r gesetzt ist
void 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));
}
}
}
}
// Schreibt Ziffer d (0-basiert) in Modul moduleIdx mit 90° CCW Korrektur
void showDigit(uint8_t moduleIdx, uint8_t d) {
if (d > 8) d = 8;
uint8_t rotated[8];
rotateCCW(DIGIT[d], rotated);
for (uint8_t row = 0; row < 8; row++) {
mx.setRow(moduleIdx, row, rotated[row]);
}
}
void allLedsOn() { for (uint8_t m=0;m<NUM_MOD;m++) for(uint8_t r=0;r<8;r++) mx.setRow(m,r,0xFF); }
void allLedsOff() { for (uint8_t m=0;m<NUM_MOD;m++) for(uint8_t r=0;r<8;r++) mx.setRow(m,r,0x00); }
void showAllNumbers() {
// Modul 0 zeigt "0", Modul 1 zeigt "1" ... Modul 7 zeigt "7"
// (entspricht physischer Modul-Nummerierung 18 wenn 0-basiert = 07)
for (uint8_t i = 0; i < NUM_MOD; i++) {
showDigit(i, i); // 0-basierter Index als Ziffer
}
}
// Doppelte Initialisierung: alle Module sicher ansprechen, auch wenn
// sie beim ersten begin() noch nicht vollständig gebootet waren.
static void initDisplay() {
mx.begin();
mx.control(MD_MAX72XX::INTENSITY, 5);
mx.control(MD_MAX72XX::TEST, MD_MAX72XX::OFF);
mx.clear();
}
void setup() {
Serial.begin(115200);
// Längere Wartezeit: Module brauchen Zeit zum Hochfahren.
// Ohne dies verpassen Module mit langsamerem Power-On-Rise die Init-Sequenz.
delay(1000);
initDisplay();
// Zweiter Durchlauf nach kurzer Pause holt Module nach die beim ersten
// Aufruf noch nicht bereit waren (z. B. bei externem Netzteil mit Anlaufverzögerung)
delay(200);
initDisplay();
Serial.println("============================================");
Serial.println(" MODUL-DIAGNOSE: Jedes Modul zeigt seinen");
Serial.println(" 0-basierten Index (0 = erstes in Kette)");
Serial.println("============================================");
Serial.printf(" HW-Typ: %s\n", HW_TYPE_NAME);
Serial.println("--------------------------------------------");
Serial.println(" Beobachte welches Modul welche Zahl zeigt:");
Serial.println(" 0 = erstes Modul nach ESP32 DIN");
Serial.println(" 7 = letztes Modul in der Kette");
Serial.println("--------------------------------------------");
Serial.println(" Wenn Ziffern auf dem Kopf stehen:");
Serial.println(" → anderen HW_TYPE auskommentieren + neu flashen");
Serial.println(" Reihenfolge: FC16_HW → PAROLA_HW → GENERIC_HW");
Serial.println("============================================");
// --- Schritt 1: Alle EIN ---
Serial.println("\n[1] Alle LEDs EIN (2 s)");
allLedsOn();
delay(2000);
// --- Schritt 2: Alle AUS ---
Serial.println("[2] Alle LEDs AUS (1 s)");
allLedsOff();
delay(1000);
// --- Schritt 3: Modulnummern ---
Serial.println("[3] Jedes Modul zeigt seinen 0-Index dauerhaft");
showAllNumbers();
Serial.println("\nErwartetes Bild auf der Hardware:");
Serial.println(" [0][1][2][3] ← obere Reihe (idx 0 = links)");
Serial.println(" [4][5][6][7] ← untere Reihe (idx 7 = rechts)");
Serial.println("\nWenn die Ziffern aufrecht und in dieser Reihenfolge");
Serial.println("stehen → HW_TYPE korrekt! In config.h übernehmen.");
}
void loop() {
// Alle 4 s: kurz blinken damit man sieht dass der Controller läuft
static uint32_t last = 0;
if (millis() - last >= 4000) {
last = millis();
allLedsOff();
delay(100);
showAllNumbers();
Serial.println("[LIVE] Module 07 angezeigt");
}
}