Operační systém NuttX: programování s RTOS na embedded zařízeních

Dnes
Doba čtení: 16 minut

Sdílet

Open hardware SaMoCon s čipem SAMv71 používaný pro řízení motorů, na které právě běží NuttX .
Autor: Štěpán Pressl
NuttX je otevřený real-time operační systém nabízející POSIX kompatibilitu a podporu spousty malých a levných mikrokontrolérů. Ukážeme si, jaké má možnosti, jak ho nakonfigurovat a používat k embedded programování.

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
  1. Operační systémy pro embedded
  2. Příprava prostředí pro práci s NuttXem
  3. Konfigurace a kompilace
  4. Orientace v příkazové řádce NuttShellu
  5. Změna konfigurace
  6. Repozitář aplikací
  7. Blikání LED
  8. Organizace kódu v core repozitáři

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 drivers

Autor: Michal Lenc

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.

Školení Zabbix

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).

Autor článku

Vystudoval kybernetiku a robotiku na ČVUT FEL. Zabývá se vývojem real-time operačních systémů pro embedded zařízení, přispívá do jader systémů NuttX a občasně RTEMS.