Links | Links
Link | Inhalt |
---|---|
Guru Meditation Errors | Übersicht und Erläuterungen zu den Nachrichten Guru Meditation Error: Core panic'ed |
https://github.com/espressif/esp-idf | Source Code des Espressif-IDF |
https://docs.espressif.com/.../api-reference/index.html | API-Referenz des IDF. Es gibt Unterschiede bei den Prozessor-Varianten (Auswahl links oben) |
ESP32 ESP32-S3 | Technisches Referenz Handbuch |
ESP32 ESP32-S3 | Datenblätter |
https://docs.espressif.com/.../hw-reference/index.html | Hardware-Referenz. Es gibt Unterschiede bei den Prozessor-Varianten (Auswahl links oben) |
Timer | Timer
Die Arduino-Bibliothek für den ESP32 (aktuelle Version 2.0.1) stellt leider keine Klasse zur Verfügung, die die Hardware-Timer kapselt. Es muss die Bibliothekskomponente esp32-hal-timer zur Timer-Konfiguration benutzt werden. Die Header-Dateien (hier esp32-hal-timer.h) werden über die Arduino.h automatisch eingebunden. Hierum muss man sich nicht kümmern.
Das folgende Code-Snippet stellt einen Timer so ein, dass alle 100 μs (entspr. 10 kHz) ein Timer-Interrupt ausgelöst wird:
hw_timer_t* timerHandle = NULL;
void IRAM_ATTR timerInterruptHandler() {
//...
}
//...
timerHandle = timerBegin(0, 80, true); // 1μs Clock
timerAlarmWrite(timerHandle, 100, true); // 100 μs Timer = 10 kHz
timerAttachInterrupt(timerHandle, timerInterruptHandler, false);
timerAlarmEnable(timerHandle);
Die HAL-Bibliothek (Hardware Abstraction Layer) arbeitet mit Handles. Das sind in diesem Fall Zeiger auf eine Struktur vom Typ hw_timer_t.
Um Timer-Interrupts zu erzeugen, muss man sich zunächst ein Handle vom System anfordern (über timerBegin(uint8_t num, uint16_t divider, bool countUp)), über das man dann den Timer konfigurieren kann. Parameter num ist die Timer-Nummer (hier 0), divider stellt den Prescaler (Vorteiler) ein, countUp legt fest, ob der zu Grunde liegende Counter inkrementiert oder dekrementiert wird. countUp kann beliebig angegeben werden.
Der Timer wird vom APB-Bus (Advanced Peripheral Bus) angesteuert, der mit 80 MHz läuft (Ausgabe des ESP32 beim nach einem Reset: ... PLL: 480 / 2 = 240 Mhz, APB: 80000000 Hz). Wenn der Prescaler mit 80 belegt wird, erhält der Counter einen Takt von 80.000.000/80 Hz = 1 MHz, entsprechend einer Taktdauer von 1μs. Über timerAlarmWrite(hw_timer_t *timer, uint64_t interruptAt, bool autoreload) wird festgelegt, bei welchen Counter-Stand der Interrupt ausgelöst wird (interruptAt). autoreload muss für sich wiederholende Interrupts mit true belegt werden. false ergibt einen einmaligen Interrupt. timerAttachInterrupt(hw_timer_t *timer, void (*fn)(void), bool edge) weist dem Timer eine ISR zu. Die Angabe bei edge ist gleichgültig. Es funktioniert zwar nur false. Gibt man jedoch true an wird edge intern auf false umgestellt. timerAlarmEnable(hw_timer_t *timer) startet den Counter indem es die Clock-Signale frei gibt.
Byte-Reihenfolge (Endianness) / Bit-Fields | Byte-Reihenfolge (Endianness) / Bit-Fields
Der ESP32 verwendet die Little-Endian-Byte-Reihenfolge.
Code-Beispiele:
union UINT32STRUCT {
struct {
uint32_t b0 : 8; // LSB
uint32_t b1 : 8;
uint32_t b2 : 8;
uint32_t b3 : 8; // MSB
};
uint32_t var;
} q;
...
// B3 B2 B1 B0 einer 32-Bit-Zahl
// q.var = 0x12 34 56 78;
q.var = 0x12345678;
Serial.printf("q: %x %x %x %x\n", q.b0, q.b1, q.b2, q.b3);
Ergibt:
q: 78 56 34 12
union UINT32STRUCT {
struct {
uint32_t b0 : 12; // LSB
uint32_t b1 : 4;
uint32_t b2 : 4;
uint32_t b3 : 12; // MSB
};
uint32_t var;
} q;
...
q.var = 0x12345678;
Serial.printf("q: %x %x %x %x\n", q.b0, q.b1, q.b2, q.b3);
Ergibt:
q: 678 5 4 123
Die niederwertigen Bit-Felder werden an den niedrigen Adressen abgelegt. Der Compiler sorgt dafür, dass byte-übergreifende Strukturen an den passenden Stellen abgelegt werden.
Eine Register-Struktur wie diese
lässt sich in dieser Struktur darstellen:union {
struct {
uint32_t mem_pd: 1;
uint32_t reserved1: 1;
uint32_t reserved2: 1;
uint32_t rx_size: 4;
uint32_t tx_size: 4;
uint32_t reserved11: 4;
uint32_t rx_flow_thrhd_h3: 3;
uint32_t rx_tout_thrhd_h3: 3;
uint32_t xon_threshold_h2: 2;
uint32_t xoff_threshold_h2: 2;
uint32_t rx_mem_full_thrhd: 3;
uint32_t tx_mem_empty_thrhd: 3;
uint32_t reserved31: 1;
};
uint32_t val;
} mem_conf;
Die niederwertigen Felder sind zuerst zu nennen.
Siehe auch ESP8266-Byte-Reihenfolge.
Über diese Struktur kann man auf die einzelnen Bits eines Bytes zugreifen:
union Byte { struct { uint8_t b0 : 1; uint8_t b1 : 1; uint8_t b2 : 1; uint8_t b3 : 1; uint8_t b4 : 1; uint8_t b5 : 1; uint8_t b6 : 1; uint8_t b7 : 1; }; uint8_t val; }; ... Byte b = { .val = 0x35 }; Serial.printf("%i %i %i %i %i %i %i %i", b.b7, b.b6, b.b5, b.b4, b.b3, b.b2, b.b1, b.b0);
Ergibt:
0 0 1 1 0 1 0 1
Permanentes Reboot abfangen | Permanentes Reboot abfangen
Beim Entwickeln von Programmen für den ESP32 bleibt es nicht aus, dass man Fehler macht. Probleme gibt es, wenn der Fehler zu einem Reset führt (z.B. Zugriff über einen nicht initialisiertem Pointer). Nach dem Reset stößt das Programm erneut auf die Fehlerstelle, was zu einem erneuten Reset führt. Dies in sehr schneller Folge unendlich lange weiter. Wenn vor dem Fehler Ausgaben gemacht werden, z.B. über die serielle Schnittstelle, kommt das Terminalprogramm, das die Ausgaben anzeigt, schnell ins schleudern.
Beim Start des Programms sollte man den Grund für den (Re-) Start auswerten. Der Power-On-Reset hat den Reset-Grund 1. Bei allen anderen Gründen, sollte man das Programm stoppen. Je nach Anwendung, muss die Liste der akzeptierten Reset-Gründe erweitert werden. Die Enumeration RESET_REASON für die Gründe findet man in <cpu>rom/rtc.h.
void setup() {
Serial.begin();
Serial.println("Programmstart");
// Reset-grund ermitteln und anzeigen
auto resetReason = UrsESP.getResetReason(0);
Serial.printf("CPU0 reset reason: %i (%s) %s\n", resetReason, UrsESP.getResetReasonName(resetReason).c_str(), UrsESP.getResetReasonDescription(resetReason).c_str());
// Wenn nicht Power-On-Reset dann stoppen.
if (resetReason != 1) // Ansonsten erfolgt dauernd ein neuer Reset
while (1);
Die oben benutzten Funktionen sind in der Bibliothek UrsESP hinterlegt.