Z notýsku vývojáře – Jak zachytit občasný pád aplikace v ESP8266

Dříve, nebo později se s tím setká každý, kdo tvoří aplikace na mikrokontroléry – skvělá, téměř hotová aplikace občas spadne. Ne tak často, aby se pád dal zachytit debuggerem (pokud se dá k hardware vůbec připojit), ale příliš často na to, aby bylo možné pády ignorovat. Stává se to i při programování ESP8266 a to častěji, než by se mohlo zdát (ti, kdo používají intenzívně komunikující sériový port pomocí knihovny SoftwareSerial o tom zcela nepochybně ví své). Ladění těchto situací bývá velmi obtížné a zdlouhavé, ale naštěstí pro nás existuje v případě ESP8266 cesta, jak si práci usnadnit. SDK, poskytované přímo od Espressif obsahuje možnost, jak při pádu aplikace zavolat uživatelskou funkci, která se jmenuje custom_crash_callback(…). Funkce je volaná z postmortem stavu přímo z jádra (weak alias je definovaný v core_esp8266_postmortem.c ). Její použití ale má nějaká úskalí – nesmí se použít dynamická alokace, blokovací funkce (serial, network, delay, …) a vykonání funkce nesmí trvat příliš dlouho, protože HW watchdog stále běží. Nápad tedy spočívá v tom, že se do EEPROM uloží informace o pádu, které se dají použít pro následnou analýzu. Tento nápad mělo přede mnou už více vývojářů, takže vznikla knihovna EspSaveCrash, která ale bohužel ignoruje zapovězení dynamické alokace, takže pokud vznikl pád aplikace z důvodu problému na heapu, EspSaveCrash nebude fungovat správně. Napsal jsem tedy vlastní variantu, která vychází z uvedené knihovny, ale netrpí tímto neduhem a kterou používám již řadu měsíců.

Nejdříve je třeba říct, že se nejedná o knihovnu v pravém slova smyslu – jedná se o samostatný .ino soubor, který je umístěný ve stejném adresáři, jako hlavní skript. Využívám zde vlastnosti, kdy ArduinoBuilder/PIO při každé kompilaci nejdříve sestaví pro překlad dočasný soubor, který sestává z vhodně poskládaných všech .ino, které jsou nalezeny v adresáři projektu.

Modul poskytuje tyto funkce:

void crashClear(void) – vymaže všechny informace o záznamech předchozích pádů.

void crashGet(String &out) – vrátí HTML formátovaný seznam zaznamenaných pádů včetně všech zaznamenaných informací.

int crashGetCount(void) – vrátí počet zaznamenaných pádů.

int crashSanityCheck(void) – ověří stav oblasti pro zaznamenávání pádů.

Ve svých aplikacích to používám tak, že na crashClear namířím webovou stránku cc (abych z webového rozhraní mohl vymazat staré záznamy/poprvé nainicializovat oblast), no a crashGet mi doplní text do webové stránky cg, takže vidím všechny zaznamenané pády přímo v prohlížeči.

Konfigurace je velmi jednoduchá – předpokládám definici indexu v EEPROM, do kterého se budou záznamy ukládat (SAVE_CRASH_EEPROM_OFFSET) a velikost EEPROM vyhrazenou pro záznamy pádů (SAVE_CRASH_SPACE_SIZE). EEPROM musí být spuštěna v uživatelské aplikaci (EEPROM.begin(…) ) a je na tvůrci aplikace, aby zajistil vyhrazení odpovídající velikosti pro práci crash.ino .

Modul byl testován s Core verze 2.3.0, 2.4.0 a git verze.

No a na závěr vlastní modul ke stažení:

Crash
Crash
crash.zip
2.1 KiB
1006 Downloads
Detaily

Pro zvídavější jsou zde odkazy k dalšímu studiu:

http://arduino-esp8266.readthedocs.io/en/latest/faq/a02-my-esp-crashes.html

https://github.com/SuperHouse/esp-open-rtos/wiki/Crash-Dumps

https://github.com/esp8266/Arduino/issues/1152

16 komentářů u „Z notýsku vývojáře – Jak zachytit občasný pád aplikace v ESP8266“

  1. Napad je to dobry, ale potreba take rici jak pak nalozit s daty. Jak v nich cist?

    Protoze me treba toto nic nerika:
    Stack: SP=0x3fff5040
    0x3fff5040: 00000000 00000000 00000000 00000000
    0x3fff5050: 00000000 a5a5a5a5 a5a5a5a5 00000290
    0x3fff5060: 3fff5408 00000000 00000000 00000000
    0x3fff5070: 00000000 00000000 00000000 00000000
    0x3fff5080: 00000000 00000000 00000000 00000000
    0x3fff5090: 00000000 00000000 00000000 00000000
    0x3fff50a0: 00000000 00000000 00000000 00000000
    0x3fff50b0: 00000000 00000000 00000000 00000000

    1. Inu, pak by mohl pomoci nástroj na dekódování https://github.com/me-no-dev/EspExceptionDecoder (viz. odkazy k dalšímu studiu…).
      PS: Doporučuji použít reálnou mailovou adresu, protože další příspěvky z podobných adres nebudu schvalovat – na to nemám čas. Pokud nechcete uvést rozumnou adresu, tak si laskavě komentář nechte pro sebe…

  2. Zdravím.
    Kolik prosím doporučujete nastavit SAVE_CRASH_SPACE_SIZE ?
    Děkuji

    1. Dobrý den,
      běžně používám 512 bytů a obvykle se vejde záznam o 2 pádech (druhý většinou už bývá zkrácený). Pokud je to nějaká anomálie, se kterou jsem se ještě nesetkal a která se nevejde, zvyšuji velikost na 1024 bytů a po odladění zase vracím zpět na 512.

  3. Zdravím,
    zrovna se pokouším využít crash.ino, ale asi dělám něco špatně. Do adresáře mého projektu sem nakopíroval crash.ino, ve sketchi mého projektu sem přidal #include , #define SAVE_CRASH_EEPROM_OFFSET 512, #define SAVE_CRASH_SPACE_SIZE 512 a ve funkci setup sem přidal EEPROM.begin(1024). V jednom místě programu volám crashGet(out) a out pak zobrazím. Ale při kompilaci mi to hlásí : crash:75: error: invalid use of incomplete type ‘struct rst_info’ a Esp.h:144:16: error: forward declaration of ‘struct rst_info’. Prosím o radu, díky.

    1. Dobrý den,
      nejspíš Vám chybí rozhraní user_interface. Do sketche Vašeho projektu přidejte:
      extern “C” {
      #include “user_interface.h”
      }
      Pavel Brychta

  4. Jěšte bych měl dotaz, ten EspExceptionDecoder se mi povedlo zprovoznit jen pod Linuxem, pod Win to hlásí chybějící nějaký exe soubor(nevím přesně teď to nemám po ruce). Proč se ptám, rád bych kompiloval pod linuxem, ale tam kompilátor dává menší velikost výsledného souboru(to jak tam píše “Projekt zabírá 353788 bytů (70%)”) než pod Win, neměla by být velikost souboru(zkompilovaného image) vždy stejná(v ideální případě). A nato svádím nezobrazení proměnné String, ale to je jiný problém a dlouhý vysvětlování.

    1. ESPEXception decoder mi běží i pod Windows – instalováno přesně podle instrukcí na githubu. Bez přesného popisu chybového hlášení těžko radit… Rozdílné veliosti výsledných souborů už byly diskutovány na internetu také – přišlo se na to, že ve zkompilovaném binárním souboru jsou obsaženy cesty k souborům, takže (údajně) odtud pramení rozdíl velikostí ( https://stackoverflow.com/questions/42773904/compiling-on-different-machines-delivers-different-hex-files-of-arduino-esp8266 ).

  5. Jo s tím to jde spustit, ale po spuštění to po mě chce otevřít soubor s příponou elf, to má být co?

  6. Mám nodemcu a narazil jsem na problém : blikám s delay(1)(ms) vše ok .
    Chtěl bych třeba 0.5- 0.9ms . Zkusil jsem dole a vše
    Resetne wifi – nodemcu E-12
    Když použiji třeba delayMicroseconds(900)
    nebo
    void myDelay(int del) {
    unsigned long myPrevMicros = Micros();
    unsigned long myCurrentMicros = myPrevMicros;
    while (myCurrentMicros – myPrevMicros <= del) {
    myCurrentMicros = Micros();
    }
    }

    {
    digitalWrite(ledPin,HIGH);
    myDelay(500);
    digitalWrite(ledPin,LOW);
    myDelay(900);
    }

    jsem amatér tak se nesmějte blbému dotazu
    děkuji za radu

  7. Nejdůležitější je zjistit, proč se CPU resetuje (důvod se vypíše na sériový port). ESP8266 je totiž v určitých vlastnostech jiné, než třeba AVR platforma – stále běží dva watchdogy, které hlídají, aby se program nezacyklil. Můj odhad je, že zabere některý z nich. Můžete zkusit za každé volání myDelay() doplnit volání yield(), který poskytne čas vnitřním procesům SDK a nakrmí hlídací psy, ale ovlivní délku cyklu. Myslel jsem si ale, že takto krátké časy by ESP mělo přežít bez resetu, protože yield je volaný po každém průchodu loop().

Komentáře nejsou povoleny.