Aan de slag met Zephyr RTOS
Zephyr is schaalbaar, en daardoor bruikbaar voor een breed scala aan microcontrollers met al hun beperkingen op het gebied van resources. Deze schaalbaarheid wordt bereikt door een modulaire architectuur waardoor ontwikkelaars alleen die onderdelen die ze nodig hebben hoeven toe te voegen om de grootte van het systeem te beperken. De Zephyr-website stelt dat het systeem kan werken op systemen met slechts 8 KB geheugen, maar schaalbaar is tot geheugens van meerdere Gigabytes.
Brede hardware-ondersteuning
Zephyr ondersteunt een breed scala architecturen zoals ARM, x86, RISC-V, Xtensa, MIPS en meer. Ook FPGA’s worden ondersteund met de Nios2- en MicroBlaze-softcores. Op het moment van schrijven kent Zephyr meer dan 600 bruikbare boards waaronder Arduino UNO R4 Minima, GIGA R1 WIFI en Portenta H7, diverse uitvoeringen van de ESP32, beide versies van de BBC micro:bit, Raspberry Pi Pico (en zelfs Raspberry Pi 4B+), nRF51- en nRF52-boards, de NXP MIMXRT1010-EVK familie en de STM32 Nucleo- en Discovery-families. En dat zijn alleen nog de bekendste namen die regelmatig in Elektor voorbijkomen, er zijn er nog een heleboel meer.
Naast processorboards ondersteunt Zephyr ook veel uitbreidingsboards (shields) en zijn er drivers beschikbaar voor allerhande interfaces en meer dan 150 sensoren.
Multitasking, netwerken en energiebeheer
Een real-time operating system (RTOS) zoals Zephyr biedt mogelijkheden zoals preemptive multitasking, inter-thread communicatie en real-time klok-ondersteuning. Het OS heeft verder ondersteuning voor netwerktechnologieën en -protocollen zoals TCP/IP, Bluetooth en IEEE 802.15.4 (zoals gebruikt in Zigbee), MQTT, NFS en LoRaWAN. Deze netwerktechnologieën, samen met krachtige power management-technieken in Zephyr, maken het geschikt voor energiezuinige IoT-toepassingen en batterijgevoede apparatuur.
De beschikbare bibliotheken en middleware maken het eenvoudig om veelvoorkomende functies zoals communicatieprotocollen, bestandssystemen en apparaatdrivers te implementeren.
Daarnaast is Zephyr ontwikkeld in overeenstemming met met beveiligingscertificaten zoals ISO 26262, waardoor het ook toepasbaar is in toepassingen die een goede beveiliging vereisen.
Geïnspireerd door Linux
Zephyr is geen Linux, maar het maakt wel gebruik van concepten, technieken en tools die ook in Linux toegepast worden. Kconfig wordt bijvoorbeeld gebruikt om het OS te configureren, en hardware-eigenschappen en -configuraties worden vastgelegd volgens de Device Tree Specificatie (DTS). Daarom zullen Linux-ontwikkelaars zich snel thuis voelen als ze met Zephyr aan de slag gaan.
Open-source
Last but not least is Zephyr vrijgegeven onder de Apache 2.0-licentie die zowel commerciële als niet-commerciële toepassingen toelaat. De gebruikersgemeenschap biedt ondersteuning en documentatie. Je kunt daar ook aan bijdragen.
Zephyr uitproberen
Zephyr uitproberen heeft meerdere jaren op mijn to-do lijst gestaan, maar mijn eerste ervaringen waren niet erg bemoedigend waardoor ik toen niet verder gegaan ben. Destijds was een van de grote problemen (naast het OS überhaupt zonder foutmeldingen te kunnen compileren) dat er een programmeermodule nodig was om de targetprocessor te programmeren, waardoor het minder toepasbaar was voor hobbyisten en semi-professionals. Dankzij Arduino en zijn bootloader zijn we gewend geraakt aan het werken zonder speciale programmeerhardware waardoor deze eis aanvoelde als een flinke stap terug.
Keuze van een board
De ontwikkelingen hebben sinds die tijd niet stilgestaan. Zoals al opgemerkt ondersteunt Zephyr inmiddels meer dan 600 microcontroller boards en er is dus een goede kans dat je al een of meer compatibele boards beschikbaar hebt. Bij het bestuderen van de lijst ontdekte ik dat ik ruim een dozijn verschillende bruikbare boards had liggen.
Leve de BBC micro:bit!
Ik heb de meeste ervan geprobeerd en heb uiteindelijk de BBC micro:bit gekozen voor mijn experimenten (figuur 1, in Zephyr bekend als bbc_microbit of bbc_microbit_v2, afhankelijk van de versie van het board). In vergelijking met andere boards heeft de BBC micro:bit, naast dat ik er een had liggen, het voordeel dat het misschien wel de beste Zephyr-ondersteuning heeft, wat betekent dat drivers voor alle interfaces op het board beschikbaar zijn en ondersteund worden met voorbeelden. En misschien wel het belangrijkst: programmeren en debuggen kan zonder extra hardware of software.
De populaire ESP-WROOM-32 (in Zephyr bekend als esp32_devkitc_wroom) is ook een geschikte kandidaat, maar debuggen vereist wel een extern tool. Arduino GIGA R1 WIFI zou ook een goede keuze zijn maar daarvan wordt de standaard bootloader overschreven als Zephyr gebruikt wordt. Dat kan weliswaar hersteld worden, maar is wat mij betreft een ongewenst bijverschijnsel. Arduino UNO R4 Minima heeft (net als veel andere boards, waaronder de Raspberry Pi Pico), officieel een SWD-programmeermodule nodig, maar ik vond een workaround door dfu-util te gebruiken (zie verderop). Net als bij de GIGA R1 wordt ook hier de Arduino bootloader overschreven door Zephyr.
...of gebruik een emulator
Als je geen bruikbaar board bij de hand hebt en je toch graag Zephyr wilt proberen, dan is de ingebouwde emulator-ondersteuning voor QEMU een mogelijkheid (alleen beschikbaar op Linux/macOS). Hiermee kun je toepassingen virtueel uitvoeren en testen. Renode van Antmicro werkt vergelijkbaar, maar ik heb dat niet uitgeprobeerd.
Zephyr Installeren
Ik heb Zephyr geïnstalleerd op een computer met Windows 11 – ik heb Linux en macOS niet geprobeerd. Uitgebreide instructies voor de installatie zijn online beschikbaar op . De benodigde stappen worden duidelijk aangegeven en verdere uitleg is niet nodig. Ik gebruikte een virtuele Python-omgeving zoals aanbevolen. Dat betekent wel dat je het commando om de virtuele omgeving te activeren zult moeten noteren omdat je het elke keer dat je de software wilt gebruiken nodig hebt. Bij gebruik van Windows PowerShell voer je het script activate.ps1 uit; onder Command Prompt is dit het activate.bat batchbestand. Windows PowerShell is beter in het verwerken van compiler- en linker-uitvoer (figuur 2).
Zephyr bestaat uit twee delen – het OS zelf en een SDK (Software Development Kit) met daarin een verzameling MCU-toolchains (21 op het moment van schrijven). Het OS en de SDK hoeven niet op dezelfde locatie geïnstalleerd te zijn. In totaal nam het pakket bij mij ongeveer 12 GB ruimte in beslag op de harde schijf. Door toolchains die je niet gebruikt te verwijderen kunt je wat ruimte vrijmaken.
Na de installatie kun je proberen of alles werkt door een van de voorbeelden te compileren en dit naar je board over te brengen met het onderstaande commando. Vervang daarbij <my_board> door de naam van het board dat je gebruikt, bijvoorbeeld arduino_uno_r4_minima:
west build -p always -b <my_board> samples/basic/blinky
Als je het pad naar het voorbeeld niet wilt aanpassen, moet je het commando uitvoeren in de map waar het staat (waarbij (.venv) aangeeft dat je in een virtuele omgeving werkt):
(.venv) <my_path_to_zephyr>\zephyrproject\zephyr
Als het voorbeeld zonder foutmeldingen compileert, kun je het naar je board overbrengen met:
west flash
waarna de ‘default’ LED op je board begint te knipperen met een frequentie van 0,5 Hz.
Zoals eerder opgemerkt heb je voor het flashen mogelijk een externe programmeermodule zoals een J-Link-adapter of een vergelijkbare (JTAG- of SWD-compatibele) programmer nodig (figuur 3) waarbij de bijbehorende driversoftware toegankelijk moet zijn (dat wil zeggen in het zoekpad – zoals %PATH% in Windows). Als dit niet het geval is, zul je een foutmelding krijgen (maar die zijn vaak lang en nogal cryptisch).
Op de BBC micro:bit V2 moest ik de eerste keer het HEX-bestand met de hand naar het board kopiëren volgens de standaard micro:bit-procedure. Daarna werkte de flash-methode prima. Het bestand zephyr.hex is te vinden in zephyrproject\zephyr\build\zephyr\.
Het standaard flash-commando voor de Arduino-boards UNO R4 Minima en GIGA R1 WIFI vereist dat het dfu-util hulpprogramma te vinden is in het zoekpad van het OS (voordat je de virtuele omgeving start, als je die gebruikt). Dit hulpprogramma is te vinden in de Arduino IDE, maar waar dit precies op je computer staat, zul je zelf moeten uitzoeken. (Standaard staat het in %HOMEPATH%\AppData\Local\Arduino15\packages\arduino\tools\dfu-util\<je meest recente Arduino versie>). Het board moet daarna eerst in DFU-modus gezet worden door twee keer snel na elkaar op de resetknop te drukken. Als de LED begint te ‘pulseren’ of te ‘ademen’ kan het programma geflasht worden.
Blinky-compatibiliteit
Ik heb in het eerste voorbeeld de Arduino UNO R4 Minima als board gebruikt om Blinky te testen en niet de BBC micro:bit waar ik eerder zo enthousiast over was. De reden daarvoor is dat, ondanks dat er 25 LED’s (de aan/uit-LED niet meegeteld) beschikbaar zijn, er geen LED op zit die compatibel is met het Blinky-voorbeeld. De ESP Wroom 32 heeft die ook niet, maar de R4 Minima wel.
De GIGA R1 werkt ook met het Blinky-voorbeeld. De MCU op dit board heeft twee rekenkernen (Cortex-M7 en -M4) en Zephyr laat je de keuze welke te gebruiken door ofwel arduino_giga_r1_m4 ofwel arduino_giga_r1_m7 te kiezen voor het build-commando. Je kunt bewijzen dat de kernen inderdaad onafhankelijk werken door het Blinky-voorbeeld twee keer te laden, een keer voor de -M4 en een keer voor de -M7. De GIGA heeft een RGB-LED en Blinky zal op elke kern een andere kleur gebruiken: blauw op de M4 en rood op de M7. Om nog duidelijker onderscheid tussen beide Blinkies te maken kun je de knippersnelheid in één van de twee aanpassen (verander in samples\basic\blinky\src\main.c de waarde van SLEEP_TIME_MS).
Hello World
Voor boards zonder Blinky-LED is er een hello_world voorbeeld dat een welkomsttekst als uitvoer produceert op de seriële poort.
west build -p always -b <my_board> samples/hello_world
west flash
Dit voorbeeld werkt op zowel de BBC micro:bit als op de ESP-WROOM-32 modules. Om de uitvoer te zien, open je een seriële monitor op je computer. De gebruikelijke baudrate is 115.200 baud (115200,n,8,1). Mogelijk moet je het board na het verbinden resetten omdat de tekst slechts eenmalig geprint wordt en je het mogelijk gemist hebt (figuur 4).
Op de R4 Minima en de GIGA R1 verschijnt de seriële uitvoer op pin 1 en niet, zoals je in je onschuld zou verwachten op de USB-C poort zoals dat in de Arduino IDE het geval is. Dat komt omdat de USB hier een interface van de MCU is en geen externe chip; omdat Zephyr modulair en schaalbaar is moet USB-ondersteuning – net als voor elke interface – expliciet ingeschakeld worden voor een project voordat deze gebruikt kan worden. Dit gebeurt in de configuratiebestanden van het project. Verderop meer hierover.
Bij boards met een ingebouwde serieel/USB-converter zul je eerst de juiste seriële poort moeten vinden (meestal is dit poort 0 als de MCU er meerdere heeft) en deze via een externe serieel/USB-converter met uw computer moeten verbinden.
Iets moeilijker
Al het je gelukt is om Blinky en hello_world aan de praat te krijgen op je board, heb je een goede uitgangspositie om echte toepassingen te gaan ontwikkelen in Zephyr. Als slechts één van de voorbeelden werkt en je het andere ook aan de praat wilt krijgen, kan het iets ingewikkelder worden.
De BBC micro:bit was mijn eerste keus voor mijn Zephyr-experimenten, ondanks het feit dat het Blinky-voorbeeld hier niet werkt. Maar dat is niet zo erg omdat voor het board ook een paar bruikbare voorbeelden beschikbaar zijn (in de bbc_microbit-submap van de samples\boards\ map) waarvan er eentje (display) veel interessanter is dan de enkele knipperende LED van Blinky. Er zijn ook voorbeelden voor andere boards, maar slechts weinig in verhouding met het totaal aantal ondersteunde boards (minder dan 5%). Bovendien betreffen veel van deze voorbeelden nogal geavanceerde of ongebruikelijke toepassingen.
Als je Blinky voor de BBC micro:bit (of de ESP-WROOM-32 of een ander incompatibel board) wilt compileren, krijg je een lastig te begrijpen foutmelding voorgeschoteld. Deze probeert je te vertellen dat led0 een onbekende entiteit is. led0 is de standaard Blinky LED (vergelijkbaar met LED_BUILTIN bij Arduino). Omdat de micro:bit een uitbreidingspoort heeft waar je onder andere LED’s op kunt aansluiten, gaan we een van de pinnen van de poort te definiëren als led0.
Maar voordat we dat doen, maken we eerst een backup van de samples/basic/blinky map of, beter nog, maken we een map met een nieuwe naam aan en gebruiken deze. Hieronder gebruiken we samples/basic/blinky, maar je moet natuurlijk de naam gebruiken van de nieuwe map.
De device tree
Het definiëren van een led0 brengt ons bij de device tree, die we al eerder kort hebben genoemd. De device tree bestaat uit een of meer tekstbestanden en beschrijft onder andere de interfaces en het beschikbare geheugen op een board of controller. In Zephyr hebben deze bestanden een .dts- of .dtsi-extensie (de ‘i’ staat hier voor ‘include’) en deze bestanden mogen zelf weer andere bestanden invoegen. Processor-gerelateerde .dtsi-bestanden staan in de dts-map, board gerelateerde .dts- en .dtsi-bestanden in de boards-map. Beide mappen zijn weer onderverdeeld naar de processor-architectuur.
Om DTS(I)-bestanden comfortabel te lezen, kun je de Device Tree plug-in voor Visual Studio Code gebruiken. Deze biedt syntax highlighting en code folding waardoor de bestanden leesbaarder zijn (DTS bestanden gebruiken een C-achtige syntax). Figuur 5 toont een deel van het .dtsi-bestand voor de nRF51822 die het hart van het BBC micro:bit V1-board vormt. Dit bestand hoort bij de DTS-bibliotheek van het board. Een voorbeeld: de status van uart0 staat op "disabled". Deze status wordt overschreven in het DTS-bestand van het board, waar deze "okay" is, wat betekent dat uart0 te gebruiken is. Hetzelfde geldt voor gpio0 en i2c0.
I2C in de device tree
Een ander fragment van het .dts-bestand voor de BBC micro:bit is te zien in figuur 6. Het toont de device tree voor de I2C-bus. Er zijn een of twee sensoren op de bus aangesloten bij de micro:bit (afhankelijk van de variant van de micro:bit V1) en ze zijn in de tree gedefinieerd als mma8653fc en lsm303agr (de laatste bevat twee sensoren, te zien omdat hij twee keer in de tree voorkomt). De eerste heeft status "okay", terwijl de andere twee op "disabled" staan. Dit klopt voor mijn board omdat dat er eentje is uit de eerste micro:bit V1-generatie.
Zoals het fragment laat zien, is deze sensor compatibel met de FXOS8700 en de MMA8653FC, het adres op de I2C-bus is 0x1d, en er zijn twee int (interrupt-)signalen gedefinieerd die verbonden zijn met GPIO pinnen 27 en 28. Als je het wilt testen is er een demoprogramma beschikbaar:
west build -p always -b bbc_microbit samples/sensor/fxos8700
west flash
Let op dat dit niet opgaat voor de BBC micro:bit V2 omdat deze een ander type sensor definieert in de device tree.
De uitvoer van dit demoprogramma is te zien in figuur 7 – maar laten we niet afdwalen.
Overlay van de device tree
Terug naar onze LED, led0. In de device tree van ons board komt zoals verwacht geen led0 voor, dus die moeten we toevoegen. We zouden het direct in de device tree van het board kunnen schrijven, maar dat zou niet correct zijn omdat het board geen led0 heeft. De correcte manier om een device tree uit te breiden is het maken van een overlay-bestand. De inhoud van zo’n overlay-bestand wordt aan de device tree toegevoegd. Secties die al in de device tree aanwezig zijn worden uitgebreid (in het geval van een nieuw item) of overschreven (als het item al bestaat in de device tree); nieuwe secties worden toegevoegd.
Overlay-bestanden moeten in de projectmap staan, in een submap genaamd boards. Als deze map bestaat, wordt door het build-proces gezocht naar een bestand met de naam <my_board>.overlay. In mijn geval is de bestandsnaam bbc_microbit.overlay (bbc_microbit_v2.overlay voor gebruikers van versie 2). Figuur 8 toont de inhoud van dit bestand.
Toevoegen van de Blinky-LED
Zephyr heeft een standaard device tree-item gereserveerd voor LED’s: leds, dus hebben we daar een node (tak) voor gemaakt (je kunt elke naam kiezen die je wilt, maar uiteraard kies je leds als het een overlay voor de bestaande leds-node is). Deze node wordt toegevoegd aan de root (het hoogste niveau) van de device tree; daarom begint de naam met een schuine streep: ‘/’, dat geeft de root aan in DT-terminologie. De volgende regel geeft aan dat deze node compatibel is met de gpio-leds driver die is ingebouwd in Zephyr (de interface voor deze driver is te vinden in zephyr\include\zephyr\drivers\led.h).
Onderliggende nodes
Nu volgt een lijst van onderliggende LED-nodes (child nodes). Omdat ik maar één LED wil aansturen is er ook maar één onderliggende node nodig, led_0, met als naam led0. Een naam geven aan een node is niet verplicht, maar maakt het mogelijk er elders in de tree naar te verwijzen, iets dat we een paar regels verderop zullen doen. Daarnaast kan de naam gebruikt worden door de ontwikkelaar om te verwijzen naar een node en onderdelen daarvan.
In een child node worden de eigenschappen van een device gedefinieerd. In het geval van de een LED is alleen de bijbehorende GPIO-pin verplicht, optioneel kan nog een label (naam) toegekend worden. Labels worden gebruikt als documentatie: voor mensen leesbare informatie in de toepassing, labels hebben verder geen functie.
Ik kies hier GPIO-pin 1, die verwijst naar het grote pad nummer 2 van de micro:bit-uitbreidingsconnector. Bij gebruik van de BBC micro:bit V2 kies je hier 4 als GPIO-pin (in plaats van 1).
Een alias maken
De volgende stap is noodzakelijk omdat het Blinky-voorbeeld het gebruikt. Het betreft het aanmaken van het led0-alias voor onze LED. Je zou verwachten dat toekennen van het gelijknamige label aan de child node volstaat, maar dat is niet zo omdat Blinky de DT_ALIAS macro gebruikt om de LED child node te benaderen. Daarom moeten we iets hebben dat deze macro kan verwerken en dat is een alias, en die komt in het aliases blok. Als Blinky de DT_NODELABEL macro gebruikt zou hebben, zou een alias overbodig zijn omdat DT_NODELABEL het led0 child node-label direct kan gebruiken. Ik begrijp dat het bestaan van labels en aliassen met dezelfde naam verwarrend kan zijn, maar dat is voor de uitleg van dit voorbeeld noodzakelijk.
Zephyr-macro’s
Macro’s zijn niet populair in C/C++, maar in Zephyr worden ze veel gebruikt. Macro’s zoals DT_ALIAS en DT_NODELABEL laten het de applicatie- en project-configuratie-tools toe om informatie uit de device tree te halen en worden daarom veel toegepast. Je kunt de beschrijving ervan vinden in het ephyr-handboek, in het hoofdstuk “Devicetree API” .
Aardig om te weten is dat veel (of misschien wel alle?) Zephyr-macro’s verwachten dat de argumenten met kleine letters zijn geschreven, waarbij alle karakters die geen letters (‘a’ tot ‘z’) of cijfers (‘0’ tot ‘9’) zijn, worden vervangen door underscores (‘_’). Dit wordt lowercase-en-underscore-compatibel genoemd. Als ik de eerder beschreven LED child node LED-0 had genoemd in plaats van led0 zou het argument voor de DT_NODELABEL-macro led_0 moeten zijn: DT_NODELABEL(led_0) omdat ‘-’ geen letter of cijfer is, en alleen kleine letters zijn toegestaan. Met andere woorden: voor de ontwikkelaar die device tree-macro’s gebruikt, geldt de underscore als wildcard (‘joker’). Daarom kan led_0 in een toepassing verwijzen naar led_0, led-0, Led_0, LED-0 en ledé0 (en elke andere denkbare variant) in de device tree. Met dit in gedachten is het aanbevolen om de documentatie van Zephyr-macro’s zorgvuldig te bestuderen.
Gij zult geen fouten maken
Bedenk dat fouten in de device tree tot gevolg hebben dat de compiler ‘FATAL ERROR’ zal melden zonder enige nadere tekst of uitleg!
‘Nieuwbouw’
Bij het experimenteren met de device tree of met je toepassing zul je het project waarschijnlijk vaak compileren. Om dat proces te versnellen kun je -p always (de ‘p’ staat voor ‘pristine’ wat zoveel als ‘ongerept’ betekent) weglaten uit het build-commando. Hierdoor wordt niet alles van de grond af opnieuw gecompileerd. Anderzijds, als je verschillende voorbeelden na elkaar wilt testen, kun je de optie beter laten staan omdat er anders voortdurend meldingen verschijnen dat de build-folder niet bij het gekozen project hoort.
Het flash-commando voert automatisch eerst het laatst gebruikte build-commando uit waardoor het volstaat om direct het flash-commando te gebruiken na een wijziging.
Gebruik een device driver
Het Blinky-voorbeeld roept gpio_pin_toggle_dt() aan om de toestand van de LED te wijzigen. Dit is logischerwijze een functie van de GPIO-driver. Dat is helemaal in orde, maar Zephyr heeft ook een verzameling specifieke LED-drivers. Een LED-driver maakt het programma niet alleen beter leesbaar, maar ook flexibeler en beter uitwisselbaar omdat de LED-driver kan worden aangepast zonder wijzigingen aan de toepassing. Hier komen de schaalbaarheid en de modulariteit van Zephyr tot uiting.
Kconfig heeft een GUI
Het integreren van de LED-driver in ons programma omvat meerdere stappen. Als eerste moet het project zodanig geconfigureerd worden dat de driver meegenomen wordt. Hiervoor gebruiken we Kconfig, het kernel build-time configuratiesysteem dat ook in Linux gebruikt wordt. Er zijn meerdere manieren om er mee te werken, waarvan een grafische gebruikersinterface (GUI) er een is. In Zephyr start je deze als volgt:
west build -t guiconfig
Het duurt even voor de GUI gestart is, maar als het eenmaal zover is zie je iets als in figuur 9. Kconfig laat veel informatie zien over het project. Let op het project-under-development gedeelte: om er zeker van te zijn dat Kconfig daadwerkelijk je project bewerkt, voer je éénmalig een pristine build uit (met de -p always vlag) voordat je de Kconfig GUI start.
Zo veel configuratie-opties…
Neem de tijd om de configuratiemogelijkheden te onderzoeken. Vouw vertakkingen open door op de + te klikken. Selecteer opties door erop te klikken, dan verschijnt onderin het venster aanvullende informatie. Merk op dat floating-point ondersteuning voor printf() een configuratie-optie is, net als C++ ondersteuning. Op vergelijkbare wijze kun je onder Build and Link Features allerlei optimalisatie-instellingen voor de compiler vinden.
Er zijn talloze configuratiemogelijkheden maar die waarin we hier geïnteresseerd zijn, is het Device Drivers-gedeelte. Vouw dit open en scrol omlaag om een indruk te krijgen van wat allemaal mogelijk is. De LED-driver staat ongeveer halverwege: Light-Emitting Diode (LED) Drivers. Selecteer de optie en laat de keuzes die erbij horen ongemoeid (figuur 10). Klik op Save en onthoud het pad naar het configuratiebestand dat onderin het venster staat. Het is leerzaam om dit bestand eens te bekijken, al is het maar om te zien dat er heel veel informatie in staat. Sluit vervolgens de GUI af.
Vanaf nu mag je de -p always vlag niet meer gebruiken in het build-commando omdat dit de zojuist gedane wijzigingen ongedaan zal maken. Verderop zal ik je verklappen uit hoe je deze wijzigingen permanent kunt maken.
Blinky met de LED device driver
Nu kunnen we het nieuwe Blinky-programma schrijven – zie figuur 11. Het start met het invoegen (#include) van de device en de LED driver files. Dan, in het hoofdprogramma (main), passen we de DEVICE_DT_GET_ANY macro toe om uit de device tree een verwijzing naar de LED op te halen. Merk op dat het gpio_leds argument voldoet aan de lowercase- en underscore-regel zodat deze overeenkomt met de gpio-leds waarde van de compatible eigenschap in de leds-node van de device tree (zoals hiervoor uitgelegd). Als geen overeenkomst gevonden wordt, bijvoorbeeld door een type- of andere fout, zal de nieuwe Blinky de melding “No device with compatible gpio-leds found” weergeven. Deze melding zal ook verschijnen als de status van een device nog op "disabled" staat.
Het gebruik van het woord ‘compatible’ als zelfstandig naamwoord in Zephyr is even wennen. De melding van hiervoor hoeft niet te betekenen dat er geen compatibel device bestaat, maar dat er geen device is met een eigenschap die compatible heet en met de waarde gpio-leds (of bijvoorbeeld gpio_leds: de underscore ‘_’ vervangt nu eenmaal alles behalve ‘a’ tot ‘z’ en ’0’ tot ’9’).
Een tweede controle verifieert of het device correct geïnitialiseerd wordt. Er van uitgaande dat dit het geval is, gaan we verder.
In de eindeloze while()-lus wordt de LED in- en uitgeschakeld door middel van de led_on en led_off commando’s die de driver kent. De parameter 0 duidt het eerste (en hier enige) device dat door de macro DEVICE_DT_GET_ANY gevonden is, hier led0.
Controleer de geretourneerde waarden
Omdat we een device driver gebruiken in plaats van een GPIO-pin via hardware-registers direct aan te sturen, is het een goede gewoonte om de geretourneerde waarden van de driverfunctie-aanroepen te controleren omdat er redenen kunnen zijn waardoor deze een foutmelding geven. Een driver moet verplicht bepaalde functies en callbacks aanbieden maar er zijn ook optionele functies mogelijk. Een LED-driver moet bijvoorbeeld led_on en led_off hebben, maar led_blink is optioneel. Als je project led_blink aanroept zal er niets gebeuren omdat deze functie niet geïmplementeerd is. De functie bestaat, maar is leeg en de retourwaarde zal dit melden. Het is daarom een goede gewoonte om waar mogelijk de retourwaarde van een aangeroepen functie te controleren in je software.
Bouw en laad het programma als volgt (zonder de -p always vlag):
west build -b bbc_microbit samples/basic/blinky
west flash
Configureren van het project
Als de LED gaat knipperen met een frequentie van 0,5 Hz hebben we een werkend programma. Om dat zo te houden moeten we de huidige, aangepaste configuratie permanent maken. Hiertoe open je het prj.conf-bestand in de map van het aangepaste Blinky-programma en voeg je de volgende regel toe (anders dan in de device tree-bestanden met hun C-achtige syntax, gebruikt Kconfig een Python-syntax voor de configuratiebestanden):
CONFIG_LED=y
Om te controleren het werkt, voer je een pristine build van je project uit en laad je het programma in het board.
Debuggen
Als je board de optie biedt (zoals de BBC micro:bit) of als je een geschikt debug-tool bezit, kun je het programma debuggen met:
west debug
Dit commando start een gdb-server en opent een terminal (figuur 12). Op internet is informatie te vinden hoe je gdb kunt gebruiken, dit valt helaas buiten het kader van dit artikel.
Zephyr versus Arduino
Nu je hebt gezien wat er nodig is om met Zephyr OS aan de slag te gaan, vraag je je misschien af waarom je het zou gaan gebruiken. Is het niet veel simpeler om gewoon Arduino te gebruiken? Net als Zephyr ondersteunt Arduino meerdere processor-architecturen en honderden board-types, en er zijn duizenden drivers en bibliotheken beschikbaar voor Arduino. Als een toepassing of een bibliotheek de standaard Arduino-API gebruikt kan deze ook eenvoudig bruikbaar gemaakt worden voor andere ondersteunde platforms met vergelijkbare interfaces en periferie, precies zoals een Zephyr-toepassing. Beide zijn bovendien open source. Dus?
Zephyr is bedoeld als zeer robuust RTOS van industriële kwaliteit en met mogelijkheden zoals task scheduling, memory management en device drivers. Daarnaast is Zephyr ontworpen om een breed scala aan complexiteit te ondersteunen, van eenvoudige IoT-apparaten tot zeer complexe embedded systemen. Het biedt meer flexibiliteit, maar vraagt ook uitgebreide kennis en begrip van embedded systeemontwerpen.
Arduino biedt ook real-time mogelijkheden, maar is geen RTOS maar een framework voor single-threaded toepassingen en de focus ligt op toegankelijkheid en eenvoud van gebruik. Arduino abstraheert veel hardwarespecifieke details waardoor het makkelijk toegankelijk is voor beginners. Het kan eventueel gebruikt worden boven op een RTOS zoals Mbed OS voor complexe toepassingen en er wordt gewerkt aan de mogelijkheid om de Arduino Core API te gebruiken onder Zephyr ().
Het is aan jou om te beslissen of je Zephyr al dan niet nodig hebt voor je volgende project. Je kunt het in elk geval eens proberen: ervaring met Zephyr staat in elk geval goed op het CV van een embedded software-ontwikkelaar.
Meer weten?
Tot zover dit artikel. Je hebt gezien dat Zephyr OS complex is en een redelijk steile leercurve heeft. In dit artikel heb ik geprobeerd deze curve wat minder steil te maken. Uiteraard is er nog veel meer te vertellen en te leren over Zephyr – denk vooral niet dat je nu alles weet. In deze White paper en dit artikel vind je links naar bronnen die een goed vervolg kunnen zijn voor geïnteresseerden.
Vragen over Zephyr?
Heb je technische vragen of opmerkingen over dit artikel? Stuur een e-mail naar de auteur op clemens.valens@elektor.com of neem contact op met Elektor.
Dit artikel (230713-03) verschijnt in Elektor Maart/April 2024.