Dokumentationen

Sammlung der (möglichst) aktuellen Dokumente zum ESP8266. Verlinkt sind lokale Kopien. Die Links ins WEB sind "veränderlich" und haben i.d.R. eine überschaubare Halbwertszeit. Immer, wenn ich von einem neuerem Dokument erfahre, werde ich es hierher verlinken. Eine gute Quelle für diese Dokumente ist die Download-Seite, die Suchfunktion von Espressif oder die Dokumenten-Mappe (Stand 2016-05-01)

Dokument Version Datum Bezeichnung (mit Link ins WEB)
Datasheet 6.0 Nov. 2018 0a-esp8266ex_datasheet_en.pdf
UART 0.2 2015-06-01 8E-ESP8266__Interface_UART__EN_v0.2.pdf
Technical Reference 1.3 2017 esp8266-technical_reference_en.pdf
NodeMCU V0.9
Schaltbild
0.9 2014-11-20 NODEMCU_ESP12.PDF
NodeMCU V1.0
Schaltbild
1.0 2014-11-20 NODEMCU_DEVKIT_V1.0.PDF
Kolban's Book on ESP32 & ESP8266 Okt. 2016 Okt. 2016 ESP8266_ESP32-oct2016.pdf
Thema Inhalt Quelle
ESP8266 Arduino Core Version 2.3.0 Dokumentation
ESP8266 Arduino Core’s documentation ESP8266 Arduino Core’s documentation  
ESP8266 core for Arduino Arduino-Unterstützung für ESP8266 (SDK)
Man schaue auf das Verzeichnis "doc"
ESP8266 Community Forum
Exception causes Auflistung der Ausnahmen und deren Ursachen ESP8266 Community Forum
ESP8266WiFi Library Referenz der ESP8266WiFi Bibliothek ESP8266 Community Forum
OTA Updates Erläuterungen zu Over The Air Updates ESP8266 Community Forum
esp8266-wiki Wiki zum ESP8266 ESP8266 Community Forum
getting started guide Viele nützliche Hinweise ESPRESSIF
Bulletin Board Developer Zone von Espressif ESPRESSIF
ESP8266 Reverse Engineering Wiki Einige Interna  
Espressif Documents Diverse Handbücher, Datenblätter u.a. Dokumentationen. ESPRESSIF
Arduino IDE 1.5 3rd party Hardware specification Konfiguration von platform.txt, boards.txt, programmers.txt Arduino GitHub
Comparison of ESP8266 NodeMCU development boards Vergleich der NodeMCU-Versionen my2cents
Espressif Systems GitHub-Seite mit vielen Dokumentationen und Quellen einzelner Komponenten der Firmware, insbesondere für den ESP32. Für ESP8266 besonders interessant: ESP8266_NONOS_SDK  
Memory Map Speicherbelegung, Vektor-, Register-Adressen uvm. esp8266-wiki

Technische Daten | Technische Daten

Gesammelte technische Daten (was beim Lesen auffällt).

Paramter Wert Quelle
Taktfrequenz 80 MHz Datasheet
CPU-Belastung durch WiFi-Stack ca. 20% Datasheet
RAM (nominal) 64 kB Wikipedia
RAM (verfügbar) ca. 50 kB Datasheet
Max. GPIO-Last:
  • Stromquelle
  • Stromsenke

Die Gesamtlast kann 16x12 mA
bzw. 16x20 mA betragen.

 
  • 12 mA
  • 20 mA
ESPRESSIF Bulletin Board

Umlaute (HEX-Code) | Umlaute (HEX-Code)

Der C++-Compiler für den ESP8266 legt Strings im UTF-8-Code ab. Das hat zu Konsequenz, dass Umlaute zwei Zeichen belegen und auf ANSI-Terminals komisch angezeigt werden. Hier hilft die explizite Angabe der ANSI-Codes im HEX-Format. Aus "Küche" wird dann "K\xfcche".

Umlaut Ersatz
Ä \xc4
ä \xe4
Ö \xd6
ö \xf6
Ü \xdc
ü \xfc
ß \xdf

UART RX Buffer | UART RX Buffer

Die Größe des internen Hardware-Empfangspuffers beträgt 128 Byte. Wird diese Größe überschritten, wird der RX Overflow Interrupt ausgelöst.

Es gibt die Möglichkeit, schon bei einem konfigurierbaren Füllungsgrad den RX Full Interrupt auszulösen (z.B. bei 100 Byte). So hat man genügend Zeit, die bereits empfangenen Daten zu verarbeiten, bevor ein Überlauf stattfindet.

Quelle: 8E-ESP8266 Interface UART, S.10 ff


UART Konfiguration | UART Konfiguration

Die ESP8266-Arduino-Umgebung stellt zwei Instanzen der Klasse HardwareSerial zur Verfügung: Serial (UART0(=0)) und Serial1 (UART1(=1)). Dies sind auch die beiden einzigen Schnittstellen, die die Methode uart_init (in uart.c, aufgerufen von SerialX.begin()) zulässt.

Die UART-Nr. wird beim Constructor übergeben und gespeichert. Dies ist die einzige Funktion des Constructors, eine Überprüfung der übergebenen Nummer erfolgt nicht.

SerialX.begin(unsigned long baud[, SerialConfig config[, SerialMode mode[, uint8_t tx_pin]]]) übernimmt die eigentliche Initialisierung. Die Parameter config, mode und tx_pin sind optional. Die Standardwerte sind config=SERIAL_8N1,  mode=SERIAL_FULL und tx_pin=1. SerialX.begin ruft im Wesentlichen uart_init mit diesen Parametern auf.

Die Angabe 2 bei tx_pin für UART0 (Serial) führt dazu, dass die TX-Funktion für Serial auf Pin GPIO2 (D4) gelegt wird. Bei allen anderen Angaben liegt TX für UART0 auf GPIO1 (D10). Letzteres ist der Standard. Die RX-Funktion für Serial liegt immer auf GPIO3 (D9). Der TX-Pin lässt sich entweder über eine entsprechende Angabe in der Methode begin einstellen oder über die Methode set_tx. set_tx hat nur für UART0 (Serial) Auswirkungen. Auch gilt: die Angabe 2 führt zu TX-PIN = GPIO2 (D4), alle anderen zu GPIO1 (D10).

Achtung: Es erfolgen während des Bootvorgangs, noch bevor die Arduino-Umgebung aktiv ist, Ausgaben über UART0 mit der Standardeinstellung TX=GPIO1 (D10).

Über Serial1 kann nur gesendet werden. Die TX-Funktion liegt auf GPIO2 (D4).

Achtung: Diese Einstellung kollidiert mit der zweiten Variante für Serial!

Eine weitere Möglichkeit, die Pins umzulegen, bietet die Methode swap(). swap hat nur bei UART0 (Serial) Auswirkungen und schaltet beim ersten Aufruf die TX-Funktion auf GPIO15 (D8) und RX auf GPIO13 (D7). Der zweite Aufruf schaltet wieder zurück auf TX = GPIO1 (D10) und RX = GPIO3 (D9). Will man beim zurücksetzen die Variante TX = GPIO2 (D4), muss swap mit der Parameter 2 aufgerufen werden (swap(2)).

Eine weitere Alternative die Pins zu tauschen ist die Methode pins(uint8_t tx, uint8_t rx). Für UART0 (Serial) sind die Kombinationen (1, 3), (2, 3) oder (15, 13) erlaubt.

Die Ausprägungen der Enumerationen SerialConfig und SerialMode können der Datei HardwareSerial.h entnommen werden.


Baudrate beim Boot-Vorgang | Baudrate beim Boot-Vorgang

By default, UART0 outputs some printed information when the device is powered on and booting up. The baud rate of the printed information is relevant to the frequency of the external crystal oscillator. If the frequency of the crystal oscillator is 40 MHz, then the baud rate for printing is 115200; if the frequency of the crystal oscillator is 26 MHz, then the baud rate for printing is 74880. If the printed information exerts any influence on the functionality of the device, it is suggested to block the printing during the power-on period by changing (U0TXD,U0RXD) to (MTDO,MTCK).

So sieht's aus:p>

 ets Jan  8 2013,rst cause:2, boot mode:(3,6)

load 0x4010f000, len 1264, room 16 
tail 0
chksum 0x0f
csum 0x0f
~ld

Quelle: Datasheet v4.7, Kap.: 4.6. Universal Asynchronous Receiver Transmitter (UART)

Stromverbrauch | Stromverbrauch

Der Stromverbrauch ist abhängig von dem aktuellen Verbindungszustand (laut Dokumentation)

Verbindungszustand Stromverbrauch Anmerkungen
Senden 170 mA  
Empfangen    60 - 75 mA Spitzen bis zu 300 mA für ca. 1 ms (s. Abb. unten)
Ein Kondensator von 1.000 bis 2.000 µF wird benötigt, um diese Spitzen abzufangen.
Modem Sleep    15 mA  

ESP8266 Stromspitzen

Persönliche Erfahrungen mit einem NodeMCU 1st generation / v0.9 / V1 und dem Arduino-Core in der Version 2.3.0:

Das Board startet im Modus "Auto Modem-Sleep". Der mittlere Stromverbrauch beträgt etwa 80-90 mA. Davon entfallen ca. 12-15 mA auf den USB2Serial-Wandler CH340G. Der Verbrauch ändert sich nicht, wenn man den Sleep-Modus abschaltet (WiFi.setSleepMode(WIFI_NONE_SLEEP);). Ebenfalls passiert nichts, wenn man den WiFi-Modus ändert. WiFi.mode(WIFI_OFF); und WiFi.mode(WIFI_STA); liefern das gleiche Ergebnis. Es macht ebenfalls keinen Unterschied, ob eine Verbindung zum Router besteht oder nicht.  "Auto Modem-Sleep" bringt offensichtlich keine Vorteile.

Der Modus "Auto Light-Sleep" bringt jedoch eine erhebliche Ersparnis! WiFi.setSleepMode(WIFI_LIGHT_SLEEP); schaltet diesen Modus an. Wenn genügend häufige oder genügend lange delay()-Aufrufe eingebaut werden (es reicht bereits ein delay(1); innerhalb von loop()), geht der mittlere Stromverbrauch auf etwa 13 mA zurück. Davon entfallen ca. 12 mA auf den CH340G. Für den ESP8266 verbleiben dann noch etwa 1 mA. Offensichtlich wird während des delay() der Stromverbrauch drastisch reduziert.

Diese hohe Einsparung basiert wohl auf dem Umstand, dass die restlichen Anweisungen in loop() recht überschaubar waren. Deshalb hat der ESP die meiste Zeit im delay() verbracht. Je nach Applikation kann das natürlich anders sein.

Im Test haben sich bisher keine Nachteile ergeben. Die serielle Schnittstelle funktionierte einwandfrei, Interrupts wurden erkannt und Daten konnten per WiFi empfangen und versandt werden.


Schaltplan für ESP01 | Schaltplan für ESP01

ESP01 Aufsicht   ESP01 Schaltplan

Größe der nativen Datentypen | Größe der nativen Datentypen

Typ Schlüsselwort Anzahl Bytes Wertebereich
character char 1 (8 Bit) -128 .. 127
unsigned character unsigned char 1 (8 Bit) 0 .. 255
short integer short 2 (16 Bit) -32.768 .. 32.767
unsigned short integer  unsigned short 2 (16 Bit) 0 .. 65.535
integer int 4 (32 Bit) -2.147.483.648 .. 2.147.483.647 (~2.1 · 109)
unsigned integer unsigned int 4 (32 Bit) 0 .. 4.294.967.295 (~4.2 · 109)
long integer long 4 (32 Bit) -2.147.483.648 .. 2.147.483.647
unsigned long integer unsigned long 4 (32 Bit) 0 .. 4.294.967.295
long long integer long long 8 (64 Bit) –9.223.372.036.854.755.808 .. +9.223.372.036.854.755.807 (~9.2 · 1018)
unsigned long long integer unsigned long long 8 (64 Bit) 0 … 18.446.744.073.709.551.615 (~1.8 · 1019)
single-precision floating-point (7 Stellen) float 4 (32 Bit) 1.17E-38 .. 3.4E38
double-precision floating-point (19 Stellen) double 8 (64 Bit) 2.2E-308 .. 1.8E308
boolescher Wert bool 1 (8 Bit) true, false

Quelle:

Serial.print("char.....:"); Serial.println(sizeof(char));
Serial.print("int......:"); Serial.println(sizeof(int));
Serial.print("short....:"); Serial.println(sizeof(short));
Serial.print("long.....:"); Serial.println(sizeof(long));
Serial.print("long long:"); Serial.println(sizeof(long long));
Serial.print("float....:"); Serial.println(sizeof(float));
Serial.print("double...:"); Serial.println(sizeof(double));
Serial.print("bool.....:"); Serial.println(sizeof(bool));
char.....:1
int......:4
short....:2
long.....:4
long long:8
float....:4
double...:8
bool.....:1

Konvertierungen:

float -> int schneidet ab.

17.4 -> 17
17.5 -> 17
17.6 -> 17

Ganzzahl-Zuweisung an Datentyp mi weniger Bytes int -> char. Es werden die niederwertigen Bytes übernommen:

0x12345 -> 45

 


Analog-Digital-Konverter (ADC) | Analog-Digital-Konverter (ADC)

Der ADC kann über analogRead(A0), analogRead(17) oder analogRead(0) abgefragt werden. Diese Abfragen sind identisch und liefern das Ergebnis von system_adc_read(). Alle anderen Pins liefern digitalRead() * 1024, also 0 oder 1024 je nach logischem Pegel.

Die Konvertierung dauert knapp 0,4 ms (einschl. Schleifensteuerung und gelegentlichem delay(0);).

1000 Konvertierungen benötigen 95 ms, wenn sie direkt hintereinander ausgeführt werden. D.h. ca. 0,1 ms pro Messung bzw. 10.000 Samples pro Sekunde. Der ESP8266 war bei der Messung mit dem WLAN verbunden.

uint32_t dummy = 0;
uint32_t start = millis();
for (uint16_t i = 0; i < 1000; i++)
{ dummy += analogRead(A0);
}
uint32_t end = millis();
Serial.print("1000 x analogRead: ");
Serial.println(end-start);
Serial.println(dummy); // nicht wegoptimieren!

"pinMode(A0, INPUT);" ist übrigens überflüssig. pinmode wirkt nur auf Pins mit Nummern ≤ 16. Der ADC-Pin befindet sich hardwarebedingt immer im Input-Modus.


Zugriff auf Systeminterna | Zugriff auf Systeminterna

Das spärlich dokumentierte Objekt ESP (Klasse EspClass) bietet ein Reihe von Methoden, die Zugriffe auf das ESP-System zulassen. Diese sind u.a.

  • Watchdog (wdt)
  • Speicherinformationen
  • Zugriff auf das Flash
  • ...

Zugriff erhält man durch

#include <Esp.h>

Einige Infos findet man mittlerweile in der Bibliotheksdokumentation.

Code:

Serial.print("ChipID: "); Serial.println(ESP.getChipId());
Serial.print("CPU frequency: "); Serial.print(ESP.getCpuFreqMHz()); Serial.println(" MHz");
Serial.print("Last reset reason: "); Serial.println(ESP.getResetReason().c_str());
Serial.print("Memory size  (SDK): "); Serial.print(ESP.getFlashChipSize()); Serial.println(" Bytes");
Serial.print("Memory size (CHIP): "); Serial.print(ESP.getFlashChipRealSize()); Serial.println(" Bytes");
Serial.print("         Free heap: "); Serial.print(ESP.getFreeHeap()); Serial.println(" Bytes");
Serial.print("       Sketch size: "); Serial.print(ESP.getSketchSize()); Serial.println(" Bytes");
Serial.print("  Free sketch size: "); Serial.print(ESP.getFreeSketchSpace()); Serial.println(" Bytes");

FSInfo fs_info;
if (SPIFFS.info(fs_info)) {
  Serial.print("File system total size: "); Serial.print(fs_info.totalBytes); Serial.println(" Bytes");
  Serial.print("             used size: "); Serial.print(fs_info.usedBytes); Serial.println(" Bytes");
  Serial.print("            block size: "); Serial.print(fs_info.blockSize); Serial.println(" Bytes");
  Serial.print("             page size: "); Serial.print(fs_info.pageSize); Serial.println(" Bytes");
  Serial.print("        max open files: "); Serial.print(fs_info.maxOpenFiles); Serial.println(" Files");
  Serial.print("       max path length: "); Serial.print(fs_info.maxPathLength); Serial.println(" Bytes");
}

Output für ESP12:

ChipID: 1767374
CPU frequency: 80 MHz
Last reset reason: Software/System restart
Memory size  (SDK): 4194304 Bytes
Memory size (CHIP): 4194304 Bytes
         Free heap: 37176 Bytes
       Sketch size: 295792 Bytes
  Free sketch size: 2846720 Bytes
File system total size: 957314 Bytes
             used size: 19829 Bytes
            block size: 8192 Bytes
             page size: 256 Bytes
        max open files: 5 Files
       max path length: 32 Bytes

Hinweis zur Flash-Größe:

  • getFlashChipRealSize liest die Größe des Speichers aus dem Flash-Chip aus
  • getFlashChipSize ist die in der IDE eingestellte Größe. Diese nutzt der Compiler aus.

Siehe auch: Build-Nummer: Immer Eins drauf!


WatchDog | WatchDog

 Die folgenden Informationen stammen aus GitHub: esp8266/Arduino/issues.

Aktuell besitzt der ESP8266 zwei WDT Zeitgeber: Der Software WDT ist an einen high priority timer interrupt angebunden und feuert nach ca. 1 Sekunde. Der Hardware WDT löst nach ca. 5-8 Sekunden aus, falls der Software WDT aus irgendwelchen Gründen nicht feuert. Wenn man die "wdt reset" Zeile auf der seriellen Konsole sieht, bedeutet dies, dass Hardware-WDT ausgelöst hat. Der WDT ist standardmäßig aktiviert.

Der Zugriff auf den Hardware WDT ist von Espressif aus dem SDK entfernt worden.

Der Zugriff auf den Software WDT erfolgt über drei Methoden (aus dem SDK API Guide:):

  • system_soft_wdt_feed(): Setzt den Watchdog zurück.
  • system_soft_wdt_stop(): Stoppt den Watchdog.
    "The software watchdog must not be stopped for too long (over 6 seconds), otherwise it will trigger hardware watchdog reset."
  • system_soft_wdt_restart(): Startet den Watchdog erneut.

Eine Einflussnahme auf die Auslöse-Zeiten ist nicht vorgesehen.

Das Objekt ESP (Klasse EspClass) bietet gewrappte Zugriffe auf diese drei Methoden:

  • void wdtEnable(uint32_t timeout_ms = 0); // note: setting the timeout value is not implemented at the moment
    void wdtEnable(WDTO_t timeout_ms = WDTO_0MS);
  • void wdtDisable();
  • void wdtFeed();

Im o.g. GitHub-Link gibt es noch folgende Anmerkung:

If you see wdt reset in serial but the board doesn't reboot, it means some of the bootstrapping pins (GPIO0, GPIO2) are not in the correct state. Make sure GPIO0 and GPIO2 are pulled high.


GPIO0, GPIO2, GPIO15 / Bootmode | GPIO0, GPIO2, GPIO15 / Bootmode

Der Espressif-Code kann in verschiedenen Modi booten. Der Boot-Modus wird beim Einschalten anhand der Pins GPIO0, GPIO2, GPIO15 festgelegt (ESP8266-Wiki: Boot-Process).

GPIO15 GPIO0 GPIO2 Mode Description
L L H UART Download code from UART
Üblicher Programmier-Modus via UART
L H H Flash Boot from SPI Flash
Normaler Programmstart
H x x SDIO Boot from SD-card

In der Meldung beim Booten 'boot mode:(x,y)' bedeuten die drei niederwertigsten Bits {MTDO (=GPIO15), GPIO0, GPIO2}.

Aus ESPRESSIF Bulletin Board:

- GPIO15, GPIO0, GPIO2 ergeben boot_sel (2:0), die beim Reset zur Bestimmung des Bootmodus ausgewertet werden.

-Das Programmieren des Flash-Speichers unter Verwendung von uart0 erfordert den Boot-Modus "boot from uart0" mit boot_sel = b001, d.h. es muss sichergestellt sein, dass beim Reset GPIO15 = 0, GPIO0 = 0 und GPIO2 = 1 sind.

Da GPIO15, GPIO0 und GPIO2 alle interne, schwache Pullups haben, kann, um GPIO2 = 1 zu erhalten GPIO2 unbeschaltet bleiben. Allerdings müssen für GPIO15 und GPIO0 externe Pulldowns geschaltet werden, um für diese beiden Signale auf "0" zu legen.

In vielen Modulen wird GPIO15 auf Masse gezogen und GPIO2 bleibt unbeschaltet. GPIO0 steuert während des Neustarts zwischen Flash-Download und normalem Hochfahren.

Diese beiden Seiten von Forward Computing and Control zeigen Beispiele, wie man diese Pins für verschiedene eigene Anwendungen nutzen kann.


Assembler-Listing generieren | Assembler-Listing generieren

Die bei Arduino FAQ beschriebene Methode zur Erzeugung eines Assembler-Listings ist für den ESP8266 nicht geeignet. Da stets die sehr große Basis-Bibliothek (SDK) eingebunden wird, dauert die Erzeugung des Listings außerordentlich lang. Es wirklich keinen Spaß bei jedem Build so lange zu warten.

 Damit man dennoch einfach an ein Assembler-Listing kommt, wenn es benötigt wird, ist folgendes Vorgehen sinnvoll:

Bei jedem Build-Vorgang erzeugt eine Batch-Anweisung eine weitere Batch-Datei mit den Kommandos zur Erstellung eines Listings im Ausgabe-Verzeichnis. Diese zweite Datei braucht man dann nur noch zu starten, um das Assembler-Listing zu erhalten. Der Trick ist, dass sämtlich Dateien mit den richtigen Pfaden versehen werden.

Die Batch-Datei ist recht einfach aufgebaut (Gen-mklss-cmd.cmd):

echo %1 -h -S -h --demangle %2 ^> %3 > %4
   "^>" maskiert das ">" Symbol. 

%1: Vollständiger Pfad zu XXX-objdump ("{compiler.path}XXX-objdump.exe").
%2: Vollständiger Pfad zur .elf-Datei ("{build.path}/{build.project_name}.elf").
%3: Vollständiger Pfad zur Ausgabe-Datei ("{build.path}/{build.project_name}.lss").
%4: Vollständiger Pfad der zu erzeugenden Batch-Datei ("")

Der Aufruf von Gen-mklss-cmd.cmd wird dann in der Datei "platform.txt" über einen "recipe.hooks"-Eintrag gesteuert und mit den notwendigen Parametern versehen (eine Zeile!):

recipe.hooks.objcopy.postobjcopy.0.pattern = "gen-mklss-cmd.cmd" 
        "{compiler.path}XXX-objdump" 
        "{build.path}/{build.project_name}.elf" 
        "{build.path}/{build.project_name}.lss" 
        "{build.path}/mk-lss.cmd"
Nicht vergessen!
"xxx" durch die Compiler-Version ersetzen.
Z.B. "avr" oder "xtensa-lx106-elf"

Wenn "gen-mklss-cmd.cmd" nicht über die PATH-Angabe gefunden werden kann, muss der vollständige Pfad angegeben werden.

Diese Konfiguration erzeugt im Ausgabeverzeichnis ein Batch-Datei mit dem Namen "mk-lss.cmd", die ein Assembler-Listing generieren kann.


NodeMCU Power Supply | NodeMCU Power Supply

Als ich nach Möglichkeiten gesucht habe, wie man den NodeMCU mit einer externen Spannungsquelle versorgen kann, bin ich auf diese Anleitung gestoßen: Powering the ESP-12E NodeMCU Development Board. Hier die wichtigsten Fakten noch einmal zusammengefasst.

Vereinfachtes Schaltbild:

NodeMCU: vereinfachstes Schaltbild zur Stromversorgung

Damit ergeben sich folgende Stromversorgungsanschlüsse:

NodeMCU: Stromversorgung

Bezüglich der herausgeführten Anschlüsse gibt es eine Reihe von Board-Varianten. Bevor man Vin mit mehr als 5V beschickt macht es Sinn einmal genau hinzuschauen und im Zweifelsfall nachmessen.

Mein Board von Amica mit einem CP2102 als USB2UART-Konverter und einem AMS1117 als Spannungsregulator hat auf der Rückseite den expliziten Hinweis für Vin: "5V empfohlen, max. 10V". Ein anderes Board von LoLin hat einen CH340G als Konverter und ebenfalls einen AMS1117 als Regulator. Der CH340G wird –anders als in der 0.9-Version– nicht durch VDD5V (= Vin) versorgt, sondern von VDD3V3, ist also auch gegen höhere Spannungen durch den Regulator geschützt. Der AMS1117 kann lt. Datenblatt Eingangsspannungen von bis zu 15V vertragen.


NodeMCU/Wemos D1 mini Pins | NodeMCU/Wemos D1 mini Pins

Die Funktion der Pins in der Arduino-Umgebung:

Pin GPIO Funktion Anmerkung
D0 GPIO16 USER Interne LED
Ist zum Aufwachen aus dem Deep-Sleep-Modus mit RESET zu verbinden. (ESP.deepSleep(10000000, WAKE_RF_DEFAULT);)
D1 GPIO5 SCL I²C
D2 GPIO4 SDA I²C
D3 GPIO0 FLASH Steuert Boot-Modus (s. GPIO0, GPIO2, GPIO15 / Bootmode)
Mit Pullup-Widerstand versehen
D4 GIO02 BootSel
TXD1

Steuert Boot-Modus (s. GPIO0, GPIO2, GPIO15 / Bootmode)
Alternativer TX-Pin für Serial
TX-Pin für Serial1
Mit Pullup-Widerstand versehen
D5 GPIO14 SCLK SPI
D6 GPIO12 MISO SPI
D7 GPIO13 RXD2
MOSI
RX-Pin bei geswappter Serial
SPI
D8 GPIO15 BootSel
TXD2
CS
Steuert Boot-Modus (s. GPIO0, GPIO2, GPIO15 / Bootmode)
TX-Pin bei geswappter Serial
SPI (Chip Select)
Mit Pulldown-Widerstand versehen
D9 GPIO3 RXD0 RX-Pin für Serial
D10 GPIO01 TXD0 TX-Pin für Serial

Eine Detaillierte Aufstellung für den WEMOS D1 MINI findet man bei EscapeQuotes.


ESP8266 und AVR beim Compilieren unterscheiden | ESP8266 und AVR beim Compilieren unterscheiden

Geht ganz einfach:

1: #if defined(__AVR__)
2: // AVR specific code here
3: #elif defined(ESP8266)
4: // ESP8266 specific code here
5: #endif

SPIFFS | SPIFFS

Die Anzahl der maximal geöffneten Dateien kann man mit folgender (Makro-) Definition anpassen.

SPIFFS_MAX_OPEN_FILES 5

Die Definition muss allerdings beim Übersetzen der Bibliothek vorliegen. Mit Visual Micro macht man das am besten über die Projekt-Eigenschaften:

Max Open Files

Daran denken: Die Bibliothek muss neu übersetzt werden. Bei VM: "Projekt neu erstellen".


FLASH, DRAM, IRAM, .data, .rodata, .bss, .text, .irom0.text | FLASH, DRAM, IRAM, .data, .rodata, .bss, .text, .irom0.text

Immer wieder gibt es Verwirrung mit den physikalischen und logischen Speicheraufbau des ESP8266.

Quellen: MicroPython on the ESP8266, Wikipedia - ESP8266

Physikalischer Speicher

Speicher Verwendung
Bootloader internes ROM, Größe 64KB, enthält Bootloader zum beschreiben des externen Flash.
FLASH externer Flash-Speicher, Größe abhängig von der Board-Variante zwischen 512 KB und 4 MB.
IRAM

Instruction RAM, statisches RAM, Größe 64 KB,

  • 32 KB enthält permanenten Code der schnell ausgeführt werden muss oder in Konkurrenz zu den sonstigen Leseroutinen des Flash steht (z.B. Interrupt Service Routinen (ISR)), dieser Bereich wird beim Booten aus dem Flash gefüllt..
  • 32 KB, aufgeteilt in zwei 18 KB Blöcke, dienen als Instruction Cache der zur Laufzeit mit dem jeweils aktuellen Code  aus dem Flash geladen wird.
DRAM Data RAM, statisches RAM, Größe 96 KB, enthält die Programmdaten. 16 KB werden bereits vom Sytems RAM Block verbraucht, so dass für die Applikation noch etwa 80 KB zur Verfügung stehen. In der Arduino-Umgebung verbraucht das SDK bereits etwa 32 KB. Für Anwendungsdaten (statische Daten, Stack und Heap) verbleiben somit etwa 48 KB.

Logischer Speicher

Segment Verwendung
.text Cached Code. Code wird wird beim Booten in das IRAM übertragen und dort ausgeführt. Der Code verbleibt permanent im IRAM. Es ist Code der schnell ausgeführt werden muss oder in Konkurrenz zu den sonstigen Leseroutinen des Flash steht (z.B. Interrupt Service Routinen (ISR))
Die maximale Größe beträgt 32 KB. 90-95% werden je nach eingebundenen Bibliotheksumfang vom SDK verbraucht.
.irom0.text Uncached Code. Dieser Code liegt im Flash. Die aktuell benötigten Teile werden zur Laufzeit in einen der beiden Instruction Cache Blöcke (je 16 KB) kopiert und dort ausgeführt. Der Kopiervorgang ist undokumentiert! Ein welchem Umfang und zu welchen Zeitpunkten Kopiervorgänge stattfinden ist unklar.
.rodata ReadOnly Data. Konstante Daten, z.B. Zeichenketten: Serial.print("Hallo"); ⟩ "Hallo" ⇒ .rodata. Beim Booten wird dieser Bereich aus dem Flah ins DRAM kopiert.
.data Initialized Data. Initialisierte Variablen, z.B. int i = 25;. Beim Booten wird dieser Bereich aus dem Flah ins DRAM kopiert.
.bss Uninitialized Data. Nicht initialisierte Variablen, z.B. int i;. Beim Booten wird dieser Bereich mit 0 (Null) gefüllt.

 


IRAM & Interrupt-Service-Routinen (ISR) | IRAM & Interrupt-Service-Routinen (ISR)

Auslöser waren zufällige Abstürze in einer ISR. Nachdem ich die ISR mit dem Attribut ICACHE_RAM_ATTR versehen habe, hat es funktioniert.

Verwendungsbeispiel

  • für Globale Methoden:
    void ICACHE_RAM_ATTR isr() {...}
  • für Klassen:
    class foo {
      void bar();
    };
    
    void ICACHE_RAM_ATTR foo::bar() {...}

 

Die nachfolgenden Hinweise gehen davon aus, dass die Standard-Platzierung für Funktionen im IRAM liegt. Bei der Kompilierung mit der Arduino-IDE (Version 1.8.5) oder Visual Micro (Version 1.1801.27) ist dies nicht der Fall. Hier liegen der Code der Methoden standardmäßig im Flash. ... und deshalb crashen die ISRs!

Im Netz und dann auch im Espressif-Forum fand ich den Hinweis, dass ISRs nicht im Flash gehalten werden sollten. Es kann Kollisionen mit den Flash-Leseroutinen geben. Statt dessen sollen die Routinen im IRAM gehalten werden. Das IRAM wird beim Booten des ESP aus dem Flash gefüllt. Von dort kann der Code ohne Flash-Zugriffe ausgeführt werden.

Zum IRAM habe ich folgendes ebenfalls im Espressif-Forum gefunden:

Der ESP8266 hat IRAM in der Größe von 32K (s. espressif_faq_en.pdf). Das ist RAM für Code. Das IRAM unterscheidet sich von den ~80k des DRAM (DRAM bedeutet Data RAM, nicht dynamisches RAM). Bei RAM-Arten können nicht gegeneinander getauscht werden.

Programme werden im Flash-Speicher gespeichert und aufgrund der schnellen Schnittstelle (SPI-Quad) können Befehle direkt "an Ort und Stelle" abgerufen und ausgeführt werden, d.h. ohne sie vorher ins RAM zu kopieren. In einigen Fällen kann der Code nicht direkt aus dem SPI-Flash-Speicher ausführen, hauptsächlich weil er maximal schnell sein muss oder weil er den Flash-Speicher selbst behandelt (z. B. Schreiben). In diesem Fall kann Code (max. 32 KB insgesamt), beim Booten vom Flash  zum IRAM kopiert werden. Dieser Code wird im IRAM und nicht von FLASH ausgeführt.

Das Makro ICACHE_FLASH_ATTR sorgt dafür, dass die Funktion vom Linker in den .irom0.text-Abschnitt ausgegeben wird. Der Standard ist die Ausgabe in das .text-Segment, das die Funktion in das IRAM-Segment einfügt.

Ein großer Teil des SDK-Codes, gibt auch den Code an das IRAM-Segment aus. Es ist also nicht viel übrig, von den 32k für eigene Anwendungen, etwa 2k, vielleicht 3k. Man muss es also sehr sparsam benutzen. Auch viele Funktionen, wenn nicht alle von libc, werden in das IRAM-Segment eingefügt. Es ist daher sehr einfach, den IRAM-Speicherplatz zu überfüllen.

Also, wenn nicht unmöglich, fügen Sie das Makro ICACHE_FLASH_ATTR allen Funktionen hinzu.

Wie oben bemerkt: Beim Arduino-Plugin ist dies genau umgekehrt!

Das IRAM bereits standardmäßig gut gefüllt. Leider wird beim Erstellen eines EPS8266-Projekts nur der Flash- und der RAM-Verbrauch angezeigt. Das IRAM bleibt außen vor. Mit der im Folgenden beschriebenen Maßnahme erhält man eine Anzeige des IRAM-Verbrauchs.

1. In die Datei platform.txt wird folgende Zeile eingefügt:

recipe.hooks.objcopy.postobjcopy.1.pattern="report-iram.cmd" "{sketch_path}" "{compiler.path}{compiler.size.cmd}" -A "{build.path}/{build.project_name}.elf"

Diese Zeile bewirkt, dass zum Schluss des Build-Prozesses das Batch-Programm "report-iram.cmd" aufgerufen wird. Die Angabe {sketch_path} dient zur Unterscheidung zwischen Arduino und Visual Micro. Die Arduino-IDE ersetzt diesen Parameter, VM macht das nicht. {compiler.path}{compiler.size.cmd} zeigt auf das Kommando, dass die Größe der einzelnen Sections auflistet. Eine typische Ausgabe dieses Programm sieht so aus:

Section-Größen

".text" ist die Section, die in das IRAM geladen wird.

2. Eine Batch-Datei mit dem Namen "report-iram.cmd" anlegen. Diese Datei platziert man am besten an einen Ort an dem sie über die PATH-Umgebungsvariable erreichbar ist. Ansonsten muss man in "platform.txt" den kompletten Pfad hinterlegen. "report-iram.cmd" hat folgenden Inhalt:

@echo off
echo .
echo ---------------------------------------------------------
IF "%~1" == "{sketch_path}" GOTO Arduino

REM -- Visual Micro -------------------------------
%2 %3 %4 |find ".text"|find /V "irom0">%temp%\IRAMSIZE
goto ComputeRAM

REM -- Arduino-IDE --------------------------------
:Arduino
ECHO ***** Executing Arduino
"%2" %3 "%4" |find ".text"|find /V "irom0">%temp%\IRAMSIZE

:ComputeRAM
set /p str=<%temp%\IRAMSIZE
set str=%str:~5,-11%
set str=%str: =%
set /A pp=(%str%*10000/32768)
set /A pp=((%str%*10000/32768)+5)/10
set /A pi=%pp%/10
set /A pd=%pp%-%pi%*10
echo.IRAM-Size %str% of 32768 (%pi%.%pd%%%)
del %temp%\IRAMSIZE

Das Ganze gibt dann folgenden Output im Visual Studio

VS Output

und in der Arduino-IDE

VS Output


WiFi.disconnect() & AutoReconnect| WiFi.disconnect() & AutoReconnect

WiFi.disconnect() trennt nicht nur die aktuelle Verbindung zum Access-Point sondern stoppt auch das AutoReconnect!


Byte-Reihenfolge (Endianness), union-Ausrichtung | Byte-Reihenfolge (Endianness), union-Ausrichtung

Insbesondere bei Ansteuerung von Sensoren und Aktoren in einem eingebetteten System (embedded system) ist notwendig zu wissen, in welcher Reihenfolge der Prozessor die Daten ablegt (Byte-Reihenfolge, Endianness) und wie einen union ausgerichtet ist. Das folgenden kleine Programm schafft ein wenig Klarheit:

union {
  uint8_t  Test8;
  uint16_t Test16;
  uint32_t Test32;
  uint64_t Test64;
  struct {
    uint32_t w1;
    uint32_t w2;
  } words;
  struct {
    uint8_t b;
    uint16_t w;
    uint32_t q;
  } unpackedStruct;
   struct __attribute__((packed)) {
    uint8_t b;
    uint16_t w;
    uint32_t q;
  } packedStruct;

} test;

test.Test64 = 0xFEDCBA9876543210;
uint8_t *p = (uint8_t *)&test;

Serial.printf("Test64:..0x%llX\n", test.Test64);
Serial.print("Test32:..0x"); Serial.println(test.Test32, HEX);
Serial.print("Test16:..0x"); Serial.println(test.Test16, HEX);
Serial.print("Test8:.. 0x"); Serial.println(test.Test8, HEX);
Serial.print("w1:......0x"); Serial.println(test.words.w1, HEX);
Serial.print("w2:......0x"); Serial.println(test.words.w2, HEX); 
Serial.print("ub:......0x"); Serial.println(test.unpackedStruct.b, HEX);
Serial.print("uw:......0x"); Serial.println(test.unpackedStruct.w, HEX);
Serial.print("uq:......0x"); Serial.println(test.unpackedStruct.q, HEX);
Serial.print("pb:......0x"); Serial.println(test.packedStruct.b, HEX);
Serial.print("pw:......0x"); Serial.println(test.packedStruct.w, HEX);
Serial.print("pq:......0x"); Serial.println(test.packedStruct.q, HEX);

for (int i = 0; i < 8; i++) {
  Serial.printf("test[%i]:.0x", i); Serial.println(*(p++), HEX);
}

Die Ausgabe ist:

Test64:..0xFEDCBA9876543210
Test32:..0x76543210
Test16:..0x3210
Test8:.. 0x10

w1:......0x76543210
w2:......0xFEDCBA98

ub:......0x10
uw:......0x7654
uq:......0xFEDCBA98

pb:......0x10
pw:......0x5432
pq:......0xDCBA9876

test[0]:.0x10
test[1]:.0x32
test[2]:.0x54
test[3]:.0x76
test[4]:.0x98
test[5]:.0xBA
test[6]:.0xDC
test[7]:.0xFE

Fazit: Der ESP8266 legt die Daten im little-endian-Format abgelegt, d.h. das niederwertigste Byte wird in der niedrigsten Adresse abgelegt. Alle Elemente der union beginnen an der gleichen Adresse und haben damit die jeweils niederwertigsten Bytes gemeinsam.

Bei einer struct ist es dann so, dass das als erstes definierte Element an der niedrigsten Adresse liegt. Ohne weitere Maßnahmen liegen die Elemente einer Struktur nicht in aufeinander folgenden Bytes. Sie werden zugriffsoptimiert ausgerichtet. Will man die Elemente lückenlos ausrichten ist die Deklaration der Struktur mit dem Kennzeichen __attribute__((packed)) zu versehen. Genauere Informationen und weitere Möglichkeiten findet man in GCC-Dokumentation Specifying Attributes of Types.

Siehe auch ESP32 Byte-Reihefolge.