Operačný systém reálneho času FreeRTOS som na mojom sector blogu spomínal už v minulosti v súvislosti s experimentálnou implementáciou senzorového uzla na ESP32 pre projekt Hladinomer vo frameworku ESP-IDF. Dnes si skúsime úpravy programu do špecifík FreeRTOS bližšie predstaviť. Upravovať budeme existujúcu implementáciu senzorového uzla projektu Hladinomer (ESP32) v prostredí Arduino IDE.
Treba však poznamenať, že Arduino IDE umožňuje pristúpiť iba k vyšším vrstvám mikrokontroléra a nie je v ňom možné robiť tak komplexné programy ako v ESP-IDF. Arduino Core pre ESP32 je vlastne wrapper (rozhranie), ktorý spúšťa isté komponenty ESP-IDF cez zjednodušené príkazy. Programovanie je tak jednoduchšie, avšak programátor nemá takú kontrolu nad hardvérom, Arduino Core je tak vhodné pre prototypovanie. Arduino Core neumožňuje využívať ULP koprocesor, ani prácu s rôznými hardvérovými funkcionalitami napr. Secure Boot, či Flash Encryption, ktoré sú natívne podporované v ESP-IDF. Taktiež nemá používateľ pokročilé možnosti konfigurácie, nakoľko menuconfig v Arduino Core nie je k dispozícii.
ESP32 je mikrokontróler z produkcie čínskej firmy Espressif Systems. Existuje mnoho modulov s týmto čipom. V dnešnom tutoriáli sa budeme držať najdostupnejšieho (a najlacnejšieho) modulu ESP32-WROOM-32. Modul je vybavený dvojjadrovým procesorom Tensilica Xtensa L6 harvardskej architektúry. Má externú flash pamäť s veľkosťou 4 MB a 512 kB dostupnej RAM pamäte (pre viac technických údajov pozri dokumentáciu).
Hlavný procesor Xtensa môže realizovať výpočty, komunikáciu so senzormi a perifériami, s ktorými je mikrokontróler prepojený cez dostupné zbernice. Zároveň však riadi a obsluhuje aj WiFi / Bluetooth zásobník (stack) pre zabezpečenie konektivity. Za správu a obsluhu WiFi / Bluetooth zásobníka zodpovedá jedno z jadier procesora u ktorého má WiFi / Bluetooth zásobník maximálnu prioritu. Jadrá sa najčastejšie označujú ako Core 0 (PRO_CPU) a Core 1 (APP_CPU).
Na Core 1 štandardne beží používateľská aplikácia vo forme úlohy (tasku). Málokto vie, že funkcia void loop() - nekonečná slučka funguje v Arduino Core ako task. Rovnakým spôsobom funguje aj funkcia app_main() v prostredí frameworku ESP-IDF. Po vytvorení hlavného tasku je možné vytvárať aj iné tasky prostredníctvom plánovača FreeRTOS a spúšťať ich tak efektívne na jadre procesora s určitou prioritou. Takto napísaný program nebude blokovať vykonávanie iných taskov, napríklad funkciou delay(), ktorá zastaví celý program pri štandardnom programovaní v Arduino IDE.
Nakoľko v našom prípade potrebujeme pre aplikáciu hladinomera merať výšku hladiny vody prostredníctvom ultrazvukového senzora vzdialenosti a následne dáta zapisovať do vzdialeného webového rozhrania je vhodné vytvoriť 2 tasky. Aby odosielací task nadväzoval na prijaté dáta, využijeme blokovací mechanizmus Queue - FIFO buffer. Buffer bude mať celkovo veľkosť 20 prvkov typu INT (2 bajty).
Bloková schéma Queue FIFO buffra
Oba tasky je možné spúšťať na rovnakom jadre, alebo je ich možné rozdeliť na samostatné jadrá procesora Xtensa. Task komunikujúci cez WiFi je vhodné spustiť na Core 0 - jadro protokolu, kde beží WiFi stack. Task vykonávajúci meranie ultrazvukovým senzorom vzdialenosti spustíme na Core 1 - aplikačnom jadre, kde beží aj hlavný task - funkcia void loop().
Task 1 (producer task - tvorí dáta)
- Vykonáva meranie ultrazvukovým senzorom vzdialenosti
- Zapisuje hodnotu int do fronty Queue
- Čaká 300 sekúnd
Task 2 (consumer task - používa dáta)
- Čaká v "nekonečnej slučke" na dáta vo fronte Queue
- Po prijatí dát vykoná jeden HTTP(S) POST request s dátami na webserver
Webserver (PHP backend) po prijatí dát vykoná porovnanie API kľúča mikrokontroléra, v prípade, že je autentizovaný a od posledného zápisu ubehlo aspoň 300 sekúnd, vykoná sa zápis dát o výške hladiny vody do MySQL databázy. Dáta sú ihneď viditeľné používateľovi na webovom rozhraní Hladinomera v dashboarde s automatickým refreshom dát cez jQuery.
Bežiaca aplikácia s 2 taskami - FreeRTOS (vrátane Core Debug Level Verbose) - HTTP
Bežiaca aplikácia s 2 taskami - FreeRTOS (vrátane Core Debug Level Verbose) - HTTPS
Úpravy programu do štandardov FreeRTOS
Pôvodný program pre senzorový uzol na ESP32 bol napísaný najjednoduchším štýlom. Pred funkciou void setup sa vykoná inicializáciá WiFiClienta, ultrazvukového senzora. Vo funkcii void setup() sa vykoná pripojenie na WiFi sieť, vypíše sa pridelená lokálna IPv4 adresa z DHCP. Následne sa vo funkcii void loop() spúšťa rutina časovaná cez millis() každých 300 sekúnd, kedy sa vykoná meranie a odoslanie dát na webové rozhranie. V loope() sa vykonáva aj udržiavanie spojenia s AP, v prípade výpadku sa vykoná reconnect. Kód nebol veľmi prehľadný, nakoľko nebol rozdelený do funkcií no bol funkčný.
Pri FreeRTOS programoch nie je potrebné v prostredí Arduino IDE využívať hlavičkové súbory knižníc súvisiach s FreeRTOS (je ich viacero), nakoľko tie sú vložené automaticky v procese kompilácie. Tak ako v predchádzajúcom prípade, prvotná inicializácia WiFiClienta a senzora sa vykoná identicky ako v prípade štantadného Arduino Core programu.
Vo funkcii setup sa vykoná pripojenie na WiFi sieť, prípadný reconnect je obslúžený v loope(). Do globálnych premenných sa doplní 2x trieda TaskHandle_t spolu s objektom tasku (vzorovo Task1 a Task2). Pre frontu Queue vytvoríme triedu QueueHandle_t s objektom q, priradíme mu hodnotu NULL.
Vo funkcii setup vytvoríme pre objekt q frontu zavolaním funkcie xQueueCreate(20, sizeof(int)); pre vytvorenie fronty o veľkosti 20 prvkov dátového typu int. Následne vytvoríme 2 tasky, ktoré môžeme priradiť k ľubovoľnému jadru procesora. Do funkcie xTaskCreatePinnedToCore() ide viacero parametrov - medzi nimi aj názov funkcie, ktorá predstavuje task. Aby sa funkcie cyklicky vykonávali a neboli nikdy ukončené, musia byť napísané efektívne napr. nekonečnou slučkou while(1). V prípade, že task neobsahuje takúto nekonečnú slučku, program sa vykoná iba raz a task bude naďalej existovať (nebude ukončený), no nevykoná sa. Do taskov som využil celé časti pôvodného programu bez funkcií millis(), ktoré som pôvodne používať kvôli časovaniu.
Vždy zvoľte prioritu tasku aspoň 1!!! inak sa task nemusí vykonávať, ako sa to stalo mne, keď som vo funkcii loop() použil yield() a task s prioritou 0 sa vôbec nezačal vykonávať :-). Každý task má priradený stack (veľkosť pamäte), ktorá nie je zdieľaná s inými taskami. Funkcie taskov sú štandardne beznávratového typu void(). Task, ktorý vykonáva meranie zapisuje hodnotu do fronty funkciou xQueueSend(). Task, ktorý vykonáva HTTP(S) request na webserver využíva blokovaciu funkciu xQueueReceive(), ktorá pozdrží beh podporogramu o zvolený čas, najviac je možné použiť portMAX_DELAY, čo predstavuje cca 50 dní.
Vo fronte Queue nikdy nenastane situácia, aby boli vo fonte 2 alebo viac prvkov, je teda jedno, či zapisujeme na začiatok, alebo na koniec fronty (poznámka: xQueueSend() zapisuje na koniec fronty). V prípade, žeby sme do fronty vkladali prvky rýchlejšie ako by ich stíhal spracuvávať druhý task, bolo by možné do fronty vložiť maximálne 20 prvkov. V momente, keď task 2 získa prvok z Queue, je automaticky odstránený z fronty.
Pre Queue existujú aj iné funkcie pre zápis / čítanie z fronty. Pri niektorých funkciách sa nevykonáva vymazanie položky z fronty, niektoré sú navrhnuté pre použitie v prerušení - ISR (pozri dokumentáciu - najlepšie ESP-IDF reference). Pokým Queue nie je vytvorená, nespustí sa ani jeden z taskov (nekonečná slučka).
Programová implementácia bola vytvorená pre HTTP i HTTPS protokol. HTTPS protokol umožňuje šifrované spojenie s webserverom, využíva certifikát certifikačnej autority - Root CA v .pem formáte, ktorý je vložený do zdrojového kódu a uložený do PROGMEM.
Programovú implementáciu pôvodného programu nájdete na Githube (sú tam použité direktívy aj pre Arduino Uno / ESP8266, ktoré sa využívajú pre rovnaký prevádzkový režim StandBy, kompilátor na základe nastavenej dosky v Arduino IDE dokáže kompilovať určité časti kódu pre danú mikrokontrolérovú platformu):
https://github.com/martinius96/hladinomer-studna-scripty/blob/master/examples/Hladinomer/HTTP/StandBy/StandBy.ino
Modifikovanú verziu HTTP firmvéru pre beh v FreeRTOS nájdete na Githube:
https://github.com/martinius96/hladinomer-studna-scripty/blob/master/examples/Hladinomer/HTTP/RTOS_ESP32_Arduino/RTOS_ESP32_Arduino.ino
Modifikovanú verziu HTTPS firmvéru pre beh v FreeRTOS nájdete na Githube:
https://github.com/martinius96/hladinomer-studna-scripty/blob/master/examples/Hladinomer/HTTPS/RTOS_ESP32_Arduino/RTOS_ESP32_Arduino.ino
Projekt Hladinomer má vlastnú stránku, kde je možné nájsť technické informácie projektu, spôsob a početnosť meraní ultrazvukovým senzorom vzdialenosti, možnosť stiahnuť jednotlivé knižnice (NewPing, Ethernet2 pre Arduino a pod..): https://martinius96.github.io/hladinomer-studna-scripty/