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 |
Links | Links
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:
Die Gesamtlast kann 16x12 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 |
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
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:
|
|
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.
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:
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:):
Eine Einflussnahme auf die Auslöse-Zeiten ist nicht vorgesehen.
Das Objekt ESP (Klasse EspClass) bietet gewrappte Zugriffe auf diese drei Methoden:
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}.
- 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:
Damit ergeben sich folgende Stromversorgungsanschlüsse:
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:
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
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,
|
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. |
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
void ICACHE_RAM_ATTR isr() {...}
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:
".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
und in der Arduino-IDE
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.