NodeMCU von Grund auf: Lua-Firmware selbst gebaut und geflasht

NodeMCU von Grund auf: Lua-Firmware selbst gebaut und geflasht
By Matthias Petermann / on 18.12.2025

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.

ℹ️ Warum selbst bauen?

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
📝 Modulare Firmware
Durch die modulare Struktur lässt sich die NodeMCU-Firmware an unterschiedliche Anwendungsfälle anpassen – vom einfachen Sensorknoten bis zu komplexeren, vernetzten Setups.

Ü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
💡 Wo liegt die fertige Firmware?

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 Initialcode
  • 0x10000.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
📝 Sauberer Start
Gerade bei eigenen Builds ist ein vollständiges Löschen sinnvoll. Alte Reste im Flash können sonst zu schwer nachvollziehbaren Effekten führen.

Firmware flashen

$ sudo esptool --port /dev/ttyUSB0 write_flash 0x0 0x00000.bin 0x10000 0x10000.bin

Serielle Konsole

💡 Serielle Kommunikation

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:
>
ℹ️ REPL: Interaktive Arbeit mit NodeMCU

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
NodeMCU Board mit eingeschalteter LED

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

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.

ℹ️ Warum ein Upload-Tool?

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.

📝 Hinweis zur Endlosschleife

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.