OTA bootloader ESP32-re

Miközben dolgoztam egy ESP32-re épülő projektemen (lásd előző cikk), pontosabban a firmware-en, történt egyszer, hogy meghalt a programozóm. Ami azért fordulhatott elő, mert valahogy kimaradt belőle a gyártó által (valamilyen okból) ajánlott ESD védődióda. Ez már a második, ami így halt meg és egyben az utolsó is. Jött az ötlet, hogy ha már úgyis akarok bele saját bootloader-t, meg WiFi-n (vagyis vezeték nélkül) firmware-t frissíteni, akkor annak most lenne itt az ideje. Nekiültem reggel és hajnali háromig megvolt a dolog.

Kezdjük ott, hogy milyen framework van az ESP-hez és hogy lehet fejleszteni rá. Kis keresés után ráakadtam (segítséggel, köszi Simon!!) a VS Code nevű Visual Studio klónra (szintén Microsoft), ami sokkal kisebb (kb 40MB) és rengeteg kiegészítő plugin van hozzá, mint a PlatformIO is, amit kifejezetten IoT fejlesztésre találtak ki. A telepítés nagyon egyszerű, kis kereséssel biztos lesz valahol tutorial.
Ezek után, hogy minden megvan, a PlatformIO-ban kikeresek egy ESP32 alapú feljlesztőkártyát (nekem ESP32-Dev board vagy hasonló), és letölti mellé az ESP-IDF-et. Aki fejlesztett már STM32-re, annak ismerős lesz. Itt magas szinten lehet fejleszteni, ugyanakkor nem veszik el a részletes konfigurálhatóság. Nem utolsó sorban egész jól van dokumentálva. Itt van a forrásfájlok között a bootloader is, és minden ami csak érdekelhet, és ha nem is nagyon alaposan de érthetően és szépen meg van csinálva a kód. Igaz így is órákat vett el hogy kiigazodjak rajtuk.

Mi is a cél? Halál a kábelekre!
A feladat az, hogy elég legyen tápfeszültséget adni az eszközre, és minden egyéb dolgot vezetékek nélkül intézni. Ahogy öregszek, egyre jobban megutálom a kábeleket, a sok kontakthibát, a csomókat, amiket lehetetlen kibogozni és hogy nincs már USB-port csak még egy eszköznek. Bővebben a feladat, hogy WiFi-n, mint csatornán átvigyek pár vezérlési adatot, és főleg hogy a firmware binárist, hiba nélkül (bízzunk meg a TCP-ben).

ESP32 boot folyamat
Az ESP boot-olása így néz ki, ezt persze nem dokumentálták, hanem a kódból és egyéb helyekről kellett rájönni, hogy mit hoz működésbe a várázsfüst a lelkében:

  1. A 0x8000-es címről elindul a chip-be írt (2.szintű) bootloader kód.
  2. UART0 inicializálás, hardveres lábak, 8bit, 0paritás, 1stop, 115200 baud.
  3. Flash inicializálás az SPI0-n, ezt a címteret hardveresen be-map-peli, és gyorsítótárból éri el.
  4. A 0x1000-es címről a bootloader program beolvassa a partíciós táblát.
  5. Ha van érvényes OTA partíció (2 van / 1 érvényes) akkor ezt “tölti be”.
  6. Ha nincs érvényes OTA partíció, akkor megnézi, hogy van-e FACTORY partíció.
  7. Ha semmit sem talál, akkor ezt kiküldi UART-on (nothing to load).
  8. A kiválasztott partíciót (egy pointer) betölti, átadja a vezérlést a programunknak.

Kicsit bővebben:
Az IC-n beleül elindul a beleírt bootloader kód.  Ide a megfelelő lába le-felhúzásával lehet eljutni. Ebben a módban indulva tudunk feltölteni kódot, de ezt bármelyik oldalon elmagyarázzák. Ebben a módban kell feltölteni 3 fontos binárist: a bootloadert, a futtatandó kódot és opcionálisan (de nagyon ajánlottan egy partíciós táblát (max 95 partícióval). Itt persze rengeteg feature van, mint cimkék, partíció típusok, kulcsok, partíció titkosítás, egyebek. De persze enélkül is működik a dolog, mint látjuk majd. Az egyetlen szépséghibája a dolognak, hogy pont az kell hozzá, aminek a hiánya miatt elkezdtük a dolgot. Szerencsére vak tyúk is talál szemet, a SEM-es kukában ráakadtam egy FTDI-os USB-UART átalakító még működő példányára, és így biztosítva volt a tojás a tyúkhoz.

Partíciók:
Mivel a specifikáció megengedni a tetszőleges partíció típusokat (csak pár foglalt van), így létrehoztam egy tetszőleges típuskódot. Utána fordítottam egy tetszőleges partíciós táblát magamnak. Az alap partíciókon kívül csak egy “firmware” típusú és egy “recovery” partíció kapott helyet. Eldobtam az OTA elnevezést, és a factory-t megtartottam, mint recovery. A firmware értelem szerűen az üzemszerűen használt program. A recovery (factory) pedig diagnosztikához és firmware feltöltéshez használható program. Még egy szépséghibája a dolognak, hogy ehhez is vezeték és programozó kell. Így egy fix bootlaoder és partíciós tábla kerül bele, és ha nagyobb flash “kerül” a cuccba, akkor sem használhatunk tágasabb partíciókat. Bár ha valaki ilyenekre vetemedik, tegyük fel, hogy nem okoz gondot neki átprogramozni az ESP-t is.
Nincs kizárva, hogy felhasználói kódból is hozzá lehetne férni a belső címtartományhoz, vagyis a firmware kódból módosítani a 2. szintű bootloader-t (az első szintű nem módosítható/hardveres) és a partíciós táblát is. Ez esetben ha mégis megszakadna a kapcsolat frissítés közben, akkor azt már csak vezetékes módban lehetne kijavítani, az első 1. szintű bootloader-rel (ami pont az ilyenek miatt hardveres, hogy azt ne lehessen elrontani).

Boot-olás:
Ha a partíciók rendben, akkor még el kell dönteni, hogy mi induljon el. A legegyszerűbb megoldás egy kivezetett GPIO földre húzása. Ha a földre van húzva (vagyis a gomb benyomva), akkor induljon el a recovery ha pedig nincs, akkor pedig a firmware. Ez kellően egyszerű. Látszólag. Ez gyanús… Mindenki általános bánatára, hogy mivel a hardver inicializálását pont a bootloader kezeli, így itt nem érhetők el a magas szintű függvények. Csak nagyon alacsony szintű függvényeink vannak, amik gyakorlatilag közvetlenül a megfelelő regiszterek bitjeit birizgálják. Ami persze dokumentálva van a ‘technical reference manual‘-ban, ami így is eszméletlen tömören fogalmaz. Most akkor hogy is van a direkt olvasás? Akkor kell signal-t használni, kell-e GPIO matrix, vagy IOMUX-ot kell használni, de nem mert az a GPIO-t pont az analóg rész kezeli, vagyis az az RTCMUX kell… Na akkor most mit kell írni? Képzeljük el az előbbi opciókat logikai operátorok tetszőleges permutációjával összekapcsolva, és keressük meg ezek alkotta lineáris függvénytérből a nekünk megfelelőt. És akkor honnan, melyik bitet kell olvasni, melyiket írni, hogy  bemenet legyen? Sokáig tartott és sok hajszál vett végső búcsút fejemtől, míg rájöttem, hogy melyik bitet kell egybe írni, és melyiket kell olvasni. Ráadásul a belső felhúzó ellenállás így sem megy (még). Ez csak ízelítő, hogy milyen nem triviális dolgok jönnek elő hasonló esetekben.

A partíciós táblák és a módosított bootloader kód a recovery forráskódjával együtt van, mint az ESP oldali kódok. A módosított fájlt ha fordítani szeretnétek, akkor ajánlom mentsétek le a régit, és írjátok felül az újjal (vagyis a csatolttal). Nekem itt van a bootloader_start.c :
C:\Users\Bennie\.platformio\packages\framework-espidf\components\bootloader\src\main
Innen már csak egy sima program fordítása (tehát akármelyik projekt) , és vele együtt lefordul binárisra ez is.

Recovery:
Ez a program intézi elsősorban a program feltöltését, másodsorban a tesztfunkciókat. Jelenleg csak GPIO-kat lehet konfigurálni ki/bemenetnek és a szinteket állítani (HIGH/LOW) mint kimenet. Ez lássuk be elég hasznos, de most nem kellett, így nagyon nem mentem bele a lehetőségek kiaknázásába. A lényeg a bináris átjuttatása a csatornán. Erre a legegyszerűbb kitaposott út egy TCP kiszolgáló indítása egy WiFi hozzáférési ponton. Erre csatlakozva egy egyszerű alkalmazásrétegbeli protokollon keresztül küldhetünk parancsokat és adatcsomagokat.

Az ESP oldali kódok és binárisok, vagyis a bootloader, a partíciós tábla és a recovery: hxsw_recovery

PC oldal:
Mivel sokat használok fejlesztőkörnyezeteket, szempont volt, hogy integrálni lehessen. Ez majdnem összejött. Annyi a bökkenő, hogy csak ‘firmware.bin’ nevű fájlokat tölt fel. Ami persze kapóra jön; dupla kattintás és feltöltötte a fájlt, nem kell paramétereket adni át. Másik hátrány, hogy ezzel nem igazán lehet kihasználni a tesztelési funkciókat. Egyébként az egészet egy PacketSender programmal teszteltem, ami tetszőleges IP csomagokat tud küldeni a hálóra. Az egész winsock2 könyvtárra épül.  Röviden: csatlakozik a 192.168.4.1:271828 TCP szerverre (a DHCP ezt a címet adja mindig) -> elküldi az update parancsot -> elküldi a csomagokat -> kilép. Ezt természetesen hibakezeléssel. Fogjuk rá, hogy hiba “kezeléssel”. Olyan hit&run-féle hibakezelés, ha hiba van, akkor széttesszük/mossuk kis kezeinket és feladjuk. Azonban a gyakorlatban általában nincs hiba, csak olyan összehányt, de már működő dolog, a szükség szörnyszülötte.
A forráskód és egyéb szükséges fájlok: hxsw_pc_side

És mindezek megvalósítása, avagy ahol az elmélet elválik a gyakorlattól:
A partíciós táblával semmi gond nem akadt, így nem érdemel különösebb érdeklődést. A kódolást kezdtem a végén, tehát először a recovery kódot írtam meg, amit még hagyományos factory partícióként töltöttem fel és foltozgattam. Első probléma ott akadt, hogy mikor még a hardvert terveztem, nem igazán hallottam még olyat, hogy antenna méretezés, sem olyat, hogy hullámimpedancia, és piros szivecskékbe sem karcoltam még labmdanegyedet (nem mintha azóta igen). Szóval csodával határos módon úgy adódott, hogy így is “tök jól” működik valahogy ez a (mellesleg rosszul) másolt antenna . Utána pár sor kód volt csak az AP létrehozása. A TCP szerver már kicsit bonyolultabb volt, mivel ez nem része az ESP-IDF-nek, de ráakadtam egy oldalra, és hivatásos programozóként összeollóztam ezt is. Vannak ugyan szépséghibái, mint hogy egy kapcsolódás után második kapcsolatot nem tud kezelni, újra kell indítani. Utána csak pár sor, hogy értelmezze a parancsokat, és visszajelezzen (OK/ERROR). Megint kapóra jött az ESP-IDF könyvtár, ami teljes támogatást ad a partíciók kezeléséhez. Egy függvényhívás, és formázza a partíciót (amire azért van szükség mert a flash memória üres állapotban csupa 1-est (255) tartalmaz, és ezt később könnyebb kinullázni). Még egy függvényhívás, és a beérkezett csomagot a megfelelő offset-re másolja majd visszajelez. Ha kész, akkor megszakítja a kapcsolatot. Tök egyszerű és meglepő módon elsőre működött. Azóta csak egyszer fordult elő, hogy megállt feltöltés közben. Gondolom köze lehet hozzá, hogy a koli eléggé szennyezett a 2.4GHz-es sávban és az antenna is majd/nem alkalmas hírközlési alkalmazásokra.

Ezt látni az AP-hoz csatlakozáskor, illetve feltöltés előtt:

És egy (sikeres) firmware feltöltés:

Természetesen amit lesz időm, igényesebb kód is kikerül majd ide. Így is le kellett adnom az esztétikai és egyéb igényeimből, hogy egyáltalán megszülethessen a működő szoftver rész.

Mivel a CNC újra működik, hamarosan következik a folytatás is, ahol a kapcsolónak elkészül a doboza. Ezzel párhuzamosan elkészült a nyák javított és fejlettebb verziója, többek között NFC-vel és kompaktabb szenzorokkal. Remélem addigra ép hardware-ben és software-lesz!