S narastajúcim výkonom mikrokontrolérov sa v súčasnom období posúvajú možnosti ich využitia aj do oblastí, ktoré boli pred pár rokmi vyhradené výkonným počítačom alebo výpočtovým strediskám. Z relatívne jednoduchých čipov, často pozostávajúcich len z MCU a nevyhnutných periférií, primárne určených pre obsluhu a jednoduché spracovanie dát zo senzorov sa vyvinuli komplexné zariadenia obsahujúce priamo na čipe univerzálne komponenty potrebné pre tvorbu kompaktného a cenovo veľmi prijateľného systému riadenia, zberu, spracovania a transportu dát zo senzorov.
Co se dozvíte v článku
Výsledkom tejto revolúcie je rozvoj technológie IoT, táto ale vyžaduje iný prístup k vývoju ako pri implementácii klasických technológií. Podstatou prístupu je využívanie technických prostriedkov (komunikačné rozhrania, prevodníky, pamäť) v abstraktnej forme a sústredenie kreativity a pozornosti na spracovanie vlastných dát a interakciu s prostredím.
Vývoj aplikácie na báze IoT
Vývoj typickej IoT aplikácie pozostáva z dvoch častí
- Vývoj spojený s obsluhou hardware – vstupných zariadení a senzorov, zobrazovacích a signalizačných prvkov a výstupných zariadení. Tento môže mať niekoľko úrovní – od jednoduchého pripojenia štandardného prvku ku normalizovanému rozhraniu (I2C, SPI, … ) a tvorbe príslušnoho API pre jeho obsluhu až po vývoj vlastného špecializovaného senzora alebo výstupného zariadenia s firmware, komunikačným rozhraním, implementáciou a spracovaním dát a unikátnym API.
- Vývoj spojený s vlastnou aplikáciou – od jednoduchej komunikačnej aplikácie až po rozsiahly systém komunikujúci cez Internet. Pretože individuálny vývoj od základu je náročný a zdĺhavý, pre implementácie IoT aplikácií bolo v poslednom období vytvorených množstvo operačných systémov. Typickým predstaviteľom je projekt Zephyr, ktorý je portovaný na viacej ako 200 platforiem, pričom pre každú platformu má implementovanú obsluhu základných periférií. Súčasťou je mikrokernel alebo nanokoernel v závislosti od výkonu zariadení, podporuje multithreading a non-preemptive a preemptive plánovač (scheduling), programovacím jazykom je C a C++.
Je zrejmé, že vývoj IoT aplikácie vyžaduje tvorbu a používanie vývojových nástrojov, ktorých príprava a odladenie môže v závislosti od rozsahu trvať nezanedbateľnú dobu. Jednou z možností, ako túto etapu zjednodušiť, je použitie flexibilného univerzálneho nástroja, umožnujúceho interaktívnu prácu a testovanie jednotlivých častí IoT zariadenia. V nasledujúcej časti ukážeme možnosti využitia interpreteru jazyka Python ako vývojového prostriedku pre IoT.
MicroPython
Python je všeobecne pokladaný za programovací jazyk vysokej úrovne ďaleko vzdialený od hardware, môžeme ho ale pokladať aj za vynikajúci nástroj, ktorý bude užitočný pri vývoji hardware. MicropPython je prepisom referenčnej implementácie CPython jazyka Python pre mikrokontroléry. Implementácia je portovaná pre rôzne cielové platformy, je škálovateľná a open-source. O jeho popularite svedčí aj to, že na githube v súčasnej dobe existuje asi 2000 rôznych vetiev (fork) s rôznymi modifikáciami a úpravami pre rôzne vývojové a experimentálne dosky. Z aplikačného hľadiska je použitie MicroPython-u jednoduché, ako firmware sa nahrá štandardným programovacím software pre konkrétny typ mikrokontroléra do jeho flash pamäte a zvyčajne prostredníctvom emulácie sériového rozhrania komunikuje s terminálovou aplikáciu v počítači.
Oblasť použitia MicroPythonu a jeho klonov môžeme rozdeliť do niekokých kategórií
- Výuka a vzdelávanie, MicroPython poskytuje možnosť interaktívnej komunikácie s mikrokontrolérom v slučke REPL (Read–Eval–Print Loop). S priamym prístupom k perifériám mikrokotroléra cez akýkoľvek terminálový emulátor bez potreby písania a vysvetlovania množstva kódu potrebného na inicializáciu a elementárnu komunikáciu je možné na veľkom množstve podporovaných dosák vysvetliť študentom základné princípy zberu a spracovania dát, naviac v jednoduchom programovacom jazyku.
- Vývoj a testovanie periférií a senzorov. MicroPython poskytuje odladené a vyskúšané referenčné implementácie rozhraní mikrokontrolérov, čím vývojára zbavuje potreby implementácie celej vertikálnej štruktúry spojenej s komunikáciou a riadením periférneho zariadenia. Moderné integrované periférie komunikujúce cez sériové rozhrania (I2C, SPI, CAN …) sú riadené zápisom a čítaním hodnôt často z desiatok rôznych registrov, s individuálnym významom jednotlivých bitov a vzájomným previazaním hodnôt. Priamym interaktívnym prístupom v jednoduchom a populárnom jazyku k registrom zariadenia je možné jednoducho overiť funkciu periférií, vyvinúť a odladiť príslušný hardware a algoritmy pre riadenie a zber dát zo zariadenia. Vďaka abstrakcii hardware a univerzálnosti implementácie je možné pre vývoj využiť aj iné platformy, ako bude cieľová a to bez potreby detailnej znalosti jej programovania.
- Monitorovací a konfiguračný nástroj pre komplexné aplikácie. Rozsiahle aplikácie na výkonných mikrokontroléroch a FPGA obsahujú často implementáciu nezávislých pomocných prostriedkov pre monitorovanie a nastavovanie parametrov systému, pri FPGA to býva často SW implementácia niektorého z malých mikroprocesorov (8051, Z80 …), pomocou programu pre tieto mikroprocesory je možné sledovať a nastavovať parametre a konfiguráciu systému. Prvé pokusy s implementáciou MicroPython-u na FPGA ukazujú perspektívu daľšieho vývoja. V oblasti operačných systémov existuje experimentálny port MicroPython-u pre monitorovanie a nastavovanie parametrov kernelu Linuxu. Pri mikrokontroléroch je situácia jednoduchšia, existuje priamy port MicroPython-u ako aplikácie pre projekt Zephyr.
Je zrejmé, že pri vhodnej forme návrhu zariadenia s mikrokontrolérom a pripojených periférnych zariadení môžeme dosiahnúť stav, kedy je možné jednoducho počas vývoja ako aj pri konečnej podobe IoT zariadenia vymeniť MicroPython za firmware zariadenia alebo naopak pri potrebe ďaľšieho vývoja, upgrade alebo modifikácie. Porovnanie klasickej architektúry vývojového prostredia a alternatívy s MicroPythonom je na nasledujúcom obrázku.
Aplikačné možnosti pri vývoji IoT na platforme STM32
Možnosti použitia MicroPython-u pri vývoji IoT môžeme demonštrovať na príklade platformy mikrokontrolérov triedy STM32. Táto trieda populárnych mikrokontrolérov obsahuje široké spektrum typov líšiacich sa výkonom, pamäťou perifériami a možnosťami optimalizácie spotreby.
Aj keď oficiálne nie je v distribúcii MicroPython-u platforma STM32 podporovaná, môžeme v zdrojovom kóde MicroPythonu nájsť port pre niektoré základné členy tejto platformy a vlastná kompilácia firmware pre zvolený typ mikrokontroléra je vďaka dokumentácii bez problémov. Inštalácii MicroPython-u na platforme STM32 bol venovaný predchádzajúci príspevok.
Prenositeľnosť kódu
Vďaka intenzívnemu využívaniu technológie HAL (Hardware Abstraction Layer) umožnuje MicroPython prenositeľnosť kódu nielen medzi mikrokontrolérmi v rámci jednej platformy, ale aj naprieč celým spektrom zariadení, na ktorých je portovaný. V nasledujúcom jednoduchom programe sú načítané dáta z teplomera a teplotného komparátora LM92 [PDF] pripojeného na zbernicu I2C číslo jedna. Dáta sú načítané ako 2 Byte z TEMPERATURE REGISTER na adrese čislo 0, čip obsahuje 7 registrov pre čítanie dát a nastavenie prahových hodnôt komparátorov a hysterézy.
import machine def read_LM92_TR(ic, addr): raw = ic.readfrom_mem(addr, 0, 2) # nacitanie 2 byte z registra 0 data = (raw[0] << 8) + raw[1] # konverzia 16 bit format td = data >> 3 # 2'compl b15-b3 temperature TEMP = (-(td & 0x1000) | (td & 0xFFF))* 0.0625
L = data & 0x01 # T_LOW -> H, TEMP < 10 deg H = (data & 0x02) >> 1 # T_HIGH -> H TEMP > 64 deg C = (data & 0x04) >> 2 # T_CRIT -> H TEMP > 80 deg return TEMP, C, H, L # MCU STM32L432KC ic=machine.I2C(1) # init I2C interface, PA10-SDA, PA9-SCL print(ic.scan()) # -> [75] list of all devices in I2C(1) print(read_LM92_TR(ic, 75)) # -> (24.5625, 0, 0, 0)
Pre skopírovanie programu do prostredia môžeme použiť skratky CTRL+E a CTRL+D. Uvedený program bude plne funkčný nielen na všetkých podporovaných mikrokontroléroch z triedy STM32, ale aj na komkoľvek zariadení, na ktoré je portovaný MikroPython a ktoré na palube obsahuje I2C rozhranie. Ak sú preto na vývoj periférneho zariadenia kladené vyššie požiadavky, môžeme samotný vývoj realizovať na procesore vyššej triedy a pre finálnu aplikáciu využiť jednoduchší procesor.
Modularita
Aby nebolo potrebné po resete mikrokontroléra opakovane nehrávať časti odladeného kódu do prostredia MicroPython-u, tento umožňuje pridanie nového kódu (frozen module) ako knižnice, ktorá sa stane súčasťou firmware. Celý postup je veľmi jednoduchý a spočíva v uložení súboru s kódom knižnice v Pythone do adresáru vytvoreného adresáru .ports/stm32/modules a následnom skompilovaní firmware a jeho nahratí do mikrokontroléra. Knižnica je dostupná pomocou štandardneho príkazu import.
micropython | +--ports | | | +--stm32 ... | | ... +--my_modules <--- vytvoreny adresar | | ... +--demo.py <--- kod naseho modulu
Pri kompilácii potrebujeme zadefinovať premennú FROZEN_DIR. Kompilácia je rovnaká ako pri vytváraní klonu pre cielovú platformu. Po kompilácii a nahratí firmware do MCU je modul dostupný cez import demo. Prekročenie rozsahu pamäte Flash má za následok chybu kompilácie.
adresar ./ports/stm32/, kompilacia pre Nucleo32 STM32L432KC make BOARD=NUCLEO_L432KC clean make BOARD=NUCLEO_L432KC FROZEN_DIR=./my_modules
Pri mikrokontroléroch s väčšou FLASH pamäťou je možné jej voľnú časť využiť ako pamäťové médium mapované ako file systém. Knižnica os poskytuje základné funkcie pre vytváranie adresárov a manipuláciu so súbormi.
>>> import os >>> help(os) object <module 'uos'> is of type module __name__ -- uos uname -- <function> chdir -- <function> getcwd -- <function> ilistdir -- <function> listdir -- <function> mkdir -- <function> ... mount -- <function> umount -- <function>
Súborový systém je možné rozšíriť o externé pamäťové médiá (SD-Card), podpora komunikácie s kartami je súčasťou MicroPython-u. File systém je dostupný aj mimo prostredia MicroPython-u, pomocou utility ./tools/pyboard.py je možné do neho ukladať, mazať súbory a vytvárať adresárovú štruktúru.
usage: pyboard.py [-h] [--device DEVICE] [-b BAUDRATE] [-u USER] [-p PASSWORD] [-c COMMAND] [-w WAIT] [--follow] [-f] [files [files ...]]
Doplňujúce argumenty pre -f (–filesystem)
ls ls ./adresar cp ./file ./ rm ./file rmdir addr mkdir addr cat ./subor vypis suboru na terminal resp cat./subor > lokalny.txt
Príklad
python pyboard.py -f ls python pyboard.py -f cp ./test.py :
Ak sa prepíšeme resp. upravíme súbor main.py v koreňovom adresári tak aby obsahoval vykonateľný kód, tento sa po resete automaticky spustí.
Škálovateľnosť
Elementárna verzia MikroPython-u bez knižníc má po skompilovaní veľkosť asi 20KB, pri konfigurácii pre jednotlivé platformy sa ale tvorcovia snažili optimálne využiť flash pamäte mikrokontrolérov doplnením čo najväčšieho množstva štandardných knižníc a podporou periférií.
V prípade, že mikrokontrolér obsahuje väčšiu pamäť FLASH a RAM, je súčasťou firmware možnosť mapovania pamäte ako súborového systému, do ktorého je možné zaradiť aj externé pamäťové média ako je SD karta a pod. V prípade, že pri vývoji nie sú niektoré knižnice a drivery potrebné, je možné konfiguráciu firmware upraviť podľa aktuálnej potreby. MicroPython pre konfigurácou cieľového firmware pre každú platformu používa súbor mpconfigboard.h. Tento obsahuje premenné, pomocou ktorých vieme zaradiť alebo vyradiť z kompilácie firmware zvolené časti zdrojového kódu.
Rozširiteľnosť
Pri vývoji neštandardných periférií nemusia štandardné drivery, ktoré sú súčasťou MicroPython-u, vyhovovať a môže vzniknúť požiadavka na nízkoúrovňovú obsluhu periférie. Podobne ako pri štandardnom Pythone je možné aj MicroPython rozširovať o natívne moduly napísané v C/C++ a ktoré môžu využívať aj systémové knižnice pre obsluhu periférií mikrokontroléra.
Pre vytvorenie modulu musíme rovnako ako v štandardnom Pythone vytvoriť rozhranie medzi natívnym modulom a jeho reprezentáciou v Pythone. Detailný postup je popísaný v dokumentácii, pre vytvorenie rozhrania využíva množstvo makier a pri rozsiahlejších moduloch môže byť štruktúra značne komplikovaná.
Pretože rozhranie k natívnym modulom je v značnej miere štandardizované, pre štandardný Python existujú generátory kódu, napr. SWIG, ktoré vygenerujú potrebné rozhranie na základe zdrojového kódu modulu. Pri MicroPythone sa používa opačný postup, môžeme použiť generátor rozhrania (stub), experimentálne implementácie, ktorý vytvorí stub pozostávajúci z makier v C na základe deklarácie funkcie v Pythone.
Príklad 1.
Príklad obsahuje jednoduchý modul, ktorý pozostáva z inicializácie a funkcie, ktorá prepína LED stanovený počet krát a s nastaviteľnou dĺžkou intervalu. Deklarácia funkcií pre vygenerovanie rozhrania má tvar:
# Subor blink.py, Python 3.7 # Deklaracie funkcii rozhrania def init()-> None: """ Funkcia pre inicializacia portu a nastavenie pinu s on-board LED, na NUCLEO_STM32L432KC je LED pripojena na PORTB, PIN3 """ def blink(n: int, delay: int) -> None: """Blikanie LED :param n: pocet bliknuti :param b: delay :return: None"""
Na základe tejto deklarácie vygenerujeme pomocou [9] stub, do ktorého doplníme implentáciu príslušnej funkcie. Pre vygenerovanie môžeme použiť postup uvedený v návode ku generátoru alebo môžeme použiť nasledujúci jednoduchý skript.
# Subor gen_stub.py # skript pre generovanie stubb pre moduly mikropythonu podla # https://github.com/pazzarpj/micropython-ustubby import ustubby import sys if len(sys.argv) < 1: print("Pouzitie: python3.7 gen_stub.py input.py > output.c") exit() md = sys.argv[1].split(".")[0] stub = __import__(md) s = ustubby.stub_module(stub) print(s)
Implementáciu vlastného kodu je možné vytvoriť pomocou HAL alebo LL, príklad časti kódu s implementáciou využívajúcou low-level knižnice STM32 je nižšie (celý kod je dostupný na GiHube)
... STATIC mp_obj_t blink_blink(mp_obj_t n_obj, mp_obj_t delay_obj) { mp_int_t n = mp_obj_get_int(n_obj); mp_int_t delay = mp_obj_get_int(delay_obj); //Your code here // ----------------------------------------------------------------- int i=0; for(i=0; i<n; i++){ LL_mDelay(delay); LL_GPIO_TogglePin(GPIOB, LL_GPIO_PIN_3); } // ----------------------------------------------------------------- return mp_const_none; } MP_DEFINE_CONST_FUN_OBJ_2(blink_blink_obj, blink_blink); ...
Štruktúra adresárov má potom tvar
my_project/ | +-- modules/ <-- adresar modulov | +--blink/ | +--blink.py <-- zdrojovu subor pre stub generator | +--blink.c <-- vygenerovany a doplneny zdrojovy kod | +--micropython.mk <-- konfiguracia modulu | +-- micropython/ <-- originalny MicroPython +--ports/ <-- porty pre platformy ... +--stm32/ <-- adresar pre kompilaciu firmware ...
Kompilácia s použitím HAL knižníc nevyžaduje žiadne úpravy, pri kompilácii implementácie modulov s LL knižnicami musíme upraviť ./stm32/Makefile
- doplniť inicializáciu príznaku USE_FULL_LL_DRIVER pre sprístupnenie LL knižníc
- doplniť kompiláciu LL knižníc
V sekcii CFLAGS doplniť
CFLAGS = $(INC) -Wall ... ... CFLAGS += -DUSE_FULL_LL_DRIVER <- doplneny flag
V sekcii SRC_HAL doplniť
SRC_HAL = $(addprefix $(HAL_DIR)/Src/stm32$(MCU_SERIES)xx_,\ hal.c \ ... hal_uart.c \ ll_gpio.c \ <- doplnene LL drivery bez prefixu ... )
Pre kompiláciu modulu ešte musíme v adresári modulu vytvoriť súbor micropython.mk, v ktorom je definovaná štruktúra modulu, ktorý môže pozostávať aj z viacerých súborov.
# Subor micropython.mk BLINK_MOD_DIR := $(USERMOD_DIR) SRC_USERMOD += $(BLINK_MOD_DIR)/blink.c CFLAGS_USERMOD += -I$(BLINK_MOD_DIR)
Spustenie kompilácie je potom rovnaké ak v predchádzajúcom prípade, v adresari ./ports/stm32/ spustíme kompiláciu firmware
make BOARD=NUCLEO_L432KC USER_C_MODULES=../../../modules CFLAGS_EXTRA=-DMODULE_BLINK_ENABLED=1 all
ktorý štandardným postupom zapíšeme do pamäte mikrokontroléra. Použitie modulu v MikroPythone je rovnaké ako akejkoľvek inej knižnice
>>> import blink >>> blink.init() >>> blink.blink(10, 100)
Príklad 2. DDS Generátor
Ako príklad rozsiahlejšieho modulu ukážeme implementáciu DDS (Direct Digital Synthesis) generátora. Pre niektoré aplikácie vyžadujúce aktívny zdroj budenia je potrebné niekedy použiť zdroj harmonického signálu s variabilnou frekvenciou. Pre implementáciu použijeme jednoducho implementovateľný princíp DDS, ktorý pozostáva z premennej reprezentujúcej 32bit fázový akumulátor a 32bit premennú fázového kroku.
Časť slova akumulátora je použitá ako adresa hodnoty v tabuľke hodnôt generovaného priebehu. Pre syntézu harmonického priebehu použijeme 8bit rozlíšenie DAC prevodníka s dĺžkou tabuľky 512 hodnôt. Pre prenos hodnôt z pamäte do DAC využijeme DMA prenos, tento budeme realizovať v dvoch krokoch využitím double-buffer, počas prenosu obsahu jedného buffra do DAC pripravíme inicializujeme obsah druhého. Pre riadenie prenosu je použitý základny časovač TIM6, výstup je PA4.
# Subor dds.py pre stub generator # Deklaracie rozhrania pre DDS na STM32L4 def init(prescaler: int, period: int, )-> None: """ Inicializacia DMA, TIM6, DAC a PA4 :param prescaler: nastavenie preddelicky pre TIM6 :param period: hodnota periody TIM6 - kroku update :return: None """ def set(step: int) -> None: """ Spustenie fazy :param step: inkrement fazy :return: None """
Jadro DDS generátora pozostáva z funkcie, ktorá inicializuje pole s hodnotami presúvanými v DMA do DAC prevodníka rýchlosťou definovanou časovačom. Polia sú dve, počas presunu prvého sa naplní druhé a uvolní sa riadenie pre MicroPython.
void DMA_Update(void){ // callback funkcia z DMA prerusenia TC (Transfer Complete) uint16_t index; LL_DMA_DisableChannel(DMA1, LL_DMA_CHANNEL_3); if(buffer_select == 0){ // vyber buffra LL_DMA_SetMemoryAddress (DMA1, LL_DMA_CHANNEL_3, (uint32_t) &buffer_low); } else{ LL_DMA_SetMemoryAddress (DMA1, LL_DMA_CHANNEL_3, (uint32_t) &buffer_high); } LL_DMA_SetDataLength(DMA1, LL_DMA_CHANNEL_3, BUFFER_SIZE); LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_3); // obnovenie prenosu DMA for(index=0; index<buffer_size; index++){="" inicializacia="" nasledujuceho="" buffra="" phase_acc="phase_acc" +="" phase_incr;="" dds="" fazovy="" akumulator="" phase_addr="(phase_acc" &="" 0x01ff0000)="">> 16; // maska pre pole 512 poloziek tabulky - 9bit if(buffer_select == 0){ buffer_high[index]= (SineTable[phase_addr] * ampl) >> 8; } else{ buffer_low[index] = (SineTable[phase_addr] * ampl) >> 8; } } buffer_select = (buffer_select==0) ? 1 : 0; } void DMA1_Channel3_IRQHandler(void){ // Interrupt handler if( LL_DMA_IsActiveFlag_TC3(DMA1) !=0){ // Transfer complete DMA_Update(); LL_DMA_ClearFlag_TC3(DMA1); } if(LL_DMA_IsActiveFlag_TE3(DMA1) == 1){ // Transfer error } }
Pretože veľkosť modulu je väčšia, vlastný kód je dostupný na github-e [11]. Pri kompilácii postupujeme rovnako ako v predchádzajúcom príklade
make BOARD=NUCLEO_L476RG_EXP USER_C_MODULES=../../../modules CFLAGS_EXTRA=-DMODULE_DDS_ENABLED
natrafíme len na jeden problém, použitý IRQHandler je už rezervovaný v MiroPythone v súbore .ports/stm32/dma.c, kde ho je potrebné zakomentovať.
Pri inicializácii modulu zadáme parametre preddeličky a periódu čitača, metódou set nastavujeme inkrement fázy (ladenie generátora)
>>> import dds >>> dds.init(4,50) >>> dds.set(0x00004000) >>> dds.set(0x00028000)
Výsledný generátor nemá pochopiteľne parametre laboratórneho prístroja, na bežné aplikácie ale postačuje. Pri frekvencii hodín jadra procesora 80MHz (STM32L476RG)je generátor použiteľný do cca 150kHz s jitterom do 1Hz.
Frekvenčné spektrum signálu na vystupe bez výstupného filtra ukazuje odstup druhej a tretej harmonickej na úrovni –20dB, zaradením filtra sa vlastnosti generátora pochopiteľne výrazne zlepšia.
Elegantný vývoj IoT
Aplikácia MicroPython-u na súčasných mikrokontrolérich pokytuje elegantnú podporu vývoja IoT, v ktorom je implementovaná rutinná obsluha štandarných periférií na vysokej úrovni pomocou overených a odladených knižníc a vývojárovi umožňuje sústrediť svoj tvorivý potenciál na návrh a vývoj vlastného hardware, driveru, konverzie a spracovania dát. Zároveň ale umožňuje aj tvorbu modulov na nízkej úrovni, ktoré je možné použiť vo finálnom firmware.