Status: experimentell. Der Werksflash über den Browser (Bootloader + App +
BOOTEND-Fuse, Bench-Punkte 1+2) ist am Gerät verifiziert (10.09.2026). Die Firmware-Verteilung über den Bus (Punkte 3–8): erster realer Test am Gerät (19.09.2026) deckte einen Bug im Bootloader auf (derselbe DI-Pin-Fehler wie in der App vorf24b861, eigene USART0-Initialisierung — behoben in3d65464, Firmware v1.9).21.09.2026 — erste vollständige End-zu-Ende-Übertragung am Gerät bestätigt (Adresse 1,
FW_BEGIN→FW_DATA→FW_END→GET_VERSIONohne einen einzigen Retry, neue Version perGET_VERSIONbestätigt). Dabei zeigte sich aber ein zweiter, eigenständiger Bug: eine Karte, die mitten inFW_DATA/FW_ENDhängen bleibt (App bereits ungültig markiert, Bootloader wartet laut Design korrekt auf ein neuesFW_BEGIN), beantwortetCMD_GET_STATUSnicht mehr und gilt dem Master daher als „offline". „Alle aktualisieren"/„Veraltete aktualisieren" filtern aber beide auf online — eine so hängen gebliebene Karte konnte dadurch nie wieder erreicht werden, nur noch per Browser-Werksflash. Fix (firmware v1.14): neuer Button „Offline erneut versuchen" +handle_module_update()lässt explizit angefragte Adressen immer zu, unabhängig vom Online-Status (siehefirmware/CHANGELOG.md).pio run -e attiny1616 -t upload(App @ 0x0000, ohne Bootloader) bleibt der abgesicherte Weg, wenn eine Karte dennoch per Browser-Werksflash zurückgeholt werden muss – erst ab Firmware 1.16: Der Upload schreibt keine Fuses, eine werksgeflashte Karte behältBOOTEND = 0x0C. Ältere Plain-Apps setzen dann IVSEL nicht, ihre Interrupts landen mitten im Code und die Zeitbasis steht (Issue #19); ab 1.16 setzt die Plain-App IVSEL selbst.Stand 1.16 (Review-Fixes, Issues #29/#30/#41): Der Bootloader (BL- Version 2) startet nach einem
FW_BEGINbis zum erfolgreichenFW_ENDin keinem Pfad die App – auch nicht nach Busstille. Die Wiederholung des zuletzt angenommenen Chunks wird quittiert,FW_BEGINstartet jederzeit neu, der Zeitablauf wird nur von eigenen Rahmen zurückgesetzt. Er nimmt nur Unicast an die eigene Adresse an (bei ungültiger EEPROM-Adresse 250), wartet 200 µs vor jeder Antwort, das Startfenster dauert echte 2,5 s (TCB0). Als „App startbar" gelten nur die Marker 0xA5 und 0xFF. Der Master wiederholt je Schritt bis zu dreimal, beginnt danach einmal neu und prüft ein verlorenesFW_END-ACK perGET_VERSION. Bootloader-Änderungen erreichen Bestandskarten nur per Browser-Werksflash – die vier Karten im Feld laufen bis dahin mit BL 1.
Die Master-Steuerung spielt den Daughter Cards die Firmware über den vorhandenen RS-485-Bus ein — kein PC, kein UPDI-Adapter, einzeln adressierbar, ausfallsicher.
0x0000 ┌─────────────────────┐
│ Bootloader │ Fuse BOOTEND = 0x0C (0x0C * 256 = 0x0C00)
0x0C00 ├─────────────────────┤
│ Anwendung │ gebaut mit env attiny1616_boot
│ (Vektortabelle, │ (-Wl,--section-start=.text=0x0C00, -DHAS_BOOTLOADER)
│ IVSEL = 0) │
0x2400 ├─────────────────────┤
│ frei │
0x4000 └─────────────────────┘
- Der Bootloader belegt ~2,5 KiB, die Boot-Sektion 3 KiB. Die App belegt ~5,6 KiB.
BOOTENDsperrt den Baustein nicht. UPDI hat weiter vollen Zugriff und kann den Fuse jederzeit zurücksetzen.LOCKBITwird nicht gesetzt; wäre es gesetzt, entsperrt ein UPDI-Chip-Erase wieder. Die einzige echte Aussperr-Falle wäreSYSCFG0.RSTPINCFGweg von UPDI — das ändert weder der Bootloader noch der Werksflash.- Interruptvektoren: tinyAVR-1 hat
CPUINT.CTRLA.IVSEL. Nach Reset = 0 (Vektoren in der App-Sektion). Der Bootloader nutzt keine Interrupts; er setzt vor dem SprungCPUINT.CTRLA = 0, die App setzt es zusätzlich selbst (#ifdef HAS_BOOTLOADER).
Reset → Bootloader. Prüft GPIOR0 (Marker 0xB7, von CMD_ENTER_BOOTLOADER)
und app_valid() (erstes App-Wort ≠ 0xFFFF und EEPROM-Byte 8 ≠ 0x00).
- gültige App, kein Marker → ~2,5 s auf
CMD_FW_BEGINlauschen, sonst App starten. - Marker gesetzt oder keine gültige App → im Bootloader bleiben und warten.
Update-Sitzung (Master-getrieben, bestehende Rahmenschicht lib/protocol):
| Kommando | Payload | Antwort |
|---|---|---|
CMD_ENTER_BOOTLOADER (0x55) |
— | — (Modul: GPIOR0=0xB7, SW-Reset) |
CMD_FW_BEGIN (0x56) |
len16 · crc16 (CRC16/MODBUS über das Image) |
[0x01] ok / [0x00] nak |
CMD_FW_DATA (0x57) |
off16 · bis 30 Byte |
[0x01] / [0x00] |
CMD_FW_END (0x58) |
— | [0x01] ok / [Fehlercode] |
CMD_GET_VERSION (0x54) |
— | [1 · maj · min · flags · bl_ver] |
Der Bootloader markiert die App bei FW_BEGIN als ungültig (EEPROM-Byte 8 = 0x00),
schreibt die Seiten per NVMCTRL (Erase+Write) ab 0x0C00, prüft bei FW_END
Länge und CRC, setzt dann Byte 8 = 0xA5 und startet per SW-Reset neu.
Fehler / Stromausfall: die App bleibt ungültig → beim nächsten Reset bleibt der Bootloader im Update-Modus, der Master wiederholt. Der Bootloader selbst kann von App-Code nicht überschrieben werden (Hardware-Schutz der Boot-Sektion).
Der Bootloader konfiguriert PA7 (Triac-Treiber) nie als Ausgang → der
Transistor bleibt gesperrt, der Motor kann im Bootloader nicht bestromt werden,
egal welcher Fehler auftritt. Der Bootloader hat einen eigenen Watchdog (~1 s).
Die App-seitige Laufzeitüberwachung (Fehlercode 0x05) und der App-Watchdog bleiben
unverändert. Der Bootloader löscht beim Start nur das Ausgangslatch von PA7
(OUTCLR), für den Fall, dass er ohne Reset erreicht wird.
| Ort | Inhalt |
|---|---|
firmware/bootloader/ |
der Bootloader (pio run -e bootloader) |
firmware/module env attiny1616_boot |
App @ 0x0C00, CMD_GET_VERSION/CMD_ENTER_BOOTLOADER |
firmware/module/lib/fwupdate/ |
empfangsseitiger Seiten-Sammler (host-getestet) |
firmware/master/lib/moduleupdate/ |
sendeseitige Warteschlange + Zustandsautomat (host-getestet) |
firmware/master/src/module_fw.h |
signierter .mota-Container der App, in die Master-Firmware eingebettet (Option A) |
tools/build_module_firmware.py |
baut alle drei Images + signiert die .mota |
Der Master prüft die eingebettete .mota beim Start gegen ota_pubkey.h
(dieselbe ECDSA-Kette wie das Master-OTA). Der Bootloader prüft nur die CRC —
Vertrauensanker ist der Master.
Einstellungen › Modul-Firmware: Tabelle mit installierter vs. gebündelter Version je Adresse, Status (aktuell / Update verfügbar / kein Bootloader / offline), Buttons „Alle aktualisieren" und „Veraltete aktualisieren". Läuft ein Update, ruht der Statusverkehr auf dem Bus; die Module werden nacheinander abgearbeitet, das jeweils betroffene ist einige Sekunden eingefroren.
REST: GET /api/module/firmware, POST /api/module/update ({"all":true} oder
{"addr":[2,3]}), GET /api/module/update/status.
Verkabelung vorbereitet (11.09.2026): zwei Daughter Cards vollständig bestückt (alle Anschlüsse gelötet) und per Flachbandkabel in Reihe verbunden, Durchgangsprüfung der Kette ok. Reine Verkabelungs-/ Lötprüfung — noch kein Signal auf dem Bus. Deckt Punkte 3–7 vor, sobald die Master-Hardware aufgebaut ist.
tools/busctl.pybeherrscht seit T17 auch die Bootloader-Kommandos (version/enterboot/fwbegin/fwdata/fwend, 0x54–0x58) — Punkte 3+4 und mitfwbegin/fwdata/fwendvon Hand auch 5–8 lassen sich damit direkt über einen USB-Serial-Adapter (z. B. FTDI + separates RS-485-Modul mit--rts-rs485) testen, ohne dass die Master-Hardware aufgebaut sein muss.POST /api/module/update(Master-Web-UI) bleibt der bequeme Weg für den Praxisbetrieb mit mehreren Karten.
- ✅ Bootloader isoliert.
pio run -e bootloader, per UPDI @ 0x0000 flashen, FuseBOOTEND = 0x0Csetzen (pymcuprog write -m fuses -o 8 --values 0x0Coder der Werksflasher). Ohne App: bleibt der Bootloader im Warte-Loop, WDT löst nicht aus (kein Dauerreset). Verifiziert 10.09.2026 über den Browser-Werksflasher (schreibt Bootloader + Fuse in einem Zug): Chip-Erase, Geräte-ID, Fuse- und Seitenschreiben liefen sauber, kein Dauerreset. - ✅ Sprung in die App.
attiny1616_boot-App @ 0x0C00 dazuflashen. Reset → Bootloader übergibt an die App (Interruptvektoren korrekt, IVSEL = 0) — verifiziert (Status-LED blinkt/leuchtet wie von der App vorgegeben, der 1-ms-Timer-Interrupt läuft also). RS-485-Kommunikation + Motorsteuerung im Zusammenspiel mit dem Master noch offen (Punkte 3+, Master-Hardware bestellt, noch nicht aufgebaut). CMD_GET_VERSION. Master (oderbusctl.py version <addr>) fragt Version ab, bekommt1 · 1 · 0 · 0x03 · x(App gültig + Bootloader vorhanden).CMD_ENTER_BOOTLOADER. Master (oderbusctl.py enterboot <addr>) schickt die Karte in den Bootloader; sie bleibt dort.- Update über den Bus.
POST /api/module/updatefür eine Karte. Verlauf im Log; nachFW_ENDstartet die Karte neu,GET_VERSIONbestätigt die Version. - Abbruch mitten im Transfer (Karte kurz stromlos): App bleibt ungültig, Bootloader wartet, Master-Wiederholung bringt sie zurück.
- „Alle aktualisieren" mit mehreren Karten: nacheinander, andere Module bleiben erreichbar.
- CRC-Fehlerpfad: eine bewusst verfälschte
.mota→ Bootloader lehnt beiFW_ENDab, App bleibt ungültig. - Bus-Signalintegrität + Dauer bei zehn Karten am realen Kabelbaum messen.
firmware/master/prebuilt/updi.js, Tab Daughter Card: Chip-Erase → Bootloader
-boot-App →BOOTEND = 0x0C→BODCFG = 0xE5. Geräte-ID-Prüfung (1E 94 21). Der Fuse-Write folgt derpymcuprog-v0-Sequenz (ADDR + DATA +WFU), inklusive Rücklese-Prüfung.
BODCFG (21.09.2026): Bisher nirgends konfiguriert (Werksvorgabe 0x00 =
Brown-out-Detection komplett aus) — Verdacht nach einem Fall, in dem eine Karte
nach längerer Stromlosigkeit ohne erkennbaren Grund softwareseitig nicht mehr
ansprechbar war. 0xE5 = LVL=BODLEVEL7 (4,2 V, höchste verfügbare Stufe;
Datenblatt DS40002204A S. 2 nennt für 20 MHz 4,5–5,5 V, mehr als 4,2 V bietet
der Chip nicht) + ACTIVE=ENABLED + SLEEP=ENABLED (beide kontinuierlich,
Kapitel 17.5.1/17.5.2). Zusätzlich neue Diagnose-Log-Zeilen: updi.js liest
EEPROM-Byte 8 (Bootloader-Marker „App gültig") direkt nach dem Chip-Erase und
nochmal unmittelbar vor dem Neustart aus — damit sichtbar wird, falls ein
alter Marker aus einem abgebrochenen Bus-Update den Chip-Erase überlebt.
Der Seitenpuffer wird wie in SerialUPDI (megaTinyCore) mit CTRLA.RSD = 1
(Response Signature Disable) beschrieben: ohne RSD schickt das Modul nach jedem
ST-Byte ein ACK, das auf der Ein-Draht-Leitung mit den folgenden Datenbytes
kollidiert. Mit RSD gehen REPEAT + ST (Wort) + 64 Datenbytes +
STCS(CTRLA, RSD=0) in einem Transfer raus, danach nur das Echo lesen.