ESP8266 und ESP8285 WLAN Module

Mit Preisen ab 1,50 € sind diese Module eine sehr preisgünstige Netzwerk-Anbindung zum Basteln. Hier findest du eine Anleitung zum Einstieg, wie man damit Geräte baut und programmiert.

Die ESP8266 und ESP8285 Chips vom chinesischen Hersteller Espressif sind 32bit Mikrocontroller mit integrierter Wi-Fi Schnittstelle. Ihre Firmware basiert auf dem Lightweight IP stack von Adam Dunkels. Es werden zahlreiche handliche Module mit diesen Mikrochips verkauft.

Normalerweise werden die ESP-Module mit der AT-Firmware verkauft. Diese nimmt über eine serielle Schnittstelle Kommandos von einem anderen Mikrocontroller an. Man kann auch eigene Programme in die Module laden.

Datenblätter: ESP8266, ESP8285

Technische Daten

  1. Die minimale Spannung hängt vom Flash Speicher ab. 2,8 Volt gehen immer. Manche Module laufen sogar noch mit 2,5 Volt.
  2. Wenn der Funk-Kanal frei ist. Auf stark genutzten Funk-Kanälen sind Latenzen bis 200 ms normal.
  3. Die Basis Firmware enthält keine Router-Funktion. Im Soft-AP Modus können die WLAN Clients nur mit dem ESP kommunizieren, nicht miteinander und auch nicht mit dem Internet. Man kann sich den Router ggf. selbst programmieren.
  4. Die Basis-Firmware belegt etwa 30 kByte Arbeitsspeicher und 260 kByte Flash.

Module und Boards

Der CHIP_EN Pin hieß früher mal CH_PD und CHIP_PU. Manche Board Hersteller nennen ihn ENABLE.

Viele I/O Pins sind nur eingeschränkt verwendbar. Beachte dazu die folgenden Beschreibungen der Module. Zur Erweiterung empfehle ich I/O Expander mit I²C Schnittstelle, wie MCP23017 und PCF8574. Für analoge Anwendungen benutze ich gerne ADS1115 und PCF8591.

Die Widerstände um den Chip herum sollten maximal 2,2 kΩ haben, denn hochohmige Widerstände sind für Funkwellen empfänglich. Beachte die Hinweise in den Hardware Design Guidelines, insbesondere Kapitel 1.6.2, was die Ausrichtung der Antenne angeht.

ESP-01 und ESP-1 Module

Diese kleinen Module sind primär als Netzwerk-Schnittstelle für andere Mikrocontroller gedacht.

Das ESP-01 Modul von der Firma AI-Thinker basiert auf dem ESP8266. Es wird mit 512 kByte Flash (in blau) oder 1 MByte (in schwarz) verkauft. Die rote Power-LED nimmt etwa 0,5 mA Strom auf, die blaue LED hängt am TXD Pin. Das ESP-01S oder ESP8266-01S von AZ-Delivery hat ebenfalls 1 MByte Flash. Es hat nur eine blaue LED an GPIO2, und einen zusätzlichen Pull-Up Widerstand am Enable Eingang. Das ESP-1 Modul von der Firma DOIT basiert auf dem ESP8285. Es hat 1 MByte Flash Speicher und einen Deckel zur Abschirmung. Es hat nur eine blaue LED am TXD Pin.

Die Minimal-Beschaltung zur Nutzung der AT-Firmware sieht so aus:


Pin Name Beschreibung
1 GPIO3 (RXD) serieller Eingang oder normaler I/O Pin. Wird beim Booten schwach auf HIGH gezogen.
2 VDD Spannungsversorgung 3,3 V 500 mA
3 GPIO0 LOW beim Booten aktiviert Firmware-Upload, muss zum normalen Start offen sein oder schwach auf HIGH gezogen werden. Gibt beim Booten LOW Impulse aus.
4 RESET LOW=Reset, intern unterschiedlich beschaltet. Siehe folgende Zeichnungen.
5 GPIO2 (TXD1) Beim Booten serieller Ausgang, darf dabei nicht auf LOW gezogen werden. Kann nach dem Booten als normaler I/O Pin verwendet werden. Ist bei der "S" Version mit einer blauen LED verbunden, die bei LOW Pegel leuchtet.
6 CHIP_EN LOW=power down, HIGH=chip enabled, hat bei der "S" Version einen 12 kΩ Pull-Up Widerstand
7 GND Masse
8 GPIO1 (TXD) Beim Booten serieller Ausgang, darf dabei nicht auf LOW gezogen werden. Kann nach dem Booten als normaler I/O Pin verwendet werden. Ist bei der Version ohne "S" mit einer blauen LED verbunden, die bei LOW Pegel leuchtet.

Der Reset Eingang des ESP ist innerhalb der Module unterschiedlich beschaltet:

Reset ESP-01 blau Reset ESP-01 schwarz Reset ESP-01S

Die 8-Polige Stiftleiste im 2,54 mm Raster ist das auffälligste Merkmal dieser Module. Dadurch lassen sie sich leicht mit einem kurzen Flachkabel absetzen, um den Empfang zu optimieren.

Die Reichweite der primitiven Antenne (Leiterschleife) ist mit Smartphones vergleichbar.

Wer unbedingt den Deep-Sleep Modus eines ESP-01 Moduls nutzen will, muss einen sehr dünnen Draht direkt an den Chip Pin 8 (GPIO16) anlöten und die Power-LED entfernen (falls vorhanden).

Doku von AI-Thinker ESP-01, AZ-Delivery ESP-01S (mit falschem Foto), DOIT ESP-1.

ESP-07 und ESP-12 Module

Diese Module kommen alle von der Firma AI-Thinker. Sie enthalten nur den ESP8266 Chip und einen Flash-Speicher.

Das ESP-07 Modul hat 1 MByte Flash, eine Keramikantenne und eine Steckbuchse für externe Antennen. Die rote Power-LED nimmt etwa 0,5 mA Strom auf, die blaue LED hängt an GPIO2.
Man soll den gezeigten Kondensator entfernen, wenn eine externe Antenne verwendet wird.
Das ESP-07S Modul hat 1 MByte Flash, keine LEDs und keine Antenne. Es muss mit einer externen Antenne betrieben werden. Die Modelle ESP-12S, ESP-12E und ESP-12F haben 4 MByte Flash und eine Leiterschleife als Antenne. Die blaue LED hängt an GPIO2, es gibt keine Power-LED.
Die Modelle 12E und 12F haben eine dritte Reihe mit nutzlosen Pins.

Die Minimal-Beschaltung zur Nutzung der AT-Firmware sieht für diese Module so aus:


Pin Name Beschreibung
1 RESET LOW=Reset, intern unterschiedlich beschaltet. Siehe folgende Zeichnungen.
2 ADC Analoger Eingang 0 bis 1 V
3 CHIP_EN Muss auf HIGH gezogen werden, damit der Chip arbeitet. LOW=power down, HIGH=chip enabled
4 GPIO16 Ist im Deep-Sleep Modus der Ausgang vom Wakeup-Timer. Gibt beim Booten einen HIGH Impuls aus. An diesem Pin funktionieren TwoWire (I²C), attachInterrupt() und pinMode INPUT_PULLUP nicht. Dafür gibt es nur hier den pinMode INPUT_PULLDOWN_16.
5 GPIO14 (SCK) Normaler I/O Pin oder SPI Takt. Wird beim Booten schwach auf HIGH gezogen.
6 GPIO12 (MISO) Normaler I/O Pin oder SPI Daten. Wird beim Booten schwach auf HIGH gezogen.
7 GPIO13 (MOSI) Normaler I/O Pin oder SPI Daten. Wird beim Booten schwach auf HIGH gezogen.
8 VDD Spannungversorgung 3,3 V 500 mA
9 Reserviert. Diese Pins existieren nur beim ESP-12E und ESP-12F. Sie sind durch den Flash Speicher belegt.
10
11
12
13
14
15 GND Gemeinsame Masse
16 GPIO15 (CS) Normaler I/O Pin oder SPI Chip Select. Muss beim Booten auf LOW gezogen werden.
17 GPIO2 (TXD1) Beim Booten serieller Ausgang, darf dabei nicht auf LOW gezogen werden. Kann nach dem Booten als normaler I/O Pin verwendet werden. Ist mit der blauen LED verbunden, die bei LOW Pegel leuchtet.
18 GPIO0 LOW beim Booten aktiviert Firmware-Upload, muss zum normalen Start offen sein oder schwach auf HIGH gezogen werden. Gibt beim Booten LOW Impulse aus.
19 GPIO4 Normaler I/O Pin, frei verfügbar
20 GPIO5 Normaler I/O Pin, frei verfügbar
21 GPIO3 (RXD) serieller Eingang oder normaler I/O Pin. Wird beim Booten schwach auf HIGH gezogen.
22 GPIO1 (TXD) Beim Booten serieller Ausgang, darf dabei nicht auf LOW gezogen werden. Kann nach dem Booten als normaler I/O Pin verwendet werden.

Der Reset Eingang des ESP ist innerhalb der Module unterschiedlich beschaltet:

Reset ESP-07 Reset ESP-07S and 12S Reset ESP12E and 12F

Die Anschlussleisten der ESP-12 Module haben 2 mm Raster.

Die Reichweite der on-board Antenne ist mit Smartphones vergleichbar. Mit einer guten externen Antenne soll das ESP-07(S) sogar weiter kommen. Der Antennen-Stecker heisst SMT, U.FL oder IPEX. Achtung: die gibt es in unterschiedlichen Größen!

Die Module ESP-07S und alle ESP-12 sind eine gute Wahl für Batteriebetrieb mit Deep Sleep.

Technische Doku des Herstellers

NodeMCU Board

Das nachgemachte NodeMCU Board von Lolin besteht aus einem ESP-12 Modul mit Spannungsregler und USB-UART Interface (CH-340). Der Flash Speicher ist 4 Megabytes groß. Das originale NodeMCU Board wird nicht mehr hergestellt, die Nachbauten sind in der Regel gut zu gebrauchen.


J2 Label Name Beschreibung
1 VIN Spannungsversorgung 5 bis 9 V 500 mA
2 G GND Gemeinsame Masse
3 RST RESET Hat 12 kΩ Pull-Up Widerstand
4 EN CHIP_EN LOW=power down, HIGH=chip enabled, hat 12 kΩ Pull-Up
5 3V VDD Spannungsversorgung 3,3 V (Eingang 500 mA oder Ausgang max. 200 mA)
6 G GND Gemeinsame Masse
7 Reserviert, durch den Flash Speicher belegt.
8
9
10
11
12
13 Reserviert, Pinbelegung variiert je nach Board
14
15 A0 ADC Analoger Eingang für 0 bis 3,2 V weil mit Spannungsteiler 220 kΩ + 100 kΩ

J1 Label Name Beschreibung
1 3V VDD Spannungsversorgung 3,3 V (Eingang 500 mA oder Ausgang max. 300 mA)
2 G GND Gemeinsame Masse
3 TX GPIO1 (TXD) Beim Booten serieller Ausgang, darf dabei nicht auf LOW gezogen werden. Ist mit dem USB-UART verbunden. Kann nach dem Booten als normaler I/O Pin verwendet werden.
4 RX GPIO3 (RXD) Serieller Eingang des ESP. Ist über 470 Ω mit dem USB-UART verbunden.
5 D8 GPIO15 (CS) Normaler I/O Pin oder SPI Chip Select. Muss beim Booten auf LOW gezogen werden. Hat 12 kΩ Pull-Down Widerstand.
6 D7 GPIO13 (MOSI) Normaler I/O Pin oder SPI Daten. Wird beim Booten schwach auf HIGH gezogen.
7 D6 GPIO12 (MISO) Normaler I/O Pin oder SPI Daten. Wird beim Booten schwach auf HIGH gezogen.
8 D5 GPIO14 (SCK) Normaler I/O Pin oder SPI Takt. Wird beim Booten schwach auf HIGH gezogen.
9 G GND Gemeinsame Masse
10 3V VDD Spannungsversorgung 3,3 V (Eingang 500 mA oder Ausgang max. 300 mA)
11 D4 GPIO2 (TXD1) Beim Booten serieller Ausgang, darf dabei nicht auf LOW gezogen werden. Kann nach dem Booten als normaler I/O Pin verwendet werden. Der Pin hat einen 12 kΩ Pull-Up Widerstand, und eine blaue LED verbunden, die bei LOW Pegel leuchtet.
12 D3 GPIO0 LOW beim Booten aktiviert Firmware-Upload, muss zum normalen Start offen sein oder schwach auf HIGH gezogen werden. Gibt beim Booten LOW Impulse aus. Hat einen 12 kΩ Pull-Up Widerstand und ist über 470 Ω mit einem Taster nach GND verbunden.
13 D2 GPIO4 Normaler I/O Pin, frei verfügbar
14 D1 GPIO5 Normaler I/O Pin, frei verfügbar
15 D0 GPIO16 Ist mit der blauen LED verbunden, die bei LOW leuchtet. Ist im Deep-Sleep Modus der Ausgang vom Wakeup-Timer. Gibt beim Booten einen HIGH Impuls aus. An diesem Pin funktionieren TwoWire (I²C), attachInterrupt() und pinMode INPUT_PULLUP nicht. Dafür gibt es nur hier den pinMode INPUT_PULLDOWN_16, für dessen Nutzung man aber die blaue LED entferen müsste.

Leider gibt es zu den Nachbauten keinen Schaltplan, so dass ich hier nur den Schaltplan des Originals zeigen kann. Bei den Nachbauten ist R7 leer, und dafür R3 mit 470 Ohm bestückt. Entsprechend ist der Taster als "Reset" (nicht "User Key") beschriftet.

In Arduino kann man die NodeMCU Labels nutzen, ich empfehle allerdings die GPIO Nummern zu verwenden.

Man kann das Board wahlweise mit 3,3 V am 3V Pin, oder mit 5 V an USB, oder mit 5 bis 9 V an VIN versorgen. Eine Diode verhindert, dass Strom von VIN zum USB Anschluss fließt. Der Spannungsregler auf den Nachbauten kann kurzzeitig bei 5 V Eingangsspannung etwa 300 mA für externe Beschaltung bereit stellen.

Für Batteriebetrieb eignet sich das Board eher nicht, da sein Spannungsregler eine Ruhestromaufnahme um 10 mA hat. Beim Original Board belastet zudem der USB-UART ständig die Batterie, während das "NodeMCU Lua Lolin V3" Board den USB Chip nur über das eigesteckte USB Kabel versorgt.

Auf einem Steckbrett läuft das Board zuverlässiger, wenn man die reservierten Stifte entfernt, die zum Flash Speicher führen.

Die Reset Leitung und GPIO0 können durch den USB-UART angesteuert werden. Damit aktiviert die Arduino IDE den Firmware-Upgrade Modus vollautomatisch:

DTR RTS RESET GPIO0
Aus Aus HIGH HIGH
Ein Ein HIGH HIGH
Ein Aus LOW HIGH
Aus Ein HIGH LOW

Alternativ kann man den Firmware-Upgrade Modus manuell aktivieren, indem man beide Tasten drückt und dann den Reset-Taster zuerst loslässt.

Wemos D1 Mini Board

Wemos D1 Mini Boards gibt es in vielen Varianten mit etwas unterschiedlicher Schaltung. Dazu kommen Nachbauten und Fälschungen, die wiederum nicht mit dem Original identisch sind.

Meistens haben sie einen USB-UART vom Typ CH340, und einem Low-Drop Spannungsregler. Alle freien I/O Pins des ESP sind herausgeführt, aber nicht der CHIP_EN Pin. Es gibt nur einen Taster, mit Reset-Funktion.

Die Pinbelegung war bei allen Wemos D1 Mini Boards bisher gleich:

Pin Label Name Beschreibung
1 RST RESET Hat 10 kΩ Pull-Up Widerstand, einen Taster nach GND, und einen 100 nF Kondensator nach GND
2 A0 ADC Analoger Eingang für 0 bis 3,2 V weil mit Spannungsteiler 220 kΩ + 100 kΩ
3 D0 GPIO16 Ist im Deep-Sleep Modus der Ausgang vom Wakeup-Timer. Gibt beim Booten einen HIGH Impuls aus. An diesem Pin funktionieren TwoWire (I²C), attachInterrupt() und pinMode INPUT_PULLUP nicht. Dafür gibt es nur hier den pinMode INPUT_PULLDOWN_16.
4 D5 GPIO14 (SCK) Normaler I/O Pin oder SPI Takt. Wird beim Booten schwach auf HIGH gezogen.
5 D6 GPIO12 (MISO) Normaler I/O Pin oder SPI Daten. Wird beim Booten schwach auf HIGH gezogen.
6 D7 GPIO13 (MOSI) Normaler I/O Pin oder SPI Daten. Wird beim Booten schwach auf HIGH gezogen.
7 D8 GPIO15 (CS) Normaler I/O Pin oder SPI Chip Select. Muss beim Booten auf LOW gezogen werden. Hat 10 kΩ Pull-Down Widerstand
8 3V3 VDD Spannungsversorgung 3,3 V (Eingang 500 mA oder Ausgang max. 50 mA)
9 5V Spannungsversorgung 4 bis 6,5 V 500 mA
10 G GND Gemeinsame Masse
11 D4 GPIO2 (TXD1) Beim Booten serieller Ausgang, darf dabei nicht auf LOW gezogen werden. Kann nach dem Booten als normaler I/O Pin verwendet werden. Der Pin hat einen 10 kΩ Pull-Up Widerstand und eine blaue LED, die bei LOW Pegel leuchtet.
12 D3 GPIO0 LOW beim Booten aktiviert Firmware-Upload, muss zum normalen Start offen sein oder schwach auf HIGH gezogen werden. Gibt beim Booten LOW Impulse aus. Hat einen 10 kΩ Pull-Up Widerstand.
13 D2 GPIO4 Normaler I/O Pin, frei verfügbar
14 D1 GPIO5 Normaler I/O Pin, frei verfügbar
15 RX GPIO3 (RXD) Serieller Eingang des ESP. Ist über 470 Ω mit dem USB-UART verbunden.
16 TX GPIO1 (TXD) Beim Booten serieller Ausgang, darf dabei nicht auf LOW gezogen werden. Ist mit dem USB-UART verbunden. Kann nach dem Booten als normaler I/O Pin verwendet werden.

In Arduino kann man die NodeMCU Labels nutzen, ich empfehle allerdings die GPIO Nummern zu verwenden.

Man kann das Board wahlweise mit 3,3 V am 3V3 Pin oder mit 5 V am 5V Pin oder über das USB Kabel versorgen. Beim Original verhindert eine Diode, dass kein Strom "Rückwärts" vom 5V Eingang zum USB Anschluss fließt. Die Ruhestromaufnahme des Boardes liegt je nach Variante zwischen 0,2 und 1,6 mA, so dass es für Batteriebetrieb nur bedingt geeignet ist.

Der Spannungsregler vom originalen Board reicht gerade für das Board aus, er darf extern nur minimal belastet werden. Nachgebaute D1 Mini Boards sind sogar oft mit zu schwachen Spanungsreglern ausgestattet. Dort kann ein Kondensator mit einigen hundert µF an 3,3 V und GND helfen.

Der Reset Taster zieht den Reset-Pin ohne Schutzwiderstand direkt auf GND. Wemos D1 Mini Boards haben keinen "Flash" Taster. Die Boards besitzen die gleiche Reset-Automatik wie das NodeMCU Board.

Der Analoge Eingang A0 ist mit einem Spannungsteiler (220kΩ + 100kΩ) ausgestattet, damit man Spannungen bis zu 3,2 V messen kann.

Dokumentation des Herstellers.

WIFI Kit 8 Board

Das WIFI Kit 8 (alias Heltec HTIT-W8266 oder Wemos TTGO ESP8266) hat 4 MByte Flash Speicher, sowie ein winzig kleines 0,91" OLED Display für Grafiken mit 128x32 Pixeln (entsprechend 4x21 Zeichen Text). Der Display Controller vom Typ SSD1306 ist über I²C mit dem ESP8266 verbunden. Für die USB Buchse wurde der Chip CP2104 von Silabs verwendet.

Viele Händler verkaufen diese Boards mit falsch beschrifteten Pins und falschen Anschlussplänen! Die folgenden Zeichnungen zeigen die richtige Pinbelegung, und in Klammern die Verbindungen zum Display:

Erkennungsmerkmal:

Erkennungsmerkmal:

Obere Stiftleiste:

Pin Name Beschreibung
1 GND Gemeinsame Masse
2 5 V Spannungsversorgung 5 V 500 mA
3 3,3 V Spannungsversorgung 3,3 V (Eingang 500 mA oder Ausgang max. 200 mA)
4 GND Gemeinsame Masse
5 CTS Clear-To-Send Eingang vom USB-UART, LOW aktiv
6 DTR Data-Terminal-Ready Ausgang vom USB-UART, LOW aktiv
7 A: GPIO5 (SCL)
B: GPIO5
A: I²C Takt zum Display mit 10 kΩ Pull-Up Widerstand
B: Normaler I/O Pin, frei verfügbar.
8 GPIO1 (TXD) Beim Booten serieller Ausgang, darf dabei nicht auf LOW gezogen werden. Ist mit dem USB-UART verbunden. Kann nach dem Booten als normaler I/O Pin verwendet werden.
9 GPIO3 (RXD) Serieller Eingang des ESP, direkt mit dem Ausgang des USB-UART verbunden, daher nicht anders verwendbar.
10 RESET Hat 10 kΩ Pull-Up Widerstand und einen Taster nach GND
11 A: GPIO2 (TXD1)
B: GPIO4 (/RES)
A: Beim Booten serieller Ausgang, darf dabei nicht auf LOW gezogen werden. Kann nach dem Booten als normaler I/O Pin verwendet werden.
B: Setzt das Display bei LOW Pegel zurück.
12 CHIP_EN Muss auf HIGH liegen, damit der Chip arbeitet. Hat 10 kΩ Pull-Up Widerstand. LOW=Power Down, HIGH=Enabled

Untere Stiftleiste:

Stift Name Beschreibung
1 GND Gemeinsame Masse
2 5 V Spannungsversorgung 5 V 500 mA
3 3,3 V Spannungsversorgung 3,3 V (Eingang 500 mA oder Ausgang max. 200 mA)
4 GND Gemeinsame Masse
5 A: GPIO4 (SDA)
B: GPIO0
A: I²C Daten zum Display mit 10 kΩ Pull-Up Widerstand.
B: LOW beim Booten aktiviert Firmware-Upload, muss zum normalen Start offen sein oder schwach auf HIGH gezogen werden. Gibt beim Booten LOW Impulse aus. Hat einen 10 Ω Pull-Up Widerstand und ist mit einem Taster nach GND verbunden.
6 A: GPIO0
B: GPIO2 (SDA,TXD1)
A: LOW beim Booten aktiviert Firmware-Upload, muss zum normalen Start offen sein oder schwach auf HIGH gezogen werden. Gibt beim Booten LOW Impulse aus. Hat einen 10 kΩ Pull-Up Widerstand und ist mit einem Taster nach GND verbunden.
B: I²C Daten zum Display, mit 10 kΩ Pull-Up Widerstand. Beim Booten serieller Ausgang, darf dabei nicht auf LOW gezogen werden.
7 GPIO15 (CS) Normaler I/O Pin oder SPI Chip Select. Muss beim Booten auf LOW gezogen werden. Hat 10 kΩ Pull-Down Widerstand.
8 GPIO13 (MOSI) Normaler I/O Pin oder SPI Daten. Wird beim Booten schwach auf HIGH gezogen.
9 GPIO12 (MISO) Normaler I/O Pin oder SPI Daten. Wird beim Booten schwach auf HIGH gezogen.
10 GPIO14 (SCK) Normaler I/O Pin oder SPI Takt. Wird beim Booten schwach auf HIGH gezogen.
B: I²C Takt zum Display, mit 10 kΩ Pull-Up Widerstand
11 A: GPIO16 (/RES)
B: GPIO16
Ist im Deep-Sleep Modus der Ausgang vom Wakeup-Timer. Gibt beim Booten einen HIGH Impuls aus. An diesem Pin funktionieren TwoWire (I²C), attachInterrupt() und pinMode INPUT_PULLUP nicht. Dafür gibt es nur hier den pinMode INPUT_PULLDOWN_16.
A: Setzt das Display bei LOW Pegel zurück.
12 ADC Analoger Eingang für 0 bis 3,2 V weil mit Spannungsteiler 220 kΩ + 100 kΩ.

Die Stromversorgung erfolgt wahlweise über ein 3,3 V oder 5 V Netzteil am entsprechenden Pin, oder über USB. Das Display nimmt zusätzlich zum ESP Chip ca. 30 mA auf. Da die USB Buchse direkt mit dem 5 V Anschluss verbunden ist, soll bei Nutzung von USB nicht gleichzeitig ein 5 V Netzteil angeschlossen sein. Der eingebaute Spannungsregler reicht gerade für das Board aus, soll daher extern nur minimal belastet werden.

Trotz des Akku-Anschlusses eignet sich das Board nicht gut für Batterie-Betrieb. Denn die Ruhestromaufnahme beträgt etwa 7 mA. Unterhalb von 3,3 V Akku-Spannung fällt das Board aus und saugt ihn mit 80 mA leer, bis er entweder kaputt geht oder seine integrierte Schutzschaltung (falls vorhanden) abschaltet. Beim Aufladen wird die Platine ziemlich warm, was für die Lebensdauer des Displays schlecht ist.

Die beiden Taster ziehen die Leitungen RESET und GPIO0 direkt ohne Schutzwiderstände auf LOW. Die Boards besitzen die gleiche Reset-Automatik wie das NodeMCU Board.

Zur Programmierung des Displays empfehle ich entweder meine schlanke OLED Klasse oder die Libraries SSD1306 und GFX von Adafruit.

Man sollte die geringe Lebensdauer des OLED beachten. Bei maximaler Helligkeit lässt die Leuchtkraft der betroffenen Bildpunkte schon nach einem Jahr deutlich nach.

Stromversorgung

Die häufigste Ursache von Fehlfunktionen ist eine zu schwache Stromversorgung. Der ESP Chip benötigt eine stabile Spannungsversorgung zwischen 2,8 und 3,6 Volt bei einer Stromaufnahme von 5 µA bis 430 mA. Innerhalb dieses Bereiches darf sich sich die Spannung nur langsam ändern. Es ist also ein schneller starker Spannungsregler erforderlich, der extreme Strom-Schwankungen ausregeln kann.

Ich empfehle zur Verbesserung der Zuverlässigkeit einen 100 µF Kondensator direkt an den Anschlüssen VDD und GND des ESP-Moduls. Beim NodeMCU Board ist dieser bereits vorhanden. Ohne diesen Kondensator liefen meine ESP-Module keine 4 Wochen fehlerfrei durch.

Die Leitungen der Stromversorgung sollen möglichst kurz sein und sternförmig an einem Verteil-Punkt zusammen kommen. Das gilt ganz besonders für GND. Steckbretter und Dupont Kabel sind aufgrund der hohen Kontaktwiderstände schlecht zur Stromversorgung geeignet.

Betrieb an Batterien/Akkus

Boards mit USB Schnittstelle eignen sich nicht gut für sparsamen Batteriebetrieb, da sie viel Ruhestrom aufnehmen. Hier sind minimale Module ohne Schnickschnack (wie das ESP-12F) die bessere Wahl. Ich empfehle dazu folgende Stromversorgung:

Wenn der ESP Chip wegen Unterspannung ausfällt nimmt er ständig ca. 80 mA auf. Lithium- und Blei-Akkus müssen daher unbedingt mit einem Tiefentladeschutz ausgestattet werden. Manche Lithium-Akkus enthalten so eine Schutz-Schaltung bereits intern.

Die Spannungsregler MCP170x lehne ich ab, weil sie mit ihren 250 mA zu knapp ausgelegt sind. Der ESP braucht zeitweise 430 mA.

Deep-Sleep Modus

Im Deep-Sleep Modus wird die CPU samt Peripherie angehalten. Die GPIO Pins sind dann hochohmig. Nur die integrierte RTC Uhr läuft weiter, allerdings viel ungenauer als im Normalbetrieb (typisch 2 Minuten Abweichung pro Tag). Ein schlafender ESP ist über WLAN nicht erreichbar. Beim Wieder-Aufwachen muss der ESP wie nach einem Reset neu booten.

Im Deep-Sleep Modus beträgt die Stromaufnahme des ESP Chips (incl. Flash Speicher) etwa 20 µA.

Beim Arduino Framework aktiviert man den Deep-Sleep Modus so:

  ESP.deepSleep(60000000); 
  delay(100);

Als Parameter gibt man an, in wie vielen Mikrosekunden der Chip wieder aufwachen soll. Der Chip kann bis zu 71 Minuten schlafen, ab SDK 2.1 (und Arduino Core 2.4.0) sind es 3,5 Stunden. Darüber wacht er nicht mehr auf. Der Wert 0 lässt den Chip für immer schlafen, oder bis zum nächsten externen Reset. Da der Timer von einem ungenauen R/C Oszillator getaktet wird, kann die Zeit 10 Prozent vom Soll abweichen.

Ohne das delay() schläft der ESP unter Umständen nicht immer zuverlässig ein.

Wenn man den Wakeup-Timer benutzt, muss der Timer-Ausgang GPIO16 wie folgt mit dem RESET Eingang des Chips verbunden werden:

Wakeup Schaltung

Dadurch wird das Reset-Signal ein bisschen verzögert und verlängert. Schau in den Schaltplan von deinem Board bzw. Modul, denn diese Bauteile sind wahrscheinlich zumindest teilweise bereits vorhanden.

Manche ESP Module hängen sich trotz korrekter Beschaltung nach dem Aufwachen auf, weil sie mit einem ungeeigneten Flash Speicher Chip (z.B. CT 580A-UGLI) bestückt sind. Siehe dazu diese Diskussion und einen möglichen Workaround in Bug-Report 6318.

Weil die Firmware beim Aufwachen neu bootet, geht der Inhalt des RAM verloren. Die RTC enthält jedoch 512 Bytes verfügbaren Speicher, der den Deep-Sleep Modus (jedoch nicht Power-Down) überlebt. Mit den Arduino Funktionen ESP.rtcUserMemoryRead() und ESP.rtcUserMemoryWrite() kommt man dort heran.

Power-Down Modus

Im Power-Down Modus ist der Chip komplett abgeschaltet. Es fließt nur noch ein kleiner Leckstrom von etwa 5 µA (incl. Flash). Leider enthalten viele Module einen Pull-Up Widerstand an diesem Pin, welcher die Ruhestromaufnahme erheblich erhöht.

Der Power-Down Modus wird durch eine fallende Flanke am CHIP_EN Eingang ausgelöst, aber erst nachdem die Firmware gestartet ist. Wenn CHIP_EN zu früh auf LOW gezogen wird, nimmt der Chip ca. 2,5 mA auf!

Die steigende Flanke am CHIP_EN Eingang bewirkt, dass der Power-Down Modus verlassen wird und die Firmware neu startet.

Stromaufnahme im Reset

Während der RESET Eingang auf LOW gehalten wird nimmt der Chip ca. 23 mA auf. Der RESET Eingang eignet sich daher nicht zum Strom-sparen.

Anschluss an 5 V Mikrocontroller

Der ESP Chip verträgt maximal 3,6 V an allen Pins. Die Signale von 5 V Mikrocontrollern kann man so mit einem Spannungsteiler herabsetzen:

Für die Rückrichtung sind keine weiteren Maßnahmen notwendig, wenn dem Mikrocontroller 3,3 V als HIGH Pegel genügen (bei AVR ist das der Fall). Falls du den Signalpegel für die Rückrichtung dennoch auf 5V erhöhen willst oder musst, kannst du dazu zum Beispiel den 74LVC1G17 verwenden.

Flash Speicher

Der ESP82xx Chip kopiert seine Firmware blockweise von einem externen Flash Speicher in sein internes RAM. Von dort aus wird der Code dann ausgeführt. Der Chip kann bis zu 16 MByte Flash Speicher adressieren, davon jedoch nur maximal 1 MByte als Programmspeicher nutzen. Die Basis Firmware belegt ungefähr ein Viertel davon.

Der ESP8266 unterstützt folgende Zugriffsarten auf den Flash Speicher:

Welche der vier Varianten tatsächlich funktionieren, hängt vom eingebauten Speicherchip ab. Die meisten können DIO mit 40 Mhz. Zum Ändern der Zugrifssparameter muss man die Firmware modifizieren.

Der ESP8285 hat 1 MByte Flash intern, welcher nur DOUT mit 40 MHz unterstützt. Dafür hat der Chip zwei weitere I/O Pins frei, nämlich GPIO9 und GPIO10.

Beim Start gibt der Chip ein paar Diagnose-Meldungen mit 74880 Baud aus, noch bevor die Firmware gestartet wird. Im Fehlerfall lohnt es sich, diese Meldungen mit einem Terminal-Programm anzuzeigen.

Performance

Um einen Eindruck zu bekommen, wie stark sich die Varianten der Flash Anbindung auf die Gesamt-Performance auswirken, habe ich zwei Testreihen durchgeführt. Die erste Testreihe vergleicht die Netzwerk-Performance: beste Antwortzeit beim Ping mit 1 kB. Die zweite Testreihe vergleicht die Rechenleistung: Anzahl von Multiplikationen in einem festen Zeitfenster.

CPU Freq. Flash Freq. Flash Mode Ping Rechenleistung
160 MHz 80 MHz QIO und QOUT 4 ms 871
DIO und DOUT 4 ms 870
40 MHz QIO und QOUT 4 ms 870
DIO und DOUT 5 ms 870
80 MHz 80 MHz QIO und QOUT 4 ms 435
DIO und DOUT 4 ms 435
40 MHz QIO und QOUT 4 ms 434
DIO und DOUT 5 ms 434
Wie man sieht, ist die Netzwerk-Performance von den Taktfrequenzen weitgehend unabhängig.

Firmware Hochladen

Der ESP Chip enthält einen unveränderlichen Bootloader, welcher ein Firmware-Upgrade über den seriellen Port ermöglicht. Beim Reset startet immer zuerst dieser Bootloader. Er erwartet folgende Steuersignale:

Firmware-Upload:

Normaler Start:

Der Bootloader gibt auf GPIO1 und GPIO2 Meldungen mit 74880 Baud aus. Nach dem Start können alle vier Pins als frei programmierbare I/O Anschlüsse verwendet werden. Daraus ergibt sich die folgende sinnvolle Grundschaltung:

Um den ESP-Chip in den Firmware-Upload Modus zu versetzen, musst du beide Taster drücken und dann den Reset Taster zuerst loslassen. Es geht darum, dass GPIO0 beim Start auf LOW steht.

Der Widerstand vor GPIO0 schützt vor Kurzschluss, falls man den Pin als Ausgang programmiert. Viele Boards haben die Reset-Schaltung von NodeMCU kopiert, mit der man sich das Drücken der Taster ersparen kann. Sie funktioniert allerdings nicht immer zuverlässig.

Auf der Download Seite kann man ein grafisches Tool zum Flashen bekommen. Leider ist das nur für Windows. Alternativ bin ich immer gut mit dem Python Script esptool.py, klar gekommen, welches meine beiden weiter unten bereitgestellten Firmware-Pakete enthalten. Zum Ausführen musst Du vorher Python installieren, sowie die Library für serielle Ports:

In Ubuntu/Debian: sudo apt install python3 python3-serial
In Windows Python installieren, dann: pip install serial

Mit Hilfe des esptool kann man folgendermaßen die Größe des Flash Speichers anhand seiner ID Nummer ermitteln:

Windows: python  esptool.py --port COM6 flash_id
Linux:   python3 esptool.py --port /dev/ttyUSB0 flash_id
4013 512 kByte
4014 1 MByte
4015 2 MByte
4016 4 MByte

Der eigentliche Upload einer Firmware-Datei geschieht mit dem Befehl:

python esptool.py --port COM6 write_flash 0x000000 firmware.bin

In diesem Fall ist 0x000000 die Ziel-Adresse im Speicher und firmware.bin die Datei, die dorthin übertragen wird. Keine Angst: Wenn das Hochladen der Firmware (warum auch immer) fehlschlägt, kann man es einfach nochmal versuchen. Der Bootloader gibt im Fehlerfall eventuell hilfreiche Meldungen mit 74880 Baud aus.

Original AT-Firmware

Die AT-Firmware ermöglicht es, das Modul wie ein Modem zu benutzen. Durch das Absetzen von Befehlen auf der seriellen Schnittstelle konfiguriert man das Modul, baut Verbindungen auf und überträgt Daten. Das Modul dient also als Netzwerk-Adapter für einen angeschlossenen Mikrocontroller. Auf der Seite I/O Schnittstellen Module für WLAN stelle ich eine solche Beispiel-Anwendung vor, und im Buch Einstieg in die Elektronik mit Mikrocontrollern erkläre ich detailliert, wie man so etwas macht.

Die AT Firmware sendet und empfängt im 100 ms Raster, maximal 2048 Bytes pro Intervall.

Für Module mit nur 512 kByte Flash Speicher muss man die AT-Firmware 0.50.0.0 vom SDK 1.4.0 verwenden. Das ist die letzte Version, die noch dort hinein passt. Da die Firmware in mehrere Dateien aufgeteilt ist, muss man sie so installieren:

512 kByte python esptool.py --port COM6 write_flash 0x000000 noboot/eagle.flash.bin 0x040000 noboot/eagle.irom0text.bin 0x03e000 blank.bin 0x07e000 blank.bin 0x07c000 esp_init_data_default.bin

Für alle größeren Module habe ich mir die zuverlässige AT-Firmware 1.1.0.0 vom SDK 1.5.4 gesichert. Sie wird mit folgendem Befehl installiert:

1 MByte python esptool.py --port COM6 write_flash 0x000000 boot_v1.5.bin 0x001000 512+512/user1.1024.new.2.bin 0x0fc000 esp_init_data_default.bin 0x07e000 blank.bin 0x0fe000 blank.bin
2 MByte python esptool.py --port COM6 write_flash 0x000000 boot_v1.5.bin 0x001000 512+512/user1.1024.new.2.bin 0x01fc000 esp_init_data_default.bin 0x07e000 blank.bin 0x1fe000 blank.bin
4 MByte python3 esptool.py --port COM6 write_flash 0x000000 boot_v1.5.bin 0x001000 512+512/user1.1024.new.2.bin 0x03fc000 esp_init_data_default.bin 0x07e000 blank.bin 0x3fe000 blank.bin

Falls deine AT-Firmware älter als 0.50.0.0 ist, rate ich zu einem Upgrade, da frühere Versionen noch ziemlich instabil liefen.

AT Befehle

In diesem Kapitel beschreibe ich nur die Befehle, die mir wichtig sind. Sie funktionieren mindestens mit der AT-Firmware von Version 0.21.0.0 bis 3.0.2, vielleicht auch mit neueren. Beachte beim Programmieren, dass das Format der Antworten je nach Firmware Version etwas variiert.

Zum Testen von Netzwerkverbindungen empfehle ich das Kommandozeilen-Programm Netcat (nc) oder alternativ Nmap (ncat). Außerdem solltest Du Dir unbedingt das Programm Wireshark anschauen, falls du es noch nicht kennst. Als Terminalprogramm zur manuellen Eingabe der AT Befehle empfehle das Hammer Terminal, Putty oder Cutecom.

Die AT Firmware kommuniziert in der Regel mit 115200 Baud. Manche Module sind auf 57600 oder 9600 Baud vor-eingestellt. Das Format für Zeilenumbrüche ist CR+LF.

Nach dem Hardware-Reset zeigt das Terminal Programm einige unlesbare Zeilen an, und dann "ready". Danach kannst du Befehle eintippen. Sollten jedoch nur unlesbare Zeichen erscheinen, stelle die Baudrate auf 74880 um, dann kannst du die Fehlermeldungen des Bootloaders lesen.

Befehle:

AT

wird einfach mit "OK" beantwortet. Der Befehl hat keine weitere Funktion, er eignet sich prima, um die serielle Verbindung zu testen.

ATE0

schaltet das Echo der Befehle aus. Dieser Befehl kann die Programmierung vereinfachen und die Geschwindigkeit geringfügig verbessern. Mit der Zahl 1 schaltet man das Echo wieder an. Für manuelles Testen lässt man das Echo am besten eingeschaltet.

AT+GMR

Zeigt die Version der Firmware an, zum Beispiel:

AT version:1.1.0.0(May 11 2016 18:09:56)
SDK version:1.5.4(baaeaebb)
compile time:May 20 2016 15:06:44
OK

AT+CWMODE=1

stellt ein, dass das Modul sich in ein bestehendes WLAN Netz einbuchen soll.

AT+RST

löst einen Software-Neustart aus. Dies ist nach dem Wechsel von CWMODE notwendig.

AT+CWLAP

listet alle erreichbaren Access Points auf. Zum Beispiel:

+CWLAP:(4,"EasyBox-4D2D18",-72,"18:83:bf:4d:2d:b2",2,-46)
+CWLAP:(4,"UPC2827302",-63,"88:f7:c7:52:40:9d",6,-22)
+CWLAP:(0,"Unitymedia WifiSpot",-64,"8a:f7:c7:52:40:9f",6,-22)
+CWLAP:(3,"Pussycat",-45,"5c:49:79:2d:5b:cd",7,-4)
OK

AT+CWJAP="Pussycat","supersecret"

das Modul verbindet sich mit dem WLAN Netz "Muschikatze" und dem angegebenen WPA/WPA2 Passwort. Diese Einstellung wird automatisch dauerhaft gespeichert und beim nächsten Neustart aktiv. Das Passwort muss mindestens 8 Zeichen lang sein!

AT+CWJAP?

zeigt an, mit welchem Access Point das Modul gerade verbunden ist. Zum Beispiel:

+CWJAP:"Pussycat","5c:49:79:2d:5b:cd",7,-60
OK

AT+CIFSR

zeigt die IP-Adresse und MAC Adresse des Moduls an zum Beispiel:

+CIFSR:STAIP,"192.168.0.111"
+CIFSR:STAMAC,"5c:cf:7f:8b:a9:f1"
OK

AT+CIPMUX=1

Stellt ein, dass das Modul mehrere Verbindungen erlauben soll. In diesem Modus sind bis zu 5 gleichzeitige Verbindungen möglich. Die folgenden Beispiele setzen alle voraus, dass dieser Modus aktiviert wurde.
Das Gegenteil wäre der AT+CIPMUX=0, wo das Modul nur als Client und mit nur einer Verbindung genutzt werden kann. In diesem Fall entfällt bei den drauf folgenden Befehlen die Kanalnummer.

AT Firmware als TCP Server benutzen

AT+CIPSERVER=1,5000

dieser Befehl schaltet den Server ein, so dass er Verbindungen auf Port 5000 annimmt. Er kann danach (je nach Firmware Version) maximal 4 oder 5 Verbindungen gleichzeitig annehmen. Danach könntest du zum Beispiel mit Netcat eine Netzwerkverbindung zu diesem Mini-Server aufbauen:

nc 192.168.0.111 5000
Hello
World!

Am seriellen Port des Moduls erscheinen dabei folgende Meldungen:

0,CONNECT
+IPD,0,7:Hello
+IPD,0,8:World!

"0,CONNECT" bedeutet, dass der TCP Server eine Verbindung auf Kanal 0 angenommen hat.
"+IPD,0,7:" bedeutet, dass der Server auf Kanal 0 genau 7 Bytes (nämlich "Hello\r\n") empfangen hat.
Nach dem Doppelpunkt folgen genau diese 7 Bytes. Achtung: Wenn der empfangene Text nicht mit einem Zeilenumbruch endet, dann kommt die nächste +IPD Meldung direkt dahinter in der selben Zeile!

AT+CIPSEND=0,9
> Welcome!

Mit diesem Befehl sendest du eine Antwort an den Computer. Die 0 ist wieder die Kanalnummer und die 9 sind die Anzahl der zu sendenden Bytes (maximal 2048). Nachdem das Modul mit ">" Antwortet, gibst du die zu sendenden Zeichen (oder Bytes) ein. Das Modul bestätigt den Sendevorgang nach kurzer Zeit mit

SEND OK

Laut Dokumentation soll man nach diesem Kommando mindestens 1 Sekunde warten. Bei meinen Tests funktionierten aber 4 Kommandos pro Sekunde weitgehend zuverlässig.

AT+CIPCLOSE=0

mit diesem Befehl schließt du die Verbindung auf Kanal 0. Das Modul antwortet mit

0,CLOSED
OK

AT Firmware als TCP Client benutzen

AT+CIPSTART=0,"TCP","mail.stefanfrings.de",25

Kanal 0 baut eine TCP Verbindung zu dem angegebenen Server auf Port 25 auf. Anstelle des Hostnamens kannst du auch eine IP Adresse verwenden. Die Antwort lautet zum Beispiel:

0,CONNECT
OK
+IPD,0,96:220 wp039.webpack.hosteurope.de 
   ESMTP Host Europe Mail Service 
   Sat, 08 Apr 2017 23:37:19 +0200

Da dieser Mail-Server sofort antwortet, siehst du auch direkt eine +IPD Zeile. Der Text hinter dem Doppelpunkt ist die Antwort des Servers. Danach kann man mit AT+CIPSEND wie bereits oben beschrieben Daten an den Server senden. Mit AT+CIPCLOSE schließt man die Verbindung.

AT Firmware für UDP benutzen

Beim UDP Protokoll entfällt der Verbindungsaufbau, und damit auch die Begrenzung auf 5 gleichzeitige Verbindungen. Man sendet einzelne Nachrichten an die gewünschten Empfänger und hofft, dass sie zügig dort ankommen. Im Gegensatz zu TCP verzichtet das UDP Protokoll auf die Wiederholung von verlorenen Paketen. Es garantiert nicht einmal, dass alle Pakete in der richtigen Reihenfolge beim Empfänger ankommen (das passiert in lokalen Netzen allerdings praktisch nie).

UDP ist einfacher zu programmieren und nutzt die Übertragungsstrecke effizienter als TCP. Es eignet sich besonders gut zur regelmäßigen Übertragung von Messwerten, wenn einzelne Aussetzer tolerierbar sind (z.B. Temperaturfühler). Leider können Javascripte in Webseiten das UDP Protokoll nicht nutzen.

AT+CIPSTART=0,"UDP","0",0,3002,0

Nach diesem Befehl kann das Modul UDP Nachrichten auf Port 3002 von beliebigen Absendern empfangen. Es kann aber keine Nachrichten senden. Zum Test kannst du das Programm Netcat benutzen:

nc -u 192.168.0.111 3002
Lets start!

Wobei du die IP-Adresse deines WLAN Moduls angeben musst. Wenn Du dann in Netcat etwas zum senden eintippst, wird das Modul den Empfang der Nachricht mit +IPD signalisieren:

+IPD,0,12:Lets start!

AT+CIPSTART=0,"UDP","0",0,3002,2

Das Modul empfängt UDP Nachrichten auf Port 3002 von beliebigen Absendern, und es kann Antworten mit AT+CIPSEND zurück schicken. Die Antworten werden immer an den Computer gesendet, von dem zuletzt eine Nachricht empfangen wurde.

AT+CIPSTART=0,"UDP","192.168.0.5",3001,3002,0

Das Modul kann UDP Nachrichten auf Port 3002 von beliebigen Absendern empfangen. Mit dem AT+CIPSEND Befehl wird es Nachrichten an den Computer 192.168.0.5 Port 3001 senden.

Soft-AP Modus

Wo kein WLAN Netz existiert, kann das ESP-Modul selbst ein kleines provisorisches Netz aufspannen. Bis zu 4 andere Geräte können sich dann mit dem Modul verbinden, aber sie können sich nicht gegenseitig sehen. Der Soft-AP Modus eignet sich gut dazu, ESP Module untereinander zu verbinden. Die Nutzung mit Smartphones und Laptops kann ich nicht empfehlen, da sie bei vielen Geräten unzuverlässig funktioniert. Im Soft-AP Modus erreichst du das WLAN Modul mit der fest codierten IP-Adresse 192.168.4.1.

AT+CWMODE=2
AT+CWSAP="ESP-Modul","supersecret",5,3
AT+RST

Diese Befehlsfolge aktiviert den Soft-AP Modus, und stellt den Namen des Netzes und das Passwort ein. Die 5 gibt den WLAN Funk-Kanal an (0-13), und die 3 legt fest, dass WPA2 Verschlüsselung verwendet wird. Diese Einstellung wird automatisch dauerhaft gespeichert und beim nächsten Neustart aktiv. Das Passwort muss mindestens 8 Zeichen lang sein!

Es gibt auch einen Kombinierten Modus (AT+CWMODE=3), in welchem sich das Modul mit einem bestehenden WLAN Netz verbindet und außerdem ein eigenes zweites Netz aufspannt.

Möglichkeiten der Programmierung

Anstatt den ESP82xx wie ein Modem über AT-Befehle zu benutzen, kann man auch eigene Firmware schreiben, die direkt auf dem Mikrocontroller des ESP Moduls läuft. Der chinesische Hersteller Espressif stellt dazu ein Software Development Kit (SDK) für die Programmiersprache C bereit, das allerdings recht kompliziert anzuwenden ist.

Das NodeMCU Projekt bemüht sich darum, die Programmierung stark zu vereinfachen, indem man auf LUA Scripte setzt. Allerdings leidet dieser Lösungsansatz an notorischem Speichermangel. Das eigene LUA Script muss samt Daten (Variablen) in ca. 40 kByte RAM gequetscht werden. Scrolle auf der Projektseite nach unten, dort gibt es einen Link zu dem "custom firmware build service", wo man sich eine individuell zusammengestellte Firmware erzeugen lässt.

Es gibt einen Python Interpreter für den ESP8266, der ebenfalls schnell an die Grenzen des knappen Speichers stößt.

Die angenehmste Lösung ist meiner Meinung nach der ESP8266 Core für Arduino. Man programmiert dort in C++. Die Installation der Arduino IDE ist kinderleicht - kein Vergleich zum Espressif SDK.

Alle vier Varianten haben eines gemeinsam: Es gibt keinen Debugger. Man muss sich mit seriellen Textausgaben zufrieden geben..

Erste Schritte mit Arduino

Der Arduino Core kombiniert das SDK des Herstellers mit dem Arduino Framework, um die Programmierung in C++ zu erleichtern.

Falls das nicht klappt oder deine Programme instabil laufen, versuche mein Bundle für Linux oder Windows. Das ist eine Kombination aus der portablen Arduino IDE 1.8.16 mit ESP8266 Core 2.3.0, die ganz sicher zuverlässig funktioniert.

Der Menüpunkt "Burn Bootloader" ist für den ESP8266 irrelevant, weil er einen fest installierten Bootloader hat, denn man nicht verändern kann.

Unter Linux funktioniert bei mir der serielle Monitor von der Arduino IDE manchmal nicht, aber mit einem externen Programm (z.B. Cutecom) geht es immer. Irgend etwas stimmt da bei der Umschaltung der Baudrate nicht.

In Arduino Quelltexten entsprechen die Pin Nummern den GPIO Nummern. Man schreibt digitalWrite(4,HIGH) um den GPIO4 auf HIGH zu setzen. Der analoge Eingang heisst in Arduino A0 oder 17.

Lies die Dokumentation von Arduino und dem ESP8266 Core in der aktuellen Version oder Version 2.3.0. Die Doku von der Version 2.3.0 ist auch im obigen Bundle enthalten.

Beispiel Sketche

Die Arduino IDE zeigt im Menü "Datei/Beispiele" Sketche an, welche die Verwendung des ESP8266 Core demonstrieren. Darüber hinaus zeige ich dir hier Sketche, die du für erste Versuche 1:1 in deine Arduino IDE kopieren kannst.

LED Blinker

Der erste Sketch lässt die blaue LED auf dem ESP-12 Modul blinken und gibt auf dem Seriellen Port immer abwechselnd "Tick" und "Tack" aus. Außerdem bucht er sich ins WLAN Netz ein, so dass es mit einem Ping Befehl erreichbar ist.

#include <Arduino.h>
#include <ESP8266WiFi.h>

// The ESP-12 has a blue LED on GPIO2
#define LED 2

// Name and password of the WLAN access point
#define SSID "Pussycat"
#define PASSWORD "supersecret"

// Runs once at startup
void setup() {
    Serial.begin(74880);
    pinMode(LED, OUTPUT);

    WiFi.mode(WIFI_STA);
    WiFi.begin(SSID, PASSWORD);
}

// Main loop, executed repeatedly
void loop() {
    digitalWrite(LED, LOW);
    Serial.println(F("Tick"));
    delay(500);

    digitalWrite(LED, HIGH);
    Serial.println(F("Tack"));
    delay(500);
}

Im Menü "Werkzeuge" kannst du Debug Meldungen aktivieren. Dazu muss das Programm erneut compiliert und hochgeladen werden. Den seriellen Port musst du aber selbst öffnen, wie im obigen Beispiel (mit Serial.begin). Wenn im "Debug Level" die Option "WiFi" ausgewählt wurde, bekommst du Meldungen über den Verbindungsaufbau zum WLAN Netz.

Für das Debugging ist die ungewöhnliche Baudate 74880 vorteilhaft, weil der Bootloader vor dem Programmstart ebenfalls Meldungen mit 74880 Baud ausgibt. Die kannst du dann auch lesen.

UDP Server

Der zweite Sketch empfängt UDP Nachrichten und sendet sie als Echo zurück:

#include <Arduino.h>
#include <ESP8266WiFi.h>
#include <WiFiUDP.h>

// The ESP-12 has a blue LED on GPIO2
#define LED 2

// Name and password of the WLAN access point
#define SSID "Pussycat"
#define PASSWORD "supersecret"

// The server accepts connections on this port
#define PORT 5444
WiFiUDP udpServer;

// Buffer for incoming UDP messages
char udp_buffer[WIFICLIENT_MAX_PACKET_SIZE+1];


// Receive UDP messages and send an echo back
void process_incoming_udp() {   
    if (udpServer.parsePacket()) {
        // Fetch received message
        int len=udpServer.read(udp_buffer,sizeof(udp_buffer)-1);
        udp_buffer[len] = 0;
                
        // Display the message
        Serial.print(F("Received from "));
        Serial.print(udpServer.remoteIP());
        Serial.print(":");
        Serial.print(udpServer.remotePort());
        Serial.print(": ");
        Serial.println(udp_buffer);
        
        // Send echo back
        udpServer.beginPacket(udpServer.remoteIP(), udpServer.remotePort());
        udpServer.print(F("Echo: "));
        udpServer.print(udp_buffer); 
        udpServer.endPacket();
        
        // Execute some commands
        if (strstr(udp_buffer, "on")) {
            digitalWrite(LED, LOW);
            udpServer.println(F("LED is on"));
        }
        else if (strstr(udp_buffer, "off")) {
            digitalWrite(LED, HIGH);
            udpServer.println(F("LED is off"));
        }
    }    
}


// Optional: Notify about AP connection status changes
void check_ap_connection() {
    static wl_status_t preStatus = WL_DISCONNECTED;
    
    wl_status_t newStatus = WiFi.status();
    if (newStatus != preStatus) {
        if (newStatus == WL_CONNECTED) {
            digitalWrite(LED, LOW);
            
            // Display the own IP address and port
            Serial.print(F("AP connection established, listening on "));
            Serial.print(WiFi.localIP());
            Serial.print(":");
            Serial.println(PORT);
        }
        else {
            digitalWrite(LED, HIGH);
            Serial.println(F("AP conection lost"));
        }
        preStatus = newStatus;
    }
}


// Runs once at startup 
void setup() {
    // LED off
    pinMode(LED, OUTPUT);
    digitalWrite(LED, HIGH);

    // Initialize the serial port
    Serial.begin(115200);
    
    // Give the serial monitor of the Arduino IDE time to start
    delay(500);

    // Use an external AP
    WiFi.mode(WIFI_STA);
    WiFi.begin(SSID, PASSWORD);

    // Start the UDP server
    udpServer.begin(PORT);
}


// Main loop, executed repeatedly
void loop() {
    process_incoming_udp();
    check_ap_connection();
}

Wenn die Verbindung zum AP aufgebaut wurde, geht die blaue LED an. Auf dem seriellen Port gibt das Programm hilfreiche Statusmeldungen aus. So kannst du den Server mit Netcat testen:

nc -u 192.168.0.111 5444
Monkey see monkey do
Echo: Monkey see monkey do
off
Echo: off
LED is off
on
Echo: on
LED is on

Der UDP Service ist einfach zu programmieren, weil man sich darauf verlassen kann, dass Nachrichten 1:1 wie gesendet eintreffen, also weder zerstückelt noch zusammengefügt. Außerdem braucht man sich nicht um den Verbindungs-Aufbau kümmern. Der größte Nachteil ist, dass UDP Nachrichten manchmal unbemerkt verloren gehen - besonders in drahtlosen Netzen. In seltenen Fällen erreichen schnell aufeinander folgende Pakete ihren Empfänger in veränderter Reihenfolge.

TCP Server

Der dritte Sketch zeigt einen TCP Server, der mehrere Verbindungen gleichzeitig bedient:

#include <Arduino.h>
#include <ESP8266WiFi.h>

// The ESP-12 has a blue LED on GPIO2
#define LED 2

// Name and password of the WLAN access point
#define SSID "Pussycat"
#define PASSWORD "supersecret"

// The server accepts connections on this port
#define PORT 5333
WiFiServer tcpServer(PORT);

// Objects for connections
#define MAX_TCP_CONNECTIONS 5
WiFiClient clients[MAX_TCP_CONNECTIONS];

// Buffer for incoming text
char tcp_buffer[MAX_TCP_CONNECTIONS][30];


/**
 * Collect lines of text.
 * Call this function repeatedly until it returns true, which indicates
 * that you have now a line of text in the buffer. If the line does not fit
 * (buffer to small), it will be truncated.
 *
 * @param source The source stream.
 * @param buffer Target buffer, must contain '\0' initiallly before calling this function.
 * @param bufSize Size of the target buffer.
 * @param terminator The last character that shall be read, usually '\n'.
 * @return True if the terminating character was received.
 */
bool append_until(Stream& source, char* buffer, int bufSize, char terminator) {
    int data=source.read();
    if (data>=0) {
        int len=static_cast<int>(strlen(buffer));
        do {
            if (len<bufSize-1) {
                buffer[len++]=static_cast<char>(data);
            }
            if (data==terminator) {
                buffer[len]='\0';
                return true;
            }
            data=source.read();
        }
        while (data>=0);
        buffer[len]='\0';  
    }
    return false;
}


// Optional: Notify about AP connection status changes
void check_ap_connection() {
    static wl_status_t preStatus = WL_DISCONNECTED;
    
    wl_status_t newStatus = WiFi.status();
    if (newStatus != preStatus) {
        if (newStatus == WL_CONNECTED) {
            digitalWrite(LED, LOW);
            
            // Display the own IP address and port
            Serial.print(F("AP connection established, listening on "));
            Serial.print(WiFi.localIP());
            Serial.print(":");
            Serial.println(PORT);
        }
        else {
            digitalWrite(LED, HIGH);
            Serial.println(F("AP conection lost"));
        }
        preStatus = newStatus;
    }
}


/** 
 * Put new connections into the array and
 * send a welcome message.
 */
void handle_new_connections() {
    WiFiClient client = tcpServer.available();
    if (client) {
        Serial.print(F("New connection from "));
        Serial.println(client.remoteIP().toString());
        
        // Find a freee space in the array   
        for (int i = 0; i < MAX_TCP_CONNECTIONS; i++) {
            if (!clients[i].connected()) {
                // Found free space
                clients[i] = client;
                tcp_buffer[i][0]='\0';
                Serial.print(F("Channel="));
                Serial.println(i);
                
                // Send a welcome message
                client.println(F("Hello World!"));
                return;
            }
        }
        Serial.println(F("To many connections"));
        client.stop();
    }
}


// Receive TCP messages and send echo back
void process_incoming_tcp() {   
    static int i=0;
    
    // Only one connection is checked in each call
    if (clients[i].available()) {
        // Collect characters until line break
        if (append_until(clients[i],tcp_buffer[i],sizeof(tcp_buffer[i]),'\n')) {        
            // Display the received line
            Serial.print(F("Received from "));
            Serial.print(i);
            Serial.print(": ");
            Serial.print(tcp_buffer[i]);

            // Send an echo back
            clients[i].print(F("Echo: "));
            clients[i].print(tcp_buffer[i]);
            
            // Execute some commands
            if (strstr(tcp_buffer[i], "on")) {
                digitalWrite(LED, LOW);
                clients[i].println(F("LED is on"));
            }
            else if (strstr(tcp_buffer[i], "on")) {
                digitalWrite(LED, HIGH);
                clients[i].println(F("LED is off"));
            }    
            
            // Clear the buffer to receive the next line
            tcp_buffer[i][0]='\0';
        }
    }
    
    // Switch to the next connection for the next call
    if (++i >= MAX_TCP_CONNECTIONS) {
        i=0;
    }
}


// Executes once during start
void setup() {
    // LED off
    pinMode(LED, OUTPUT);
    digitalWrite(LED, HIGH);

    // Initialize the serial port
    Serial.begin(115200);
    
    // Give the serial monitor of the Arduino IDE time to start
    delay(500);

    // Use an external AP
    WiFi.mode(WIFI_STA);
    WiFi.begin(SSID, PASSWORD);

    // Start the TCP server
    tcpServer.begin();
}


// Main loop, executed repeatedly 
void loop() {
    handle_new_connections();
    process_incoming_tcp();
    check_ap_connection();
}

Wenn die Verbindung zum AP aufgebaut wurde, geht die blaue LED an. Auf dem seriellen Port gibt das Programm hilfreiche Statusmeldungen aus. So kannst du die Anwendung mit Netcat testen:

nc 192.168.0.111 5333
Hello World!
I am Groot
Echo: I am Groot
off
Echo: off
LED is off
on
Echo: on
LED is on

Bei TCP kann man sich im Gegensatz zu UDP absolut sicher sein, dass innerhalb einer Verbindung keine Nachrichten verloren gehen und dass die Reihenfolge nicht durcheinander gerät. Aber eine Textzeile kommt unter Umständen zerteilt in mehreren Segmenten an, selbst wenn sie kurz ist. Oder ein Segment liefert zwei Textzeilen, die ursprünglich einzeln gesendet wurden.

Deswegen habe ich die Funktion append_until() entwickelt, die Eingaben Zeilenweise unabhängig von der Segmentierung einsammelt. Im Gegensatz zu Arduinos readStringUntil() hat sie folgende Vorteile:

WiFi Config Service

Bei den obigen Beispielen wurden die Zugangsparameter für den WLAN Access Point einfach fest in den Quelltext geschrieben. Für Nicht-Entwickler ist diese Vorgehensweise jedoch ungeeignet. Eigentlich wurde dazu WPS erfunden, doch das hat bei mir (auch auf anderen Geräten) so oft nicht funktioniert, dass ich es gar nicht mehr verwende.

Eine naheliegende Alternative wäre, ein provisorisches WLAN Netz zu erzeugen um dort eine Webseite zur Konfiguration bereit zu stellen. Mein kleines WiFi-Monitor Projekt enthält die Klasse ConfigService, welche genau das tut.

An dem folgenden verkürzten Quelltext siehst du, wie einfach der WiFi Config Service anzuwenden ist:

#include "ConfigService.h"

ConfigService configService(80);

void setup() {
    configService.begin(5,"WiFi Monitor","");
    ...
}

void loop() {
    configService.run();
    ...
}

Nach dem Einschalten der Stromversorgung aktiviert das Programm für 5 Minuten ein provisorisches Netz mit dem Namen "WiFi Monitor" ohne Passwort. Du kannst deinen Computer oder Smartphone mit diesem Netz verbinden und dann die Seite http://192.168.4.1 aufrufen:

Dort gibst du die Zugangsdaten deines Access Points (z.B. Fritz Box) ein. Wenn sie richtig sind, baut das Modul sofort eine Verbindung zu diesem Gerät auf. Das Hauptprogramm in diesem Beispielprojekt testet die Erreichbarkeit des konfigurierten Webservers und zeigt den Status anhand einer bunten LED an.

Besserer Editor

Der Texteditor von der Arduino IDE ist verglichen mit anderen Entwicklungsumgebungen relativ dumm. Ich benutze gerne Qt Creator, weil es mich mit viel mehr Warnungen vor potentiellen Fehlern und mit besseren Vorschlägen während des Tippens unterstützt. Die Verwendung eines externen Texteditors ist in der alten Arduino IDE 1.x sogar ausdrücklich vorgesehen. Dazu gibt es im Menü File/Preferences eine entsprechende Einstellung. In der neuen Arduino IDE 2.x ist diese Einstellung entfallen, es funktioniert ohne sie.

So öffnest du deinen Arduino Sketch im Qt Creator:

Beispiel includes für den ESP8266 Core in Version 3.1.2:
/home/stefan/.arduino15/packages/esp8266/tools/xtensa-lx106-elf-gcc/3.1.0-gcc10.3-e5f9fec/xtensa-lx106-elf/include
/home/stefan/.arduino15/packages/esp8266/hardware/esp8266/3.1.2/tools/sdk/include
/home/stefan/.arduino15/packages/esp8266/hardware/esp8266/3.1.2/tools/sdk/lwip2/include
/home/stefan/.arduino15/packages/esp8266/hardware/esp8266/3.1.2/cores/esp8266
/home/stefan/.arduino15/packages/esp8266/hardware/esp8266/3.1.2/variants/generic
/home/stefan/.arduino15/packages/esp8266/hardware/esp8266/3.1.2/libraries/ESP8266WiFi/src
/home/stefan/.arduino15/packages/esp8266/hardware/esp8266/3.1.2/libraries/Wire
/home/stefan/.arduino15/packages/esp8266/hardware/esp8266/3.1.2/libraries/SPI
/home/stefan/.arduino15/packages/esp8266/hardware/esp8266/3.1.2/libraries/I2S
/home/stefan/.arduino15/packages/esp8266/hardware/esp8266/3.1.2/libraries/EEPROM
Beispiel includes für eine "portable" Installation mit dem ESP8266 Core in Version 2.3.0:
/opt/arduino-1.8.16/portable/packages/esp8266/tools/xtensa-lx106-elf-gcc/1.20.0-26-gb404fb9-2/xtensa-lx106-elf/include
/opt/arduino-1.8.16/portable/packages/esp8266/tools/sdk/include
/opt/arduino-1.8.16/portable/packages/esp8266/tools/sdk/lwip/include
/opt/arduino-1.8.16/portable/packages/esp8266/hardware/esp8266/2.3.0/tools/sdk/include
/opt/arduino-1.8.16/portable/packages/esp8266/hardware/esp8266/2.3.0/cores/esp8266
/opt/arduino-1.8.16/portable/packages/esp8266/hardware/esp8266/2.3.0/variants/generic
/opt/arduino-1.8.16/portable/packages/esp8266/hardware/esp8266/2.3.0/libraries/ESP8266WiFi/src
/opt/arduino-1.8.16/portable/packages/esp8266/hardware/esp8266/2.3.0/libraries/Wire
/opt/arduino-1.8.16/portable/packages/esp8266/hardware/esp8266/2.3.0/libraries/SPI
/opt/arduino-1.8.16/portable/packages/esp8266/hardware/esp8266/2.3.0/libraries/I2S
/opt/arduino-1.8.16/portable/packages/esp8266/hardware/esp8266/2.3.0/libraries/EEPROM

Achte darauf, dass deine Sketches mit #include <Arduino.h> beginnen, damit dein Editor die Header Dateien des Arduino Frameworks einbindet und deren Schlüsselwörter erkennt. Die Arduino IDE fügt diese Zeile notfalls automatisch beim Compilieren ein, aber das kann dein externer Editor nicht wissen. Also füge sie manuell ein.

Bei anderen Entwicklungsumgebungen läuft es prinzipiell auf die gleiche Vorgehensweise hinaus. Falls es für deine IDE ein gut gepflegtes Arduino Plugin gibt, wäre das natürlich zu bevorzugen.

WLAN vorübergehend aus schalten

Ohne WLAN nimmt der Chip 10-15 mA Strom auf. Der analoge Eingang funktioniert in diesem Zustand viel besser. Um die WLAN Schnittstelle vorübergehend aus zu schalten, muss man den Wakeup-Timer einbeziehen:

  ESP.deepSleep(1, WAKE_RF_DISABLED); 
  delay(100);

Der Mikrocontroller legt sich sehr kurz schlafen und startet mit deaktiviertem WLAN neu durch.

So reaktiviert man die Schnittstelle wieder:

  ESP.deepSleep(1, WAKE_RFCAL); 
  delay(100);

Der Timer-Ausgang GPIO16 muss wie folgt mit dem RESET Eingang verbunden werden:

Wakeup Schaltung

Dadurch wird das Reset-Signal ein bisschen verzögert und verlängert. Schau in den Schaltplan deines Boardes! Diese Bauteile sind wahrscheinlich zumindest teilweise bereits vorhanden.

Fallstricke

Wenn das ESP Modul instabil läuft, kontrolliere zuerst die Stromversorgung mit einem Oszilloskop. Das ist nämlich der einfachste und dennoch ein sehr häufiger Knackpunkt. Oft hilft schon ein gewöhnlicher 100 µF Elko an VDD und GND.

Es ist wichtig, das die Firmware genügend Rechenzeit ab bekommt, sonst fällt das Modul aus. Alle eigenen Programmteile müssen spätestens nach 50 ms (empfohlen werden 20 ms) die Kontrolle an die Firmware abgeben. Längere Programmschleifen können dazu die Funktionen delay(ms) oder yield() aufrufen.

Wenn man den ADC zu oft hintereinander ausliest, dann fällt WLAN aus. Man muss zwischen den Zugriffen Pausen (z.B. 100 ms) einfügen.

Für eigene Programme stehen nur ungefähr 4 kByte Stack zur Verfügung. Wenn lokale Variablen in Prozeduren mehr Platz belegen, stürzt das Programm ab. Größere Datenmengen (z.B. Puffer für Daten) sollen daher vorzugsweise global deklariert werden, damit sie außerhalb des Stack liegen.

Bedenke das String Objekte und Zeichenketten normalerweise im RAM liegen. Hier ist beschrieben, wie man sie stattdessen im Flash Speicher anlegt, um RAM zu sparen.

Dynamische Speicherverwaltung auf dem Heap verlangt besondere Sorgfalt, damit der Heap nicht fragmentiert wird. Sobald der eigene Code die Schlüsselwörter delete oder free enthält, sollte man die Langzeit-Stabilität der Anwendung gründlich überprüfen. String Objekte machen davon intensiv Gebrauch. Mehr dazu hier.

Wenn man Daten sendet, wird aus jedem print() und write() Aufruf ein einzelnes Ethernet Paket. Viele kleine Pakete reduzieren die Performance, verglichen mit wenigen großen Paketen. Wenn man jedoch in einem Aufruf zu viele Daten sendet, teilt die Firmware sie automatisch auf mehrere Pakete auf und wartet notfalls sogar darauf, dass Platz im Sendepuffer frei wird.

Da der Empfangspuffer mehrere Pakete aufnehmen kann, muss man bei der Programmierung davon ausgehen, dass readBytes() einige Kilobytes am Stück zurück liefert, vor allem wenn die Daten schnell aufeinander folgend eintreffen. Passe daher beim Auslesen der Daten auf, weder deine Puffer noch den Stack zu überladen. Benutze die Methoden readString() und readStringUntil() am besten gar nicht, weil sie beliebig lange Strings ansammeln, die leicht einen Speicher-Überlauf auslösen. Besser geht es mit meiner append_until() Funktion aus dem obigen Beispiel-Sketch.

Verlasse dich nicht darauf, dass gesendete TCP Nachrichten den Empfänger in der gleichen Segmentierung erreichen, wie sie gesendet wurden. Die Netzwerk-Komponenten dürfen die Segmente des TCP Datenstromes zerlegen und neu zusammenfügen. In dieser Diskussion wurden die Effekte der Segmentierung schön erklärt.

Beim UDP Protokoll sendet und empfängt man Pakete mit der oben genannten maximalen Größe. Diese kann unter Umständen durch Netzwerk Komponenten (Router, Modems, etc.) noch weiter eingeschränkt sein.

Die Funktion attachInterrupt() kann mit allen GPIO Pins außer Nr. 16 verwendet werden. Interrupt-Handler Routinen müssen mit dem Makro ICACHE_RAM_ATTR (vor Arduino core 3.0.0) oder IRAM_ATTR (ab Version 3.0.0) gekennzeichnet werden, sonst stürzt der Mikrocontroller ab. Innerhalb von Interruptroutinen darf man keine WLAN-Funktion benutzen, den ADC nicht auslesen und auch nicht delay() oder yield() benutzen. Ich rate davon ab, Interrupts überhaupt zu verwenden, weil es dabei oft zu unerwarteten Störungen kommt.

Alle GPIO Pins außer Nr. 16 unterstützen die pinModes OUTPUT, INPUT und INPUT_PULLUP. Der GPIO16 hat keinen internen Pull-Up Widerstand, stattdessen aber einen internen Pull-Down Widerstand (INPUT_PULLDOWN_16). Die I²C Implementierung "TwoWire" funktioniert nicht auf GPIO16 weil sie dessen abweichende Bitmuster im Konfigurations-Register nicht berücksichtigt.

Es wird empfohlen, nach ESP.deepSleep() immer einen delay(100) einzufügen, damit das Einschlafen zuverlässig funktioniert.

Jedes mal, wenn die Parameter der WLAN Verbindung (verglichen mit dem vorherigen Zustand) verändert werden, findet ein Schreibzugriff auf den Flash Speicher statt. Diese Einstellungen werden nämlich automatisch abgespeichert und beim nächsten Booten wieder angewendet. Das betrifft die Funktionen:

Wenn die Einstellungen oft geändert werden, kann der Flash Speicher kaputt gehen. Um dies zu verhindern, kann WiFi.persistent(false) das automatische Speichern deaktivieren. Das WiFi Passwort muss entweder ein Leerstring ("") oder mindestens 8 Zeichen lang sein.

Kritik

Der ESP82xx Chip kann nur mit der Firmware des Herstellers genutzt werden, da viele wichtige Details der Hardware geheim gehalten werden. Wesentliche Teile der Firmware sind nur in Binärform ohne Quelltext veröffentlicht. Jede Anwendung hängt von dieser Firmware ab, auch Arduino, NodeMCU (Lua), und MicroPython.

Der Hersteller hat seine Software früh veröffentlicht, als sie noch instabil lief. Dennoch stieß sie auf großes Interesse, besonders in der Arduino Community. Die letzte stabile Version ist das SDK 1.5.4 vom 20. Mai 2016.

Beginnend mit Version 2.0 glich Espressif sein SDK an das besser strukturierte IDF des ESP32 an. Aber diese Sofware lief nie richtig stabil, und der Hersteller brach die Arbeit an dem Projekt ab. Die beiden letzten unvollendeten Varianten sind:

Doch die Arduino Community blieb am Ball. Der Arduino Core 3.1.x von 2023 basiert auf einer korrigierten Variante des NONOS SDK v3.0.5.

Das Datenblatt war anfangs eine Marketing Broschüre voller Angaben, die eher Wunschdenken als der Realität entsprachen. Über fast 10 Jahre verteilt wurden die Angaben dann schrittweise korrigiert und ergänzt. Achte daher darauf, mit einem aktuellen Datenblatt zu arbeiten.

Das Datenblatt nennt leider immer noch einige Features (I²C, PWM, IR Remote), die der Chip in Wirklichkeit nicht hat. Im Kleingedruckten dazu heißt es, dass man diese in Software implementieren "kann". Da die CPU in unregelmäßigen Abständen mit der WLAN Schnittstelle beschäftigt ist, ist es für Software jedoch unmöglich, Signale mit exakten Timings zu erzeugen. Dies äußert sich zum Beispiel in sichtbarem Flackern bei PWM gedimmten Lampen. Für die beliebten Neopixel LEDs (WS2812 und ähnliche) muss man die serielle Schnittstelle oder I²S mit DMA missbrauchen, um ausreichend genaue Signale zu erzeugen.

Ein mutmaßlicher Fehler im Design des Chips bewirkt, dass er sich beim Aufwachen aus dem Deep-Sleep Modus aufhängt. Deswegen muss der Timer Ausgang mit dem Reset Eingang verbunden werden, was einen kompletten Neustart der Firmware mit Verlust der Daten im RAM bewirkt.

Ich hatte den ESP8266 mit Arduino Core 2.3.0 zwei Jahre lang erfolgreich im Dauertest ohne einen einzigen Neustart. Fehlerhafte Programme (z.B. mit Stack Überlauf) können den Chip jedoch dermaßen blockieren, dass sogar der Watchdog versagt. Auch der CHIP_EN Eingang funktioniert dann nicht mehr. Ein externer Reset funktioniert jedoch immer.

Der ADC Eingang taugt nur als grobes Schätzeisen, weil er nicht linear arbeitet und sich vom Funk-Interface stören lässt. Es gab zwischenzeitlich mal ein ein Datenblatt, wo bis zu 20 % Abweichung genannt wurden.

Die RTC läuft im Deep Sleep Modus so ungenau, dass sie praktisch unbrauchbar ist. Daher ist mir die fehlende Unterstützung im Arduino Core egal.

Bei all der Kritik möchte ich jedoch hervorheben, dass ich ESP82xx Module gerne in Kombination mit dem Arduino Framework benutze. Sie sind billig und funktionieren zuverlässig.

Nachfolger

Als direkten Nachfolger empfiehlt Espressif den ESP8684. Das ist ein abgespeckter ESP32-C2 mit einem RISC-V Kern, WLAN, Bluetooth und integrierten Flash Speicher. Allerdings kann man die ganze ESP32-C2 Serie nicht mit Arduino programmieren.

Alle paar Monate kommen neue ESP32 Modelle auf den Markt, während andere entfallen. Die meisten ESP32 haben Bluetooth und mehr I/O Pins. Einige haben 2 CPU Kerne. Man programmiert sie normalerweise mit dem ESP-IDF in der Programmiersprache C. Einige Modelle sind auch mit dem Arduino-ESP32 Core in C++ programmierbar.

Bei allen ESP32 Modellen ist weiterhin ein zentraler Teil der Firmware nur in Binärform ohne Quelltext verfügbar. WLAN und Bluetooth kann man nicht gleichzeitig benutzen. Die analogen Eingänge sind ähnlich ungenau, wie beim ESP82xx.

Startseite