Ve světě embedded zařízení existuje několik možností, jak mikrokontrolér programovat. Tou nejzákladnější je bare metal programování, kdy z kódu, nejčastěji psaném v jazyku C, přímo ovládáme jednotlivé periferie. Tento přístup nám dává plnou kontrolu nad zařízením, ale znamená více práce a složitější kód, jelikož si vše musíme psát sami. Pomoci v tomto ohledu mohou HAL knihovny, které často poskytují sami výrobci mikrokontrolérů. Jde o knihovny, které práci s periferiemi implementují za nás a poskytují jednoduší rozhraní formou API funkcí (například přečtení hodnot z převodníku či poslání zprávy přes SPI). Díky tomu se můžeme soustředit jen na psaní aplikace.
Co se dozvíte v článku
Nevýhodou spoléhání se na HAL je závislost na knihovnách třetích stran, které jsou často těžko editovatelné. Různé HALy také poskytují různé funkcionality, přechod mezi mikrokontroléry různých výrobců pak znamená nutnost využít jiné knihovny s nekompatibilním API a tím pádem bolehlav při portování aplikací.
Operační systémy pro embedded
Alternativou je využití operačních systémů navržených pro embedded zařízení, často též disponujícími vlastnostmi pro real-time systémy a označovanými jako RTOS (real-time operating system). Ty obvykle nabízejí kombinaci HAL knihoven (vlastních implementací či převzatých od výrobců), jednotného API pro práci s periferiemi a různých užitečných funkcionalit jako souborové systémy, konzole či networking stack.
Jedním takovým operačním systémem reálného času je i NuttX. Ten byl publikován v roce 2007 Greogory Nuttem (z jeho příjmení vychází název). Původně byl publikován pod BSD licensí, ale postupně v rámci zařazení pod The Apache Software Foundation přelicencován pod Apache License 2.0. Velikou výhodou NuttXu je téměř plná kompatibilita s POSIX API, zároveň se snaží udržet kompatibilitu s GNU/Linux implementací funkcionalit epoll či inotify. To umožňuje z velké části provádět vývoj aplikací na linuxovém počítači s využitím standardních debugovacích nástrojů – ovšem pozor, kompatibilita není pravidlem v případě mnoha periferií, jak si později ukážeme.
NuttX disponuje podporou širokého množství architektur (ARM, RISC-V, Xtensa, AVR), mikrokontrolérů a vývojových kitů. Obecně cílí spíše na menší, levnější a jednojádrové mikrokontroléry. Je ovšem potřeba dávat pozor, jelikož ne pro všechny mikrokontroléry je implementována podpora pro všechny periferie. NuttX totiž nevyužívá HALy třetích stran (i zde je výjimka, podpora pro RISC-V varianty ESP32 využívá knihovny od Espressifu), nýbrž si knihovny implementuje sám. To na jednu strany zlepšuje přehlednost kódu, na druhou stranu můžou některé ovladače chybět. Obecně platí, že velmi dobrá podpora je pro mikrokontroléry architektur ARM a RISC-V.
Operační systém je také široce konfigurovatelný, z linuxového jádra si vypůjčil Kconfig syntaxi a umožňuje vypnout velké množství funkcionalit, které nejsou pro danou aplikaci potřeba. Díky tomu není problém dostat minimální build na cca 32 kB RAM a 32 kB flash (v případě flash to ale často může znamenat absenci shellu). Jde se dostat i na nižší hodnoty, ale již by šlo o výrazně osekaný systém. Také je ale možné nakonfigurovat množství funkcionalit, jako jsou síťový stack s TCP/IP, DHCP podpora, telnet, souborové systémy (FAT, LittleFS) či třeba shell jako aplikaci v rámci jedné binárky. NuttX shell, neboli NuttShell, je pak možné použít třeba pro monitorování a správu procesů, debugování, správu souborů či nastavení sítě. Jeho jednoduchou verzi si lze vyzkoušet v online demu.
Příprava prostředí pro práci s NuttXem
Pro konfiguraci a kompilaci operačního systému si prvně musíme nainstalovat potřebné balíčky. Ty jsou uvedeny v dokumentaci projektu. Veškeré příklady budou na desce NUCLEO-L476RG s architekturou ARM, toolchain ke kompilaci lze tedy též nainstalovat podle kroků z odkazované dokumentace. Většinu ukázek a příkladů lze provádět i na jiné NuttXem podporované desce než na zmíněném Nucleu, typicky by se měly akorát lišit piny či cesta k souborům. Pro programování desky se pak hodí OpenOCD (kromě instalace může být na některých systémech i potřeba správně nastavit udev rules). NuttX se skládá ze dvou hlavních repozitářů – jádro systému a aplikace. Můžeme si je následujícími příkazy naklonovat do adresáře nuttx.
mkdir nuttx cd nuttx git clone https://github.com/apache/nuttx.git core git clone https://github.com/apache/nuttx-apps apps
Repozitář core obsahuje jádro operačního systému, ovladače a BSP, apps jsou podpůrné aplikace – včetně shellu.
Konfigurace a kompilace
NuttX používá pro svou konfiguraci nástroj Kconfig. Aby nebylo potřeba vytvářet pokaždé konfiguraci od nuly, disponuje každá podporovaná deska několika výchozími konfiguracemi, ze kterých lze vycházet. Pro načtení těchto konfigurací disponuje NuttX skriptem. Můžeme ho spustit s přepínačem -L, čímž si vypíšeme všechny dostupné konfigurace. Pozor, veškerá práce teď bude probíhat v repozitáři core.
cd core ./tools/configure.sh -L
Pomocí grep můžeme zobrazit jen konfigurace pro svou desku nucleo-l476rg.
./tools/configure.sh -L | grep nucleo-l476rg nucleo-l476rg:nsh nucleo-l476rg:nxdemo
Výsledkem by měly být dvě dostupné konfigurace. Většina podporovaných desek bude obsahovat konfiguraci nazvanou nsh poskytující základní systém (ne vždy minimální velikosti, jak uvidíme za chvíli) a konzoli typicky dostupnou na sériovém portu.
Výše zmíněný skript pak znovu použijeme pro nastavení konfigurace.
./tools/configure.sh nucleo-l476rg:nsh
Výstupem by měl být soubor .config se všemi nastavenými a zakázanými možnostmi z nsh konfigurace. Nyní můžeme systém zkompilovat pomocí příkazu make.
$ make
Create version.h
LN: platform/board to /path/to/nuttx/apps/platform/dummy
Register: rand
Register: dd
Register: ostest
Register: nsh
Register: sh
Register: alarm
CC: chip/stm32l4_idle.c chip/stm32l4_gpio.c:46:11: note: '#pragma message: CONFIG_STM32L4_USE_LEGACY_PINMAP will be deprecated migrate board.h see tools/stm32_pinmap_tool.py'
46 | # pragma message "CONFIG_STM32L4_USE_LEGACY_PINMAP will be deprecated migrate board.h see tools/stm32_pinmap_tool.py"
| ^~~~~~~
CPP: /home/michal/Michal/projects/nuttx/core/boards/arm/stm32l4/nucleo-l476rg/scripts/l476rg.ld-> /home/michal/Michal/projects/nuttx/corLD: nuttx
Memory region Used Size Region Size %age Used
flash: 225996 B 512 KB 43.11%
sram: 9572 B 96 KB 9.74%
CP: nuttx.hex
CP: nuttx.bin
Výstupem jsou .hex a .bin soubory se zkompilovaným operačním systémem NuttX a shellovou aplikací NSH v rámci jedné binárky. Za zmínku stojí i přes 220 kB využité flash paměti (velikost kódu). To je výrazně víc oproti výše uvedeným 32 kB, konfigurace ale navíc obsahuje větší aplikace jako ostest (aplikace testující různé funkcionality systémy) a dema rand či alarm. Jejich zakázáním a zapnutím optimalizace bychom se byli rychle schopen dostat na cca třetinu potřebné paměti. Další velké snížení by přineslo vypnutí shell konzole, pro svou demonstraci ji ale budeme potřebovat.
Nyní nám již nebrání v tom NuttX do desky nahrát. Nucleo desky se po připojení kabelem k PC typicky namountují jako mass storage device, nový firmware tak lze jednoduše nakopírovat pomocí příkazu cp.
cp nuttx.bin /media/.../NOD_L476RG
Univerzálnější přístup je vyžití nástroje OpenOCD, který je dostupný jako balíček v rámci většiny linuxových distribucí. Kromě nahrávání nového firmwaru jde použít také k připojení se k desce za účelem debugování pomocí nástroje gdb. Jak takto debugovat NuttX na desce si ukážeme později.
openocd -f interface/stlink-v2.cfg -f target/stm32l4x.cfg -c "program nuttx.bin 0x08000000 reset exit"
Přepínač -c ve výše uvedeném příkazu nám umožní říct, co za příkazy má OpenOCD po připojení k desce vykonat. V našem případě použijeme pomocný skript program, který nahraje soubor nuttx.bin do flash paměti na adresu začínající 0×08000000, provede reset desky a odpojí se od ni.
Pokud bychom chtěli konfiguraci zahodit a nakonfigurovat například jinou desku, lze tak učinit přes příkaz make distclean. Pokud bychom si naopak chtěli konfiguraci uložit, můžeme zavolat make savedefconfig, který nám v kořenovém adresáři vygeneruje soubor defconfig.
Analogicky bychom mohli NuttX nakonfigurovat jako simulátor běžící na našem počítači. Pokud bychom neměli po ruce žádnou desku podporující NuttX, lze zkusit nakonfigurovat a spustit sim:nsh konfiguraci.
./tools/configure.sh sim:nsh
Výsledkem kompilace je na našem počítači spustitelná binárka nuttx v adresáři nuttx/core
Orientace v příkazové řádce NuttShellu
V naší základní konfiguraci nsh je konzolová aplikace NuttShell dostupná na sériovém portu, který je na desce NUCLEO-L476RG vyveden na konektor USB mini a typicky hlásí jako CDC ACM device. Ke konzoli se můžeme jednoduše připojit například pomocí aplikace tio či jiného terminálového emulátoru. Baudrate komunikace je v NuttXu v základu nastaven na 115200. Po připojení zmáčknutí klávesy enter by se měl objevit nový řádek s prefixem nsh>.
$ tio /dev/ttyACM0 [15:40:22.893] tio 3.9 [15:40:22.893] Press ctrl-t q to quit [15:40:22.893] Connected to /dev/ttyACM0 nsh>
Nápovědu všech dostupných příkazů vyvoláme posláním znaku ? či textu help.
nsh> ?
help usage: help [-v] []
. cd exit mount sleep umount
[ cp expr mv usleep unset
? cmp false printf source uptime
alias dirname help pwd test watch
unalias date hexdump rm time xd
basename dmesg ls rmdir true
break echo mkdir set truncate
cat exec mkrd kill uname
Builtin Apps:
alarm dd nsh ostest rand sh
nsh>
Příkazem ls dev si můžeme vypsat všechny ovladače znakových zařízení (character device) zaregistrované v /dev.
nsh> ls /: dev/ nsh> ls dev /dev: console null random rtc0 ttyS0 zero
Změna konfigurace
Pokud bychom chtěli vyzkoušet příkazy jako ps nebo free, narazily bychom na problém, že nejsou v momentální konfiguraci (platí pro NUCLEO-L476RG, na jiných nsh konfiguracích fungovat mohou) podporované.
nsh> ps nsh: ps: command not found
Důvodem je chybějící souborový systém procfs, což si můžeme jednoduše ověřit chybějícím adresářem /proc, pokud v kořenovém adresáři konzole zavoláme příkaz ls. Pokud chceme systém jednoduše překonfigurovat, můžeme zavolat make menuconfig nebo grafické make qconfig. Druhá aplikace má výhodu, že v ní lze jednodušeji vyhledávat, pokud známe přesný název volby. Po spuštění make menuconfig se v terminálu otevře nástroj, který nám dovolí systém konfigurovat.
Pro povolení souborového systému procfs vstoupíme do sekce File Systems, najdeme PROCFS File System a stisknutím klávesy y volbu povolíme. Znovu zakázat ji lze stiskem n. Případně jde o volbu s názvem CONFIG_FS_PROCFS, pokud bychom chtěli použít vyhledávání. Podobným způsobem lze mimochodem povolit i jiné podporované souborové systémy, například FAT FS, LittleFS či SmartFS – ten je v NuttXu využíván jako souborový systém pro paměti NOR.
Díky nastavení volby CONFIG_FS_PROCFS se zkompiluje kód potřebný k provozování procfs a BSP kód ho inicializuje (toto není pravidlem, ale k tomu se dostaneme později). Nyní lze opět příkazy make a openocd NuttX zkompilovat a nahrát. Můžeme si všimnout, že povolením procfs se nám zvětšila velikost kódu zhruba o 20 kB. Po zavolání ls již vidíme adresář /proc a měly by nám fungovat nové příkazy.
nsh> ps
TID PID PPID PRI POLICY TYPE NPX STATE EVENT SIGMASK STACK COMMAND
0 0 0 0 FIFO Kthread - Ready 0000000000000000 0001000
1 1 0 100 RR Task - Running 0000000000000000 0002000
nsh> free
total used free maxused maxfree nused nfree name
120468 5956 114512 6336 87184 27 2 Umem
nsh>
Dále si zkusíme nakonfigurovat demo aplikaci Hello World! (volba CONFIG_EXAMPLES_HELLO). Dema a příklady najdeme v menuconfigu v Application Configuration (úplně dole) v sekci Examples. Chceme povolit „Hello, World!“ example. Po jeho zaškrtnutí se nám objeví další možnosti konfigurace – můžeme nastavit jméno programu, jeho prioritu (pozor, vyšší číslo zde znamená vyšší prioritu) nebo velikost stacku pro aplikaci. Opět zkompilujeme, nahrajeme a zkusíme aplikaci spustit příkazem hello.
nsh> hello Hello, World!!
Repozitáře NuttXu disponují velkým množstvím demo aplikací a příkladů, ze kterých se lze inspirovat při tvorbě vlastních aplikací.
Repozitář aplikací
Prozatím jsme všechny operace prováděli v core repozitáři NuttX. Ten obsahuje jádro operačního systému a jeho základní komponenty včetně podpory architektur, mikrokontrolérů a desek. Při přípravě prostředí jsme ale kromě tohoto repozitáře klonovali i repozitář apps. V něm, jak již z názvu vyplývá, najdeme aplikace, mezi které vlastně patří i implementace konzole NuttShell. Na cestě apps/examples/hello pak najdeme zdrojový kód minule vyzkoušené aplikace hello.
michal@desktop:~/path/to/nuttx/apps$ ls examples/hello CMakeLists.txt hello_main.c Kconfig Make.defs Makefile
Můžeme se podívat na zdrojový kód hello_main.c. Vidíme, že se téměř v ničem neliší od kódu, který bychom napsali pro linuxovou aplikaci.
#include <nuttx/config.h>
#include <stdio.h>
int main(int argc, FAR char *argv[])
{
printf("Hello, World!!\n");
return 0;
}
Jediný rozdíl je řádek #include <nuttx/config.h>, který zajišťuje include kconfig maker CONFIG_ a tedy dovoluje v aplikaci měnit chování v závislosti na konfiguraci. V našem příkladu ale žádné CONFIG_ makra nepoužíváme, mohli bychom tak tento include klidně smazat. Aplikace by pak mohla být beze změny spuštěna i na Linuxu.
Blikání LED
V dalším příkladu se již podíváme na práci s hardwarem. Deska NUCLEO-L476RG má na sobě zelenou LED označenou jako LD2, kterou můžeme z procesoru řídit přes digitální výstup. Zde bude konfigurace již trochu náročnější, narážíme totiž na první problém kombinace Kconfig a NuttXu — některé konfigurační volby si navzájem odporují a v dokumentaci nejsou tyto situace dostatečně dobře popsány.
Nejprve potřebujeme v konfiguraci zakázat volbu Board LED Status Support ( CONFIG_ARCH_LEDS) v Board Selection sekci a následně v Device Drivers → Led support LED driver ( CONFIG_USERLED) a Generic Lower Half LED Driver ( CONFIG_USERLED_LOWER). Těmito změnami v konfiguraci si zajistíme přítomnost low level ovladače pro diody.
Demo aplikaci povolíme volbou CONFIG_EXAMPLES_LEDS, případně v konfiguraci v aplikacích stejně jako u Hello world najdeme volbu LED driver example. Nyní opět zkompilujeme a nahrajeme NuttX. V adresáři /dev bychom měli nově vidět zaregistrovaný ovladač userleds a v dostupných aplikacích novou aplikaci leds. Můžeme ji spustit stejně, jako jsme spouštěli příklad s hello world. Výsledkem by měla být blikající zelená dioda LD2.
Zdrojový kód demo aplikace již bude zajímavější než v případě klasického Hello world. Najdeme ho v adresáři apps/examples/leds. Vidíme, že s aplikace spustí druhý task a hlavní ukončí, díky čemuž automaticky neblokuje terminál. Zajímavý je pro nás hlavně kód ve funkci led_daemon. V něm se použije POSIX volání sigaction pro zaregistrování signálu SIGTERM, který bychom z terminálu přes příkaz kill mohli použít k ukončení tasku. Otevření ovladače zařízení proběhne pomocí standardního volání open.
printf("led_daemon: Opening %s\n", CONFIG_EXAMPLES_LEDS_DEVPATH);
fd = open(CONFIG_EXAMPLES_LEDS_DEVPATH, O_WRONLY);
if (fd < 0)
{
int errcode = errno;
printf("led_daemon: ERROR: Failed to open %s: %d\n",
CONFIG_EXAMPLES_LEDS_DEVPATH, errcode);
goto errout;
}
K ovládání LEDek se využijí volání ioctl. Jednotlivé příkazy IOCTL samozřejmě kompatibilní s Linuxem nebudou. To platí i pro další periferie, které v NuttXu používáme — většinou bývají implementované přes volání ioctl či zápisy/čtení struktur specifických pro NuttX. To sice narušuje absolutní kompatibilitu s kódem psaným pro Linux, ale dává možnost mít API lépe navržené pro přístup k periferiím potřebným v embedded programování.
printf("led_daemon: LED set 0x%02x\n", (unsigned int)ledset);
ret = ioctl(fd, ULEDIOC_SETALL, ledset);
if (ret < 0)
{
int errcode = errno;
printf("led_daemon: ERROR: ioctl(ULEDIOC_SUPPORTED) failed: %d\n",
errcode);
goto errout_with_fd;
}
usleep(500 * 1000L);
Organizace kódu v core repozitáři
K hlubšímu pochopení, jak ovladače zařízení v NuttXu fungují, se musíme podívat zpátky do repozitáře core. V něm nás budou zajímat tři adresáře — arch, boards a drivers.
NuttX rozděluje implementaci ovladačů na dvě vrstvy, často nazývané upper (vyšší) a lower (nižší). Nižší vrstva je blíž samotnému hardwaru a implementuje ovladače specifické pro daný mikrokontrolér. Zdrojové kódy této části najdeme v adresáři arch. Znamená to, že každý mikrokontrolér má svojí vlastní implementaci ovladačů, přestože některé mohou být napříč platformami hodně podobné či dokonce úplně stejné. Přináší to lepší přehlednost zdrojového kódu optimalizovaného pro daný mikrokontrolér, zároveň je ale tím pádem zdrojový kód často duplikován mezi různými mikrokontroléry. Nižší vrstva ovladačů obsahuje implementaci nastavení periferií, zápisy a čtení registrů či zpracování přerušení (interrupt handling).
Vyšší vrstva naopak implementuje obecnou část ovladače a jeho API, tedy funkce jako open, write či ioctl. Ve výsledku tak nižší vrstvy implementované pro různé mikrokontroléry využívají jednu společnou vyšší vrstvu, se kterou pak interaguje aplikace. Díky tomuto designu je zajištěno jednotné API napříč různými platformami. Implementaci této vrstvy najdeme v adresáři drivers.
NuttX nemá podporu pro device tree, veškerá inicializace, respektive volání inicializačních funkcí vyšší a nižší vrstvy, tak probíhá ve třetím zmíněném adresáři, kterým je boards. V ní je implementována podpora pro desky a vývojové kity. Jde víceméně o definici použitých pinů a volání inicializačních funkcí.
Pojďme se podívat, jak takové rozdělení vypadá prakticky v případě ovladačů na blikání LED.
drivers
Implementaci obecného ovladače LED najdeme v drivers/leds, kde nás zajímá soubor userled_upper.c. Všimněte si, že v jeho úvodu jsou definované standardní souborové operace, které funkce userled_register zaregistruje jako ovladač znakového zařízení (character device driver).
static const struct file_operations g_userled_fops =
{
userled_open, /* open */
userled_close, /* close */
NULL, /* read */
userled_write, /* write */
NULL, /* seek */
userled_ioctl, /* ioctl */
};
Tato funkce má také jako jeden ze vstupních parametrů strukturu userled_lowerhalf_s, která poskytuje volání do nižší vrstvy ovladače. Pokud totiž zavoláme ioctl nastavující LED na určitou hodnotu, musíme z vyšší vrstvy tuto informaci nějak předat nižší vrstvě, která vykoná zápis do registru či nastavení pinu GPIO. Právě k tomu slouží ukazatele na funkce nižší vrstvy předané v struktuře při inicializaci. Na zavolání funkce nižší vrstvy z vrstvy vyšší se koneckonců můžeme podívat, najdeme ho ve funkci userled_ioctl.
case ULEDIOC_SETLED:
{
FAR struct userled_s *userled = (FAR struct userled_s *)
((uintptr_t)arg);
int led;
bool ledon;
/* Verify that a non-NULL pointer was provided */
if (userled)
{
led = userled->ul_led;
ledon = userled->ul_on;
/* Check that a valid LED is being set */
if ((size_t)led < 8 * sizeof(userled_set_t) &&
(priv->lu_supported & (1 << led)) != 0)
{
/* Update the LED state */
if (ledon)
{
priv->lu_ledset |= (1 << led);
}
else
{
priv->lu_ledset &= ~(1 << led);
}
/* Set the LED state */
lower = priv->lu_lower;
DEBUGASSERT(lower != NULL && lower->ll_setled != NULL);
lower->ll_setled(lower, led, ledon);
ret = OK;
}
}
}
break;
Implementaci nižší vrstvy bychom normálně našli v adresáři arch, v případě LED ovladače je zde ale výjimka. Ovládání diod totiž není z pohledu mikrokontroléru nic jiného než nastavení výstupních pinů procesorů do logické jedničky či nuly. Implementace je tak vlastně stejná napříč všemi mikrokontroléry. Díky tomu najdeme v adresáři i soubor userled_lower.c implementující nižší vrstvu ovladače (vzpomeňme si, že jsme tuto volbu dříve povolili v konfiguraci).
V upper kódu výše je proměnná lower ukazatel na proměnnou g_userled_lower typu struct userled_lowerhalf_s ze souboru userled_lower.c:
static const struct userled_lowerhalf_s g_userled_lower =
{
.ll_supported = userled_supported,
.ll_setled = userled_setled,
.ll_setall = userled_setall,
};
Vyšší vrstva v případě příkazu ULEDIOC_SETLED volá funkci ll_setled z nižší vrstvy. Volání g_userled_lower.ll_setled se tedy propíše na volání userled_setled ze stejného souboru. Ta je ve výše zmíněném souboru definována následovně.
static void userled_setled(FAR const struct userled_lowerhalf_s *lower,
int led, bool ledon)
{
board_userled(led, ledon);
}
Definici funkce board_userled najdeme v adresáři boards.
boards
Pro naši desku NUCLEO-L476RG jde konkrétně o cestu boards/arm/stm32l4/nucleo-l476rg/. Zde v podadresáři config najdeme všechny dostupné konfigurace, v src zase zdrojové kódy. Většina periferií je inicializována z funkce bringup v souboru stm32_bringup.c. Zde najdeme i inicializaci našeho LED ovladače.
#ifdef CONFIG_USERLED
/* Register the LED driver */
ret = userled_lower_initialize("/dev/userleds");
if (ret < 0)
{
syslog(LOG_ERR, "ERROR: userled_lower_initialize() failed: %d\n", ret);
}
#endif
Ještě nás zajímá soubor stm32_userleds.c, který implementuje samotné zapínání/vypínání LEDky, tedy funkce jako board_userled. Jak je vidět, nejde vlastně o nic jiného než o nastavení výstupního pinu.
void board_userled(int led, bool ledon)
{
if (led == BOARD_LD2)
{
stm32l4_gpiowrite(GPIO_LD2, ledon);
}
}
Obecnou nižší vrstvu, kterou jsme si představili, u jiných periferiích v kódu NuttX úplně často nenajdeme. Používá se ještě u ovladačů GPIO, ale jinak je nižší vrstva implementována pro každý mikrokontrolér separátně v sekci arch. Princip fungování ale zůstává stejný.
V příštím díle se podíváme na další periferie NuttXu. Podrobně si ukážeme, jak z aplikace pracovat s analogově-digitálním převodníkem (ADC), pulzně šířkovou modulací (PWM) a jak se ovládají vstupní a výstupní piny (GPIO).
