HINWEIS: Befindet sich in der Testphase!
HINWEIS: team-nessoN1-meshcore muss aus Gesundheitlichen Gründen Pausieren. Wer mag kann Ihn wegen Details der Projektübergabe unter E-Mail — team-nessoN1-meshcore kontaktieren.
Autor: team-nessoN1-meshcore — team-nessoN1-meshcore@posteo.de
Stand: April 2026
Diese Seite beschreibt die Hardware-Abstraktionsschicht (HAL) für den Arduino Nesso N1 als MeshCore-Zielplattform. Das Board wurde gemeinsam von Arduino und M5Stack entwickelt und kombiniert einen ESP32-C6 Mikrocontroller mit einem SX1262 LoRa-Transceiver, Wi-Fi 6, Bluetooth 5.3, Thread/Zigbee und einem 1,14″-Touchscreen in einem kompakten, batteriebetriebenen Gehäuse.
| Komponente | Details |
|---|---|
| MCU | Espressif ESP32-C6 (RISC-V, 160 MHz) |
| Flash | 16 MB |
| RAM | 512 KB |
| LoRa | Semtech SX1262, 850–960 MHz, bis +22 dBm |
| Antennenschalter / LNA | Extern, gesteuert über I²C-GPIO-Expander |
| I²C-GPIO-Expander | PI4IOE5V6408 (2× Instanzen, Adressen 0x43 und 0x44) |
| Display | ST7789P3, 1,14″, 240×135 px, Touchscreen (FT6336U) |
| IMU | BMI270 (6-Achsen) |
| Akku | 250 mAh LiPo, USB-C Laden |
| Konnektivität | Wi-Fi 6, BT 5.3, Thread/Zigbee (802.15.4), LoRa |
Anders als bei den meisten anderen MeshCore-Zielplattformen sind beim Nesso N1 die LoRa-Steuerleitungen nicht direkt mit GPIO-Pins des ESP32-C6 verbunden, sondern laufen über einen PI4IOE5V6408 I²C-GPIO-Expander (Adresse 0x43):
| Signal | Expander-Pin | Funktion |
|---|---|---|
| SX_NRST | P7 | Hardware-Reset des SX1262 |
| SX_ANT_SW | P6 | Antennen-RF-Schalter |
| SX_LNA_EN | P5 | Low-Noise-Amplifier |
| KEY1 | P0 | Taste A (Input) |
| KEY2 | P1 | Taste B (Input) |
Konsequenz: RadioLib's setRfSwitchTable() funktioniert nicht (nur direkte GPIOs). NessoN1Board implementiert stattdessen das RfSwitchCallback-Interface, das CustomSX1262Wrapper bei jedem TX/RX-Übergang aufruft.
| Signal | GPIO |
|---|---|
| MOSI | G21 |
| MISO | G22 |
| SCK | G20 |
| LoRa CS (NSS) | G23 (Software-CS) |
| LCD CS | G17 (Software-CS) |
| LCD DC | G16 |
lora_spi.begin() wird ohne NSS-Argument aufgerufen. Mit NSS würde Arduino einen APB-Hardware-CS-Callback für GPIO23 registrieren; M5GFX tut dasselbe für GPIO17 — zwei Callbacks auf demselben Bus führen zu addApbChangeCallback: duplicate und SPI-Korruption.
Der ESP32-C6 hat keinen VSPI-Bus: SPIClass lora_spi(FSPI).
| Firmware | ARDUINO_USB_CDC_ON_BOOT | Debug-Output |
|---|---|---|
| Repeater | 1 | USB-Serial aktiv |
| Companion BLE | 0 | kein USB-Serial |
| Companion WiFi | 0 | kein USB-Serial |
Das ST7789P3 hat 240×135 Pixel (Landscape, setRotation(1)). Alle UITask-Standardlayouts passen ohne Überlauf:
| Element | Breite | x (zentriert) | Rechte Kante | Passt |
|---|---|---|---|---|
| MeshCore-Logo (64×36 px XBM) | 64 px | 88 | 152 | ✅ |
| Versionszeile (setTextSize 2) | 156 px | 42 | 198 | ✅ |
| Node-Name „NessoN1 Repeater„ | 192 px | 24 | 216 | ✅ |
| Frequenzzeile „869.6 SF8 BW62.5“ | 204 px | 18 | 222 | ✅ |
Alle vier Elemente belegen zusammen ~100 px von 135 px Höhe — kein vertikaler Überlauf.
radio_init() in target.cpp: 1. board.begin() SPI2, Wire, Expander, loraReset() 2. board.onRfRx() RF-Switch für Kalibrierung 3. radio.std_init(nullptr) RadioLib — nullptr: kein zweites spi->begin() 4. display.begin() M5GFX Lazy Construction — zwingend nach std_init!
Datum: April 2026
Betroffene Datei: platformio.ini, [env:NessoN1_repeater_display]
Symptom: Display zeigt Boot-Screen (~4 Sek.) und Home-Screen (~10 Sek.), dann dauerhaft schwarz. Kein Wakeup durch Tastendruck.
Ursache: UITask.h (MeshCore, examples/simple_repeater/UITask.h) definiert den Makro SCREEN_TIMEOUT (Standardwert: 10 000 ms). Nach Ablauf wird intern display.turnOff() aufgerufen. Der Wakeup-Pfad in UITask prüft PIN_USER_BTN — einen direkten GPIO-Pin. Auf dem Nesso N1 hängen KEY1 und KEY2 über I²C-Expander 0 (Adresse 0x43, Pins P0/P1). UITask hat damit keinen Wakeup-Mechanismus — das Display bleibt nach Timeout-Ablauf permanent schwarz.
Fix:
; platformio.ini — [env:NessoN1_repeater_display]: -D SCREEN_TIMEOUT=0 -D DISPLAY_TIMEOUT=0
Beide Flags auf 0 deaktivieren den Timeout vollständig. DISPLAY_TIMEOUT wird zusätzlich gesetzt, weil verschiedene UITask-Versionen unterschiedliche Makronamen verwenden.
Hinweis Akku-Betrieb: Sinnvoller Wert z.B.-D SCREEN_TIMEOUT=30000. Der Button-Wakeup ist übernesso_ui_tick()bereits integriert (siehe Fix 7).
Datum: April 2026
Betroffene Datei: NessoDisplayDriver.cpp
Symptom: Serial meldet M5GFX meldet Display: 135 x 240 px. UITask-Texte werden mit negativem x-Offset abgeschnitten und sind nicht lesbar.
Ursache: setRotation(1) wurde in begin() nach _gfx→width() / _gfx→height() aufgerufen. M5GFX tauscht die Dimensionen erst nach setRotation() intern um. Zusätzlich trat das Problem auf, wenn display.begin() in main.cpp vor radio_init() aufgerufen wurde: Der idempotente Guard beim zweiten begin()-Aufruf hat setRotation(1) dann gar nicht mehr ausgeführt.
Fix in NessoDisplayDriver.cpp:
// setRotation ZUERST — M5GFX tauscht Dimensionen erst danach: _gfx->setRotation(1); // Jetzt korrekte Werte: Serial.printf("[display] M5GFX bereit: %d x %d px\n", _gfx->width(), _gfx->height()); // Ausgabe: 240 x 135 px ✓
Datum: April 2026
Betroffene Datei: NessoN1Board.cpp
Symptom: ERROR: radio init failed: -2, Serial zeigt [loraReset] TIMEOUT!.
Ursache: examples/simple_repeater/main.cpp ruft unter #ifdef DISPLAY_CLASS in setup() explizit board.begin() auf — vor radio_init(). Auf dem Nesso N1 führt board.begin() intern loraReset() aus: der SX1262 bootet, BUSY geht HIGH. Anschließend ruft radio_init() erneut board.begin() auf → zweites loraReset() → SX1262 bootet erneut, BUSY wieder HIGH → radio.std_init() trifft Chip mitten im Bootvorgang → RADIOLIB_ERR_CHIP_NOT_FOUND = -2.
Fix in NessoN1Board::begin():
static bool s_boardBeginCalled = false; if (s_boardBeginCalled) { Serial.println("[board] begin() bereits initialisiert — Guard greift"); return; // loraReset() wird NICHT nochmals ausgeführt } s_boardBeginCalled = true;
Vollständige Lösung: In main.cpp board.begin() und display.begin() aus setup() entfernen — beide werden intern durch radio_init() erledigt (siehe main_cpp_setup_hinweis.txt).
Datum: April 2026
Betroffene Dateien: NessoN1Board.cpp, NessoDisplayDriver.cpp
Symptom:
[E][Wire.cpp:131] setPins(): bus already initialized. [E][esp32-hal-cpu.c:123] addApbChangeCallback(): duplicate func=... [E][esp32-hal-cpu.c:146] removeApbChangeCallback(): not found func=...
Ursache: main.cpp ruft display.begin() vor radio_init() auf. M5GFX ruft intern spi_bus_initialize(SPI2_HOST) und Wire.begin() auf. Wenn anschließend radio_init() → board.begin() → lora_spi.begin() läuft, versucht der ESP-IDF SPI-Treiber denselben APB-Callback erneut zu registrieren → Duplikat.
Fix: Der Board-Guard (Fix 3) verhindert den zweiten lora_spi.begin()-Aufruf. Die drei [E]-Zeilen sind funktional harmlos, aber ein sichtbarer Indikator für die unvollständig behobene Aufruf-Reihenfolge.
Datum: April 2026
Betroffene Datei: NessoN1Board.cpp
Symptom: Log meldet BUSY direkt nach NRST HIGH: LOW (unerwartet) obwohl Reset korrekt funktioniert und std_init erfolgreich ist.
Ursache: Der delay(20) nach NRST HIGH war zu lang. Der SX1262 auf dem Nesso N1 hat einen BUSY-HIGH-Puls von unter 1 ms nach dem Reset — der Chip hatte BUSY längst wieder LOW gezogen, wenn digitalRead() nach 20 ms lief.
Fix:
_exp0.digitalWrite(NESSO_EXP0_SX_NRST, true); delay(2); // war: delay(20) — 2 ms reicht, BUSY-Puls auf Nesso N1 < 1 ms
Logmeldung geändert zu:
[loraReset] BUSY 2 ms nach NRST HIGH: LOW (Chip hat BUSY-Puls bereits abgeschlossen — normal auf Nesso N1)
Datum: April 2026
Betroffene Dateien: simple_repeater/main.cpp, simple_repeater/UITask.h, simple_repeater/UITask.cpp, simple_repeater/MyMesh.h
Symptom: KEY1 und KEY2 reagieren scheinbar nicht. Im Serial-Log erscheint [ui] KEY1 erkannt — die Taste wird erkannt, aber es passiert nichts am Display.
Ursache: nesso_ui_tick() gibt bei erkannter Taste -1 (KEY1) oder -2 (KEY2) zurück, wenn das Display bereits an ist. In main.cpp war die Auswertung dieser Werte vollständig auskommentiert. Zusätzlich hatte UITask keine navigierbaren Screens und keine Verbindung zu MyMesh.
Fix — drei Ebenen:
1. Neues Interface UIActions.h — entkoppelt UITask von MyMesh:
class UIActions { public: virtual void uiGetNeighborList(char* buf, int bufSize) = 0; virtual void uiSendDiscover() = 0; };
2. MyMesh implementiert UIActions — in MyMesh.h:
class MyMesh : public mesh::Mesh, public CommonCLICallbacks, public UIActions { void uiGetNeighborList(char* buf, int bufSize) override { formatNeighborsReply(buf); } void uiSendDiscover() override { sendNodeDiscoverReq(); } };
3. UITask bekommt 3 Screens + Navigation, verbunden via setActions():
SCREEN_HOME — Node-Name, Frequenz, SF/BW/CR SCREEN_NEIGHBOURS — Nachbarliste (HEX-ID, Alter, SNR) SCREEN_POLL_SENT — Feedback "Poll gesendet" für 2 Sek.
4. main.cpp wertet btn aus (vorher auskommentiert):
if (btn == -1) ui_task.nextScreen(); // KEY1: vorwärts / Poll if (btn == -2) ui_task.prevScreen(); // KEY2: zurück
Bedienung nach dem Patch:
| Taste | Display AUS | Display AN (Home) | Display AN (Nachbarn) |
|---|---|---|---|
| KEY1 | Wakeup | → Nachbarn-Screen | → Poll senden |
| KEY2 | Wakeup | bleibt Home | → Home-Screen |
Nach dem Poll zeigt der Screen 2 Sekunden „Poll gesendet“, dann wechselt er automatisch zurück zur aktualisierten Nachbarliste.
Datum: April 2026
Betroffene Dateien: NessoDisplayDriver.cpp, NessoDisplayDriver.h, platformio.ini
Symptom: KEY1 und KEY2 werden trotz korrekter Flanken-Erkennung nicht oder unzuverlässig registriert. Im Serial-Log:
[E][Wire.cpp:131] setPins(): bus already initialized. change pins only when not. [E][esp32-hal-cpu.c:123] addApbChangeCallback(): duplicate func=...
Ursache: M5GFX initialisiert den I²C-Bus intern über WireInternal.begin(SDA, SCL). Der eigene PI4IOE5V6408-Treiber in NessoN1Board initialisiert Wire ebenfalls direkt. Beide greifen auf denselben Bus (SDA=GPIO10, SCL=GPIO8) zu mit unterschiedlichen TwoWire-Instanzen → Bus-Kollisionen. Dokumentiert im Arduino Forum (November 2025).
Fix: M5Unified übernimmt Button-Handling und Display-Init koordiniert. M5.begin() initialisiert Wire, M5GFX und GPIO-Expander in der richtigen Reihenfolge.
// NessoDisplayDriver.cpp — begin(): M5.begin(cfg); // statt: _gfx->begin() _gfx = &M5.Display; // statt: static M5GFX gfx_instance // checkButtons(): M5.update(); if (M5.BtnA.wasPressed()) return 1; // KEY1 if (M5.BtnB.wasPressed()) return 2; // KEY2
; platformio.ini — nesso_n1_base: lib_deps = m5stack/M5GFX m5stack/M5Unified ; NEU: Fix 8
Entfernt: _prevBtnState, _btnReadyAt, _lastBtnCheck — manueller Debounce entfällt, M5Unified macht Flanken-Erkennung intern.
cd <projektwurzel> cp variants/arduino_nesso_n1/credentials.ini.example credentials.ini
credentials.ini anpassen:
[credentials] build_flags_repeater = -D ADMIN_PASSWORD='"dein_passwort"' build_flags_wifi = -D WIFI_SSID='"dein_wlan"' -D WIFI_PWD='"dein_passwort"'
credentials.iniist in.gitignoreund wird nicht ins Repository eingecheckt.
Repeater mit Display (empfohlen als Einstieg):
pio run -e NessoN1_repeater_display --target upload pio device monitor -b 115200
Repeater ohne Display:
pio run -e NessoN1_repeater --target upload
Companion Radio — BLE:
pio run -e NessoN1_companion_ble --target upload
Companion Radio — WiFi/TCP:
pio run -e NessoN1_companion_wifi --target upload
[board] begin() gestartet [board] I2C-Scan: [board] 0x43 ← Expander 0 (LoRa/Keys) GEFUNDEN [board] 0x44 ← Expander 1 (Display) GEFUNDEN [loraReset] BUSY 2 ms nach NRST HIGH: LOW (normal auf Nesso N1) [loraReset] BUSY=LOW nach 0 ms — SX1262 bereit [board] begin() abgeschlossen [display] M5GFX bereit: 240 x 135 px (nach setRotation(1)) [display] ST7789 bereit, Backlight ein [init] === radio_init() START === [init] 3/8: board.begin() [board] begin() bereits initialisiert — Guard greift [radio] std_init OK [init] === radio_init() abgeschlossen — Radio bereit === Repeater ID: ...
Drei [E]-Zeilen (Wire.setPins, APB-Duplikat) erscheinen noch solange main.cpp die falsche Aufruf-Reihenfolge hat — sie sind harmlos.
| Funktion | GPIO |
|---|---|
| LoRa MOSI | G21 |
| LoRa MISO | G22 |
| LoRa SCK | G20 |
| LoRa CS (NSS) | G23 |
| LoRa BUSY | G19 |
| LoRa IRQ (DIO1) | G15 |
| LCD CS | G17 |
| LCD DC | G16 |
| I²C SDA | G10 |
| I²C SCL | G8 |
| Touch INT | G3 |
| Funktion | Expander-Pin |
|---|---|
| Taste A (KEY1) | P0 |
| Taste B (KEY2) | P1 |
| LNA Enable | P5 |
| Antennen-Switch | P6 |
| LoRa Reset (NRST) | P7 |
| Funktion | Expander-Pin |
|---|---|
| LCD Reset | P1 |
| LCD Backlight | P6 |
| Modus | ANT_SW | LNA_EN | Beschreibung |
|---|---|---|---|
| TX | HIGH | LOW | Sende-Pfad aktiv, LNA geschützt |
| RX | HIGH | HIGH | Empfangs-Pfad mit LNA aktiv |
| IDLE | LOW | LOW | HF-Pfad getrennt, Stromsparmodus |
target.cpp
└─ radio_init()
├─ board.begin() SPI2, Wire, I²C-Scan, Expander, loraReset()
├─ rtc_clock.begin(Wire)
├─ board.onRfRx() RF-Switch für Kalibrierung
├─ radio.std_init(nullptr) kein zweites spi->begin()
├─ radio_driver.setRfSwitchCallback(&board)
└─ display.begin() M5GFX Lazy Construction — nach std_init
NessoDisplayDriver
├─ begin() static M5GFX; setRotation(1) vor width()/height()
└─ checkButtons() Polling KEY1/KEY2 via I²C-Expander 0 (50ms Debounce)
NessoN1Board (RfSwitchCallback)
├─ begin() Guard (einmaliger Aufruf) + loraReset()
├─ onRfTx() ANT_SW=HIGH, LNA_EN=LOW
├─ onRfRx() ANT_SW=HIGH, LNA_EN=HIGH
└─ onRfIdle() ANT_SW=LOW, LNA_EN=LOW
variants/arduino_nesso_n1/ ├── NessoN1Board.h — Pin-Konstanten, PI4IOE5V6408-Treiber, Board-Klasse ├── NessoN1Board.cpp — begin() mit Guard (Fix 3), loraReset() (Fix 5) ├── NessoDisplayDriver.h — DisplayDriver-Erweiterung, Lazy Construction ├── NessoDisplayDriver.cpp — begin() mit setRotation-Fix (Fix 2) ├── target.h — Externe Deklarationen, nesso_check_buttons() ├── target.cpp — radio_init() Pflichtsequenz, globale Objekte ├── platformio.ini — incl. SCREEN_TIMEOUT=0 (Fix 1) ├── credentials.ini.example — Vorlage für lokale Zugangsdaten └── main_cpp_setup_hinweis.txt — Hinweis zur korrekten main.cpp-Struktur (Fix 3/4) src/helpers/ui/ └── DisplayDriver.h — Abstraktes Interface (Pure-Virtuals) src/helpers/radiolib/ ├── RfSwitchCallback.h — Abstraktes Interface für externen RF-Switch └── CustomSX1262Wrapper.h — SX1262-Wrapper mit RF-Switch-Callback
| # | Thema | Status |
|---|---|---|
| 1 | main.cpp: board.begin() / display.begin() vor radio_init() entfernen | Workaround aktiv (Guard) |
| 2 | KEY1/KEY2 als UITask Screen-Wakeup einhängen | offen |
| 3 | Touch FT6336U (I²C 0x38) integrieren | offen |
| 4 | IMU BMI270 (I²C 0x68) für Auto-Rotation | offen |
| 5 | Akku-Monitor (I²C 0x49) für Display-Anzeige | offen |
MIT-Lizenz, identisch mit MeshCore. Vollständiger Text im Wurzelverzeichnis des Repositories.