Technischer Kontext: Der ESP8266
Der ESP8266 ist ein kostengünstiger WLAN-fähiger Mikrocontroller von Espressif, der seit vielen Jahren eine feste Größe in Bastel-, IoT- und Prototyping-Projekten ist.
Technisch handelt es sich um einen 32-Bit-Mikrocontroller mit integriertem WiFi-Stack, externem Flash-Speicher und ausreichender Rechenleistung für typische Aufgaben wie Sensorik, Aktorsteuerung oder einfache Netzwerkprotokolle (z. B. MQTT oder HTTP).
Ein entscheidender Punkt ist, dass der ESP8266 nicht auf eine bestimmte Laufzeitumgebung festgelegt ist.
Je nach Anwendungsfall lassen sich unterschiedliche Firmwares einsetzen – unter anderem AT-Firmware, MicroPython oder NodeMCU.
Der ESP8266 bildet in diesem Kontext die Hardwaregrundlage für den folgenden Build- und Flash-Prozess.
Ausgangspunkt
Die hier verwendeten ESP8266-Boards kamen ursprünglich bereits mit NodeMCU vorinstalliert.
Ohne größere Überlegungen habe ich sie später mit MicroPython geflasht – vor allem aus pragmatischen Gründen. Für schnelle Experimente war das ausreichend und funktionierte zuverlässig.
Erst mit etwas Abstand rückte NodeMCU wieder in den Fokus. Beim erneuten Hinsehen wurde klar, dass die Firmware vollständig Open Source ist und sich tatsächlich aus den Quellen selbst bauen lässt.
Daraus entstand weniger ein konkretes Projekt als eine sachliche Fragestellung:
Wie aufwändig ist es, eine solche Firmware selbst zu bauen – und was gewinnt man dadurch?
Neben der besseren Kontrolle über Build und Module spielt dabei auch Lua eine Rolle.
Die Sprache empfinde ich inzwischen als angenehm direkt und gut geeignet für Embedded-Kontexte – ohne dass daraus ein grundsätzlicher Vergleich zu Python werden soll.
Motivation
NodeMCU wird häufig als fertige Umgebung eingesetzt: Image flashen, Lua-Skripte schreiben, fertig.
Für viele Szenarien ist das vollkommen ausreichend.
Mich interessierte jedoch bewusst der Schritt davor: der Weg von den Quellen bis zur laufenden Firmware auf echter Hardware.
Nicht aus Notwendigkeit, sondern aus Interesse:
- Welche Module sind tatsächlich im Image enthalten?
- Wie entsteht das Firmware-Binary?
- Wie reproduzierbar ist der Build-Prozess?
Gerade bei Embedded-Systemen schafft dieses Verständnis eine solide Grundlage für spätere Experimente und Fehlersuche.
Vorbereitung: Toolchain und Quellcode
$ sudo apt install python-is-python3 esptool cu
$ mkdir nodemcu
$ cd nodemcu
$ git clone https://github.com/nodemcu/nodemcu-firmware.git
$ cd nodemcu-firmware
$ git submodule update --init --recursive
Konfiguration
NodeMCU ist modular aufgebaut. Die Module bestimmen, welche Funktionalität später im Lua-REPL und in Skripten zur Verfügung steht. Über folgende Datei lässt sich festlegen, welche Lua-Module in das Image aufgenommen werden:
$ nano app/include/user_modules.h
Übersicht: NodeMCU-Module (user_modules.h)
| Modul | Default | Kategorie | Zweck / typische Verwendung |
|---|---|---|---|
| adc | ✓ | Hardware | Analog-Digital-Wandler |
| bit | ✓ | Core | Bitoperationen |
| dht | ✓ | Sensor | DHT11 / DHT22 Temperatur & Feuchte |
| file | ✓ | Core | Zugriff auf Flash-Dateisystem |
| gpio | ✓ | Hardware | Digitale Ein- / Ausgänge |
| i2c | ✓ | Bus | I²C-Kommunikation |
| mqtt | ✓ | Netzwerk | MQTT-Client |
| net | ✓ | Netzwerk | TCP / UDP Sockets |
| node | ✓ | System | Systemfunktionen (Restart, Heap, etc.) |
| ow | ✓ | Bus | OneWire (z. B. DS18B20) |
| spi | ✓ | Bus | SPI-Kommunikation |
| tmr | ✓ | Zeitsteuerung | Timer, Delays |
| uart | ✓ | Kommunikation | Serielle Schnittstelle |
| wifi | ✓ | Netzwerk | WLAN-Konfiguration & Status |
| ads1115 | ✗ | Sensor | Externer ADC |
| adxl345 | ✗ | Sensor | Beschleunigungssensor |
| bme280 | ✗ | Sensor | Temperatur / Feuchte / Druck |
| bmp085 | ✗ | Sensor | Luftdrucksensor |
| http | ✗ | Netzwerk | HTTP-Client |
| mdns | ✗ | Netzwerk | mDNS / Zeroconf |
| pwm | ✗ | Hardware | Pulsweitenmodulation |
| pwm2 | ✗ | Hardware | Erweiterte PWM |
| sntp | ✗ | Zeit | Netzwerk-Zeitsynchronisation |
| tls | ✗ | Sicherheit | TLS / SSL |
| websocket | ✗ | Netzwerk | WebSocket-Client |
| ws2812 | ✗ | Hardware | RGB-LEDs (NeoPixel) |
| u8g2 | ✗ | Display | Grafikdisplays |
| enduser_setup | ✗ | Provisioning | WLAN-Setup per Captive Portal |
Build ausführen
Der eigentliche Build ist dann unspektakulär:
$ make
Nach einem erfolgreichen Build legt NodeMCU die erzeugten Firmware-Images im Verzeichnis
nodemcu-firmware/bin/ ab.
Typischerweise finden sich dort mindestens zwei Dateien:
0x00000.bin– Bootloader und Initialcode0x10000.bin– eigentliche NodeMCU-Firmware
Diese Images werden später an die entsprechenden Flash-Offsets des ESP8266 geschrieben.
Hardware prüfen und Flash vorbereiten
$ sudo esptool --port /dev/ttyUSB0 flash_id
$ sudo esptool --port /dev/ttyUSB0 erase_flash
Firmware flashen
$ sudo esptool --port /dev/ttyUSB0 write_flash 0x0 0x00000.bin 0x10000 0x10000.bin
Serielle Konsole
Zur Kommunikation mit der Firmware kann ein serielles Terminal verwendet werden. Je nach Tool und Terminal-Einstellungen verhalten sich Ein- und Ausgabe unterschiedlich zuverlässig.
Für einfache Tests und interaktive Arbeit hat sich picocom in der Praxis als
robuste und unkomplizierte Lösung erwiesen.
$ sudo apt install picocom
$ sudo usermod -a -G dialout mpeterma
$ picocom -b 115200 /dev/ttyUSB0
...
NodeMCU 3.0.0.0
modules: adc,bit,dht,file,gpio,i2c,mqtt,net,node,ow,spi,tmr,uart,wifi
powered by Lua 5.1.4
cannot open init.lua:
>
Nach dem Verbinden über die serielle Konsole landet man in einem REPL (Read–Eval–Print Loop) der NodeMCU-Firmware.
In diesem Modus können Lua-Kommandos direkt eingegeben und sofort ausgeführt werden. Das eignet sich gut für erste Tests, Debugging und das schrittweise Ausprobieren von Hardwarefunktionen.
Ein einfaches Beispiel ist das Ein- und Ausschalten eines GPIO-Pins, an dem etwa eine LED angeschlossen ist:
Änderungen wirken unmittelbar, ohne dass die Firmware neu geflasht werden muss.
> gpio.mode(4, gpio.OUTPUT) -- GPIO4 als Ausgang konfigurieren
> gpio.write(4, gpio.LOW) -- LED einschalten
> gpio.write(4, gpio.HIGH) -- LED ausschalten
Ein erstes Programm: init.lua
Im ersten Schritt wird kein externes Tool verwendet.
Stattdessen entsteht das Programm direkt im REPL der NodeMCU-Firmware.
a = file.open("init.lua", "w")
a.write("print('Hello World')")
a.close()
node.restart()
Dabei passiert Folgendes:
- Über das REPL werden Lua-Kommandos unmittelbar auf dem Gerät ausgeführt.
- file.open(…) legt auf dem Flash-Speicher des ESP8266 eine Datei init.lua an.
- Mit a.write(…) wird der Programmcode direkt in diese Datei geschrieben.
- Nach dem Schließen der Datei wird das Gerät neu gestartet.
- Beim Neustart lädt NodeMCU automatisch die Datei init.lua und führt ihren Inhalt aus:
Hello World
init.lua ist der standardmäßige Einstiegspunkt von NodeMCU.
Existiert diese Datei im Flash, wird sie beim Start automatisch geladen
und ausgeführt.
Für einfache Experimente kann der gesamte Programmcode direkt in dieser
Datei untergebracht werden.
Bei komplexeren Vorhaben ist es üblich, die Logik auf mehrere Dateien
oder Module aufzuteilen und diese aus der init.lua heraus zu laden.
Upload per Tool
Das direkte Erzeugen von Dateien über das REPL ist für erste Tests praktisch,
skaliert jedoch nur begrenzt.
Sobald Programme größer werden oder aus mehreren Dateien bestehen,
wird das Tippen von Code über die serielle Konsole schnell unübersichtlich.
Für echte Projekte ist es daher deutlich sinnvoller, Lua-Dateien lokal zu bearbeiten und anschließend gesammelt auf das Gerät zu übertragen.
Ein Upload-Tool ermöglicht:
- Bearbeitung des Codes mit Editor, Versionsverwaltung und Syntax-Highlighting
- Übertragung mehrerer Dateien in einem Schritt
- reproduzierbare Updates ohne manuelle REPL-Eingaben
Das eigentliche Firmware-Image bleibt dabei unverändert; es werden nur Dateien im Flash-Dateisystem aktualisiert.
Auf meinem Debian-System ist nodemcu-uploader nicht über die Paketquellen
verfügbar.
Der Einsatz einer virtuellen Python-Umgebung (venv) stellt sicher,
dass das Tool isoliert installiert wird, ohne das System-Python oder andere
Pakete zu beeinflussen.
sudo apt install python3-pip python3-venv
mkdir ~/Apps/nodemcu-uploader
cd ~/Apps/nodemcu-uploader
python3 -m venv venv
source venv/bin/activate
pip install --upgrade pip
pip install nodemcu-uploader
Beispiel: LED blinken auf GPIO4
Als etwas realistischeres Beispiel folgt ein kleines Lua-Programm, das eine LED an GPIO4 (D2 auf vielen NodeMCU-Boards) periodisch ein- und ausschaltet.
-- init.lua
local pin = 4
gpio.mode(pin, gpio.OUTPUT)
while true do
gpio.write(pin, gpio.HIGH)
tmr.delay(500000) -- 500 ms
gpio.write(pin, gpio.LOW)
tmr.delay(500000) -- 500 ms
end
Die Datei wird anschließend auf das Gerät übertragen:
nodemcu-uploader --port /dev/ttyUSB0 --baud 115200 upload init.lua
Nach einem Neustart des ESP8266 läuft das Programm automatisch und die LED beginnt zu blinken.
Endlosschleifen mit tmr.delay() blockieren den Lua-Interpreter und sind nur für einfache Tests geeignet.
In produktiven Anwendungen werden stattdessen Timer-Callbacks (tmr.create()) oder eventbasierte Ansätze verwendet.
Fazit
NodeMCU lässt sich ohne größere Hürden aus den Quellen bauen, flashen und auf realer Hardware betreiben.
Der gesamte Weg – vom Klonen des Repositories über den Build bis zur interaktiven Arbeit im REPL – ist transparent und gut nachvollziehbar.
Der eigentliche Mehrwert liegt dabei weniger im Ergebnis als im Prozess:
Man versteht, welche Module Teil der Firmware sind, wie das Image entsteht und wie sich Logik und Firmware sauber voneinander trennen lassen. Gerade bei Embedded-Systemen schafft dieses Wissen eine solide Grundlage für spätere Experimente, Erweiterungen oder Fehlersuche.
Lua erweist sich in diesem Kontext als angenehm direkte Sprache, insbesondere in Kombination mit dem REPL und der einfachen Dateistruktur von NodeMCU. Für kleine Experimente reicht eine einzelne init.lua, für größere Vorhaben lassen sich Struktur und Tooling problemlos erweitern.
Damit ist NodeMCU nicht nur eine „fertige Laufzeitumgebung“, sondern ein gut beherrschbares System – geeignet sowohl für schnelle Tests als auch als Basis für weiterführende Ideen und Projekte.