====== 10. MBED a Nucleo ====== Cílem cvičení je vyzkoušet programování kitu Nucleo STM32F401RE v programovacím prostředí MBED. K jednoduchým experimentům postačí účet na webu [[https://os.mbed.com/|mbed.com]], pro vážnější práci lze využít např. Visual Studio Code s rozšířením PlatformIO, MBED IDE, nebo libovolné jiné podporované prostředí včetně pipeline v příkazové řádce. Více lze nalézt na dedikované [[courses:b2b99ppc:howtos:write_and_debug_stm32|stránce]]. ===== Kostra programu ===== V embedded světě je podobně jako ve světě větších počítačů, základem programu v C funkce ''main()''. Malý rozdíl je v tom, že tato funkce nezpracovává argumenty příkazové řádky (není odkud je vzít) a nebude pravděpodobně vracet žádnou návratovou hodnotu - jednak ji není kam vracet a navíc funkce ''main()'' by měla obsahovat nekonečnou smyčku. Přítomnost nekonečné smyčky je celkem logická - program mikrokontroléru by neměl nikdy skončit, pouze vykonává jednotlivé podprogramy, vyvolané např. přerušením. #include "mbed.h" int main() { // inicializace while (1) { // hlavní program } } ===== Vstupně / výstupní brána ===== Komunikace embedded prostředků s okolním světem je zajištěna pomocí vstupně / výstupní brány, v anglickém názvosloví ''GPIO'' (General Purpose Input Output) rozhraní, nad kterým mohou být postaveny další fyzické a transportní vrstvy (např. přístrojové sběrnice). Mikrokontrolér ''STM32F401'', který je srdcem kitu Nucleo, má pět 16b GPIO bran (A-E), jejichž jednotlivé piny (0-15) mohou pracovat v některém z následujících módů: * digitální vstup, popsán třídou [[https://os.mbed.com/docs/mbed-os/v5.15/apis/digitalin.html|DigitalIn]] * digitální výstup, popsán třídou [[https://os.mbed.com/docs/mbed-os/v5.15/apis/digitalout.html|DigitalOut]] * analogový vstup, popsán třídou [[https://os.mbed.com/docs/mbed-os/v5.15/apis/analogin.html|AnalogIn]] Zvolený pin může v daném čase pracovat pouze v jednom z těchto módů. Konfigurace prohěhne vytvořením instance třídy, parametrem konstruktoru je název pinu. Konfigurace nejnižšího pinu brány GPIOA pro digitální výstup proběhne takto: DigitalOut pin_a0(PA_0); Pro názvy jednotlivých pinů se používá několik různých konvencí, pro naše účely je nejvhodnější konzultace se stránkou [[https://os.mbed.com/platforms/ST-Nucleo-F401RE/#arduino-compatible-headers|kitu]]. Minimálně v počátcích našeho embedded programování si vystačíme s ''PA_5'', ke kterému je připojedna on-board LED dioda ''LED1'' a ''PC_13'', ke kterému je připojeno modré uživatelské tlačítko ''BUTTON1''. ===== Blinky LED ===== Blikající LED dioda je takový //Hello World!// embedded programování. Využijeme toho, že Nucleo disponuje LED diodou, která se dá programově ovládat a ukážeme si na příkladu, jak vypadá kostra programu. Abychom dosáhli správného efektu, budeme měnit logickou úroveň na pinu ''PA_5'', ke kterému je připojena dioda a mezi změnou stavu bude program čekat ve funkci [[https://os.mbed.com/docs/mbed-os/v5.15/apis/wait.html|wait()]]. #include DigitalOut led(LED1); int main() { while (1) { led = 1; wait(0.3); led = 0; wait(0.7); } } ===== Jednoduchá třída ===== Mbed může využívat principy objektového programování, které známe z C++. Následující příklad ukáže, tak vytvořit jednoduchou třídu, jejíž instance bude manipulovat se stavem pinu, jehož název ja parametrem konstruktoru. K dosažení zajímavého efektu se postupně zvětšuje délka mezi změnou bliknutí. class Blikac { DigitalOut pin; public: Blikac (PinName p = LED1) : pin (p) {} void blikej (int n) { for (int i = 0; i < n; i++) { blik (0.1 + (0.25/n) * i); } } void blik (float d = 0.5) { pin = 1; wait(d); pin = 0; wait(d); } }; Tuto třídu pak lze použít v hlavním programu int main() { Blikac led; while(1) { led.blikej(5); led.blik(); } } ===== Komunikace po sériové lince ===== Standardním způsobem komunikace s mikrokontroléry je asynchronní sériová linka - [[https://cs.wikipedia.org/wiki/USART|USART]]. Duplexní komunikace probíhá po dvou vodičích (pinech), které jsou připojeny na některé z HW rozhraní((Teoreticky je možné vytvořit sériové spojení z libovolných dvou pinů, např. Arduino může používat knihovnu [[https://www.arduino.cc/en/Reference/softwareSerial|SoftwareSerial]]; mikrokontroléry STM32 mají ovšem dostatek HW prostředků a není třeba se touto monžností zabývat.)). Pro využití sériové komunikace v Mbed je třeba vytvořit instanci třídy [[https://os.mbed.com/docs/mbed-os/v5.15/apis/serial.html|Serial]], jejíž konstruktor má jako parametry piny, po kterých probíhá komunikace. Při komunikaci přes debugovací rozhraní (USB připojení k Nucleu) lze využít předdefinovaná makra - ''SERIAL_TX'' pro odesílání a ''SERIAL_RX'' pro příjem, nebo alternatitvně ''USBTX'' a ''USBRX''. Serial pc(SERIAL_TX, SERIAL_RX); Pro správnou funkci asynchronního přenosového kanálu je klíčové, aby obě strany pracovaly stejně rychle. V zásadě je možné používat libovolné rychlosti (uváděné v bitech za vteřinu, nebo se používá jednotka [[https://cs.wikipedia.org/wiki/Baud|baud]] ve zkratce //Bd//), je ovšem vhodné používat s ohledem na doporučení [[https://www.itu.int/rec/T-REC-V.22-198811-I/en|ITU-T V.22]] rychlosti v hodnotách násobků 1200 Bd - 1200, 2400, 4800, 9600, atd((Maximální rychlost komunikace je ovlivněna kvalitou a délkou metalického spoje a dále také odezvou mikrokontroléru na přerušení. Obecně lze říci, že maximální používaná rychlost je 115200 Bd.)). Za nejobvyklejší hodnotu je všeobecně považována hodnota ''9600 Bd'', které je použita jako defaultní i v v případě Mbed. Změnit ji lze pomocí metody [[https://os.mbed.com/docs/mbed-os/v5.15/mbed-os-api-doxy/classmbed_1_1_serial_base.html#a9afb7aa9321cd71a8a26a673157583d2|Serial::baud]] pc.baud(19200); Pro komunikaci po ''USART'' implementuje Mbed podobné rozhraní, jako známe ze standardní knihovny ''C'': znakově orientované funkce ''putc'' a ''getc'', a funkce pro formátovaný vstup a výstup ''printf''. int a = 0; while(1) { pc.printf("uplynulo %d vterin\n\r", a++); wait(1); } ===== Přerušení ===== Nejjednoduším způsobem, jak pracovat s periferiemi mikrokontroléru, je pravidelné zjišťování jejich stavu - [[https://en.wikipedia.org/wiki/Polling_(computer_science)|polling]]. Tento způsob práce je sice jednoduchý, ale častokrát zbytečně plýtvá časem (např. čekací funkce, která po určený čas nedělá nic, nebo čekání na data přicházející po sériové lince) a prostředky. Je proto zaveden systém [[https://cs.wikipedia.org/wiki/P%C5%99eru%C5%A1en%C3%AD|přerušení]], ve kterém hlavní program (nekonečná smyčka) vykonává relativně málo činností (např. spí :-) a jednotlivé periferie dávají přerušovacímu systému dohodnutým způsobem vědět, že došlo k nějaké důležité události. V tu chvíli je běh hlavního programu přerušen, vykoná se podprogram svázaný s tímto přerušením a funkce hlavního programu je obnovena. V následujícím textu budou popsány tři zdroje přerušení, se kterými budeme pracovat. ==== Časovač ==== Časovač je vnitřní periferie mikrokontroléru, která může odměřovat čas nezávisle na stavu programu. Činnost časovače probíhá tak, že po inicializaci dochází s každým hodinovým impulsem k inkrementaci hodnoty hodnoty čítače až do chvíle, než dojde k dosažení maximální hodnoty a dojde k přetečení. V tu chvíli čítač vyvolá přerušení, které je možné zpracovat podprogramem. Časovač může pracovat v kontinuálním nebo single-shot režimu. Jednou z tříd, která popisuje časovač, je [[https://os.mbed.com/docs/mbed-os/v5.15/apis/ticker.html|Ticker]]. Časovač je propojen s funkcí pro obsluhu přerušení metodou [[https://os.mbed.com/docs/mbed-os/v5.15/mbed-os-api-doxy/classmbed_1_1_ticker.html#aecc275cbe328728bbee4d153e5b6b5b5|Ticker::attach()]], jejími argumenty jsou ukazatel na funkci a čas opakování ve vteřinách. Následující kód bude měnit stav LED diody ve vteřinových intervalech. Na rozdíl od funkce [[https://os.mbed.com/docs/mbed-os/v5.15/apis/wait.html|wait()]] může kontrolér v době, kdy nedochází ke změně stavu, vykonávat jiný kód. DigitalOut led(LED1); Ticker t; void obsluha() { led = !led; } int main() { t.attach(obsluha, 1); while(1) { // -- } } ==== Externí přerušení - I/O brána ==== GPIO umí vyvolat přerušení při změně logické úrovně na vstupním pinu. Navíc lze rozlišit, zda se jedná o změnu s vysoké úrovně na nízkou (sestupná hrana) nebo z nízké na vysokou (náběžná hrana). Následující kód ošetřuje obě varianty změny na vstupním pinu ''PC13'' (''USER_BUTTON''), navíc konfiguruje vstupní [[https://en.wikipedia.org/wiki/Pull-up_resistor|pull-up rezistor]], aby byla správně definována logická hodnota na vstupním pinu v případě, že tlačítko není připojeno na žádnou úroveň. Nucleo modrým tlačítkem pin ''PC13'' připojuje na ''GND'', proto je třeba definovat hodnotu v rozepnutém stavu rezistorem připojeným k napájecímu napětí, navíc to znamená, že stisknutí tlačítka vytváří sestupnou hranu. DigitalOut led(LED1); InterruptIn button(USER_BUTTON); void press () { led = 1; } void release () { led = 0; } int main() { button.mode(PullUp); // definice rozepnunteho stavu - pull up resistor button.fall(release); button.rise(press); while(1) { // -- } } ==== Sériová komunikace ==== Použití funkce scanf při práci se sériovým kanálem zablokuje běh programu, což je nevýhodné zejména pokud data přicházejí v nepravidelných intervalech. Naštěstí může ovladač sériového komunikačního kanálu také vyvolat přerušení a to v případě, že se ve vstupním bufferu objeví data, která je možné číst. Následující příklad ukazuje, jak propojit periferii s podprogramem pro obsluhu přerušení. Zajímavé je, jak se program chová: za normálních okolností je hlavní program v nekonečné smyčce, pokud přijde byte po sériové lince, zavolá se poprogram a tam čeká funkce scanf, dokud nebude ukončen přenos, znakem ''0x0D'' (Carriage Return - Enter). Serial pc (USBTX, USBRX); void obsluha () { int a; pc.scanf("%d", &a); pc.printf("a = %d", a); } int main() { pc.attach(obsluha); while(1) { // -- } }