{"id":257,"date":"2024-01-09T13:12:45","date_gmt":"2024-01-09T12:12:45","guid":{"rendered":"https:\/\/redero.fr\/?p=257"},"modified":"2025-09-19T21:55:35","modified_gmt":"2025-09-19T19:55:35","slug":"mon-premier-driver-zephyr-rtos","status":"publish","type":"post","link":"https:\/\/redero.fr\/?p=257","title":{"rendered":"Mon premier driver Zephyr RTOS"},"content":{"rendered":"\n<p>\u00c7a faisait quelques temps que le concept me faisait de l\u2019oeil: un RTOS avec une abstraction suffisante pour rendre votre code embarqu\u00e9 multi-plateforme, le tout sponsoris\u00e9 par la fondation Linux. J\u2019ai donc test\u00e9 pour vous : d\u00e9velopper un <a href=\"https:\/\/github.com\/everedero\/driver_nrf24l01\">driver pour Zephyr RTOS<\/a>, et je vais vous donner mes impressions et mes astuces.<\/p>\n\n\n\n<p>En gen\u00e8se du projet, mon tiroir \u00e0 bidouilles contient plusieurs kits d\u2019\u00e9valuation : un Nucleo STM32, un ESP32 et un nRF52DK. Et puis j\u2019ai retrouv\u00e9 quelques \u00e9metteurs-r\u00e9cepteurs 2.4 GHz nRF24 sur AliExpress, sans doute achet\u00e9s pour un projet oubli\u00e9. Or, le nRF24 n&rsquo;a pas (encore) de support Zephyr.<\/p>\n\n\n\n<figure class=\"wp-block-image aligncenter size-thumbnail wp-duotone-unset-1\"><img loading=\"lazy\" decoding=\"async\" width=\"150\" height=\"150\" src=\"https:\/\/redero.fr\/wp-content\/uploads\/2024\/01\/nrf24l01-150x150.png\" alt=\"nRF24L01\" class=\"wp-image-351\"\/><\/figure>\n\n\n\n<p>J\u2019ai commenc\u00e9 par \u00e9couter religieusement le sieur Marull-Paretas dans son <a href=\"https:\/\/www.youtube.com\/watch?v=o-f2qCd2AXo\">Mastering Zephyr Driver Development<\/a>, et c\u2019est parti pour le fun!<\/p>\n\n\n\n<p>Vous pouvez aussi vous r\u00e9f\u00e9rer au <a href=\"https:\/\/bootlin.com\/blog\/zephyr-implementing-a-device-driver-for-a-sensor\/\">tutoriel de chez Bootlin.<\/a><\/p>\n\n\n\n\n\n<h2 class=\"wp-block-heading\">1- Pr\u00e9paration du dossier de travail<\/h2>\n\n\n\n<p>Tout d&rsquo;abord, j\u2019ai fork\u00e9 l\u2019application d\u2019exemple suivante : <a href=\"https:\/\/github.com\/zephyrproject-rtos\/example-application\">https:\/\/github.com\/zephyrproject-rtos\/example-application<\/a>.<\/p>\n\n\n\n<p>Il y a une petite subtilit\u00e9: rangez ce git clone que je ne saurais voir, il faut imp\u00e9rativement utiliser west pour cloner afin d\u2019avoir l\u2019environnement Zephyr complet :<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>west init -m https:\/\/github.com\/zephyrproject-rtos\/example-application --mr main my-workspace<\/code><\/pre>\n\n\n\n<p class=\"has-small-font-size\">Ne me demandez pas comment je le sais. Oui, je sais ce que vous allez dire, il faut lire les instructions du Readme parfois.<\/p>\n\n\n\n<p>Si vous avez suivi le <a href=\"https:\/\/docs.zephyrproject.org\/latest\/develop\/getting_started\/index.html\">tutoriel de base<\/a> pour installez Zephyr, vous aurez les sources compl\u00e8tes dans ~\/zephyrproject, et une version all\u00e9g\u00e9e de quelques modules dans le project example-application.<\/p>\n\n\n\n<p>Voil\u00e0 mon nouveau dossier Zephyr driver, poss\u00e9dant sa propre copie de Zephyr et son propre dossier de modules :<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>zephyr_driver\n\u2514\u2500\u2500 my-workspace\n    \u251c\u2500\u2500 driver_nrf24l01\n    |   \u251c\u2500\u2500 west.yml\n    |   \u251c\u2500\u2500 CMakeList.txt\n    |   \u251c\u2500\u2500 ...\n    |\n    \u251c\u2500\u2500 modules\n    |   \u2514\u2500\u2500 hal\n    |       \u251c\u2500\u2500 cmsis\n    |       \u251c\u2500\u2500 espressif\n    |       \u251c\u2500\u2500 nordic\n    |       \u2514\u2500\u2500 stm32\n    \u2514\u2500\u2500 zephyr\n        \u251c\u2500\u2500 arch\n        \u251c\u2500\u2500 boards\n        \u251c\u2500\u2500 cmake\n        \u251c\u2500\u2500 CMakeLists.txt\n        \u251c\u2500\u2500 ...<\/code><\/pre>\n\n\n\n<p>N\u2019oubliez pas que vous utilisez le Zephyr local, celui all\u00e9g\u00e9 en modules, qui correspondent \u00e0 des bouts de codes tierces parties des diff\u00e9rents fabricants. Ceux-ci sont t\u00e9l\u00e9charg\u00e9s automatiquement par west, il suffit de les ajouter au fichier west.yml du projet et d\u2019appeler :<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>west update<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">2- R\u00e9daction du driver<\/h2>\n\n\n\n<p>Le nRF24 est un petit \u00e9metteur-r\u00e9cepteur 2.4 GHz qui communique via un bus SPI, avec tout un tas de registres d\u00e9crits dans sa <a href=\"https:\/\/infocenter.nordicsemi.com\/pdf\/nRF24L01P_PS_v1.0.pdf\">documentation<\/a> comme d\u2019habitude.<\/p>\n\n\n\n<p>Il existe un <a href=\"https:\/\/github.com\/nRF24\/RF24\">driver pour Raspberry Pi<\/a> tr\u00e8s complet, utilis\u00e9 comme inspiration. Le fichier header qui d\u00e9crit les diff\u00e9rents registres a \u00e9t\u00e9 fort utile pour m&rsquo;\u00e9viter les erreurs idiotes de recopie des valeurs.<\/p>\n\n\n\n<p>Ensuite, le sensei Marull-Paretas nous dit que nous devons choisir entre :<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Impl\u00e9menter une API g\u00e9n\u00e9rique existante de Zephyr, ou<\/li>\n\n\n\n<li>Cr\u00e9er une API customis\u00e9e<\/li>\n<\/ul>\n\n\n\n<p>L&rsquo;API, c&rsquo;est simplement la liste des fonctions accessibles \u00e0 la partie applicative de Zephyr. C&rsquo;est donc beaucoup mieux d&rsquo;avoir une API g\u00e9n\u00e9rique, puisque \u00e7a permettrait de changer le composant pour une \u00e9quivalence de mani\u00e8re transparente. Or, dans mon cas, l&rsquo;API \u00ab\u00a0radio propri\u00e9taire\u00a0\u00bb n&rsquo;existe pas encore, donc je vais commencer mon driver sans, et je le modifierai quand une API g\u00e9n\u00e9rique sera disponible.<\/p>\n\n\n\n<p>Un premier travail est donc de modifier exemple-application pour ne garder que les fichiers relatifs au driver, les renommer, et modifier les fichiers de configuration pour compiler les nouveaux fichiers. J&rsquo;en ai profit\u00e9 pour modifier les fichiers projets pour int\u00e9grer la target de travail, dans mon cas, ma carte Nucleo.<\/p>\n\n\n\n<p>Une fois le squelette de l&rsquo;application pr\u00e9par\u00e9, il est temps de remplir les champs avec la logique r\u00e9elle du composant, avec la datasheet d&rsquo;un c\u00f4t\u00e9 et l&rsquo;analyseur logique de l&rsquo;autre.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Driver et application d&rsquo;exemple<\/h3>\n\n\n\n<p>Le fichier le plus important est<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>.\/drivers\/nrf24l01\/nrf24l01.c<\/code><\/pre>\n\n\n\n<p>Mais ce fichier n&rsquo;est pas standalone, il faut l&rsquo;invoquer dans une application main :<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>.\/app\/src\/main.c.<\/code><\/pre>\n\n\n\n<p>Zephyr appelle l&rsquo;initialisation de chaque driver (dans mon cas nrf24l01_init) avant de d\u00e9marrer l&rsquo;application. Si comme moi, vous aimez \u00e9crire un brouillon et vous soucier du d\u00e9coupage en fonctions dans un second temps, il est possible de commencer \u00e0 coder avant d&rsquo;avoir mis en place les fonctions d&rsquo;API, en mettant simplement tout en vrac dans nrf24l01_init et avec un main.c vide (c&rsquo;est une technique perso, ne faites pas \u00e7a si vous respectez la philosophie de toujours coder proprement).<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Device tree<\/h3>\n\n\n\n<p>Les donn\u00e9es du device tree sont \u00e0 venir coller dans deux structures :<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>struct nrf24l01_config<\/code><\/pre>\n\n\n\n<pre class=\"wp-block-code\"><code>struct nrf24l01_data<\/code><\/pre>\n\n\n\n<p>La structure config est d\u00e9clar\u00e9e comme const, elle ne sera pas modifi\u00e9e pendant le runtime, on a tendance \u00e0 y mettre les r\u00e9f\u00e9rences aux GPIOs et aux bus utiles au fonctionnement du driver. La structure data est modifiable, donc c&rsquo;est l\u00e0 o\u00f9 finissent tout ce que j&rsquo;eusse \u00e9t\u00e9 tent\u00e9 de d\u00e9clarer comme variable globale.<\/p>\n\n\n\n<p>Toutes les options du device tree sont list\u00e9es et d\u00e9crites dans un fichier Yaml appel\u00e9 \u00ab\u00a0bindings\u00a0\u00bb. Ce fichier est \u00e0 la fois une documentation du driver et une partie du code, il n&rsquo;est absolument pas facultatif et il faut bien faire attention au nom du fichier et aux subtilis\u00e9s typographiques. Histoire v\u00e9cue : j&rsquo;ai pass\u00e9 une heure \u00e0 d\u00e9boguer parce-que j&rsquo;avais appel\u00e9 ce fichier nrf24l01.yaml et pas nordic,nrf24l01.yaml.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>.\/dts\/bindings\/nrf24\/nordic,nrf24l01.yaml<\/code><\/pre>\n\n\n\n<p>Dans mon exemple, je n&rsquo;ai pas eu besoin de cr\u00e9er un device tree complet personnalis\u00e9 puisque j&rsquo;utilise une carte d&rsquo;\u00e9valuation, j&rsquo;ai simplement ajout\u00e9 le nrf24l01 dans un device tree overlay :<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>.\/app\/boards\/nucleo_f756zg.overlay<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Options de c<strong>onfiguration<\/strong><\/h3>\n\n\n\n<p>J&rsquo;ai \u00e9galement rajout\u00e9 quelques options Kconfig \u00e0 mon driver, notamment un switch entre fonctionnement en polling et fonctionnement en interruption :<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>.\/drivers\/nrf24l01\/Kconfig<\/code><\/pre>\n\n\n\n<p>Ces options sont ensuites s\u00e9lectionn\u00e9es c\u00f4t\u00e9 application, dans<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>.\/app\/prj.conf<\/code><\/pre>\n\n\n\n<p>Notez qu&rsquo;il est \u00e9galement possible mettre ce type d&rsquo;options dans le device tree. Techniquement \u00e7a ne change rien, philosophiquement, on doit mettre ce qui est mat\u00e9riel dans device tree et ce qui est logiciel dans le Kconfig. Il y a une zone grise entre configuration mat\u00e9rielle et configuration logique, puisque l&rsquo;option \u00ab\u00a0polling\u00a0\u00bb va surtout \u00eatre utilis\u00e9e si vous ne disposez pas de l&rsquo;interruption hardware, mais dans les drivers mainline de Zephyr, il est d&rsquo;usage de mettre \u00e7a dans la configuration.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">API custom<\/h3>\n\n\n\n<p>En ce qui concerne l&rsquo;API customis\u00e9e, elle est d\u00e9crite dans le dossier include.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>.\/include\/app\/drivers\/propy_radio.h<\/code><\/pre>\n\n\n\n<p>Attention, il n&rsquo;est pas possible de donner aux fonctions API les m\u00eames noms que les fonctions en interne du driver. M\u00eame si l\u2019API ne concerne qu\u2019un seul driver pour le moment, il est de bon ton d\u2019essayer de trouver un nom qui pourra s\u2019appliquer \u00e0 d\u2019autres produits similaires, ici je croisis \u00ab\u00a0propy_radio\u00a0\u00bb pour pouvoir \u00eatre appliqu\u00e9e \u00e0 d\u2019autres produits de radio propri\u00e9taires. J&rsquo;ai appel\u00e9 mes fonctions API \u00ab\u00a0propy_radio_read\u00a0\u00bb et \u00ab\u00a0propy_radio_write\u00a0\u00bb et leurs impl\u00e9mentations \u00ab\u00a0nrf24l01_read\u00a0\u00bb et \u00ab\u00a0nrf24l01_write\u00a0\u00bb.<\/p>\n\n\n\n<p>C\u00f4t\u00e9 application, c&rsquo;est bien \u00e9videmment \u00ab\u00a0propy_radio_read\u00a0\u00bb et \u00ab\u00a0propy_radio_write\u00a0\u00bb qui sont appel\u00e9es.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Tests CI<\/h3>\n\n\n\n<p>Dans mon cas, difficile de tester mon projet en workflow Github avec Qemu, puisqu&rsquo;il s&rsquo;agit de quelque chose de compl\u00e8tement hardware. J&rsquo;ai donc supprim\u00e9 les tests en rapport avec l&rsquo;ex\u00e9cution pour ne v\u00e9rifier que la bonne compilation sur les diff\u00e9rentes plate-formes.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">3 \u2013 R\u00e9sultat<\/h2>\n\n\n\n<p>Le r\u00e9sultat est l\u00e0 : <a href=\"https:\/\/github.com\/everedero\/driver_nrf24l01\" target=\"_blank\" rel=\"noreferrer noopener\">https:\/\/github.com\/everedero\/driver_nrf24l01<\/a><\/p>\n\n\n\n<p>Mon application d&rsquo;exemple consiste \u00e0 prendre le driver nRF24 nouvellement cr\u00e9\u00e9 pour le faire tourner sur les 3 plate-formes d&rsquo;\u00e9valuation de mon tiroir \u00e0 bidouilles, un STM32 Nucleo, un nRF52DK, et un ESP32 WROOM. Et c&rsquo;est super facile, puisqu&rsquo;il suffit juste de cr\u00e9er un device tree overlay pour chaque plate-forme, en connectant les bons c\u00e2bles aux bons endroits, et le syst\u00e8me se d\u00e9brouille avec \u00e7a ! Pour d\u00e9montrer la magie, mon exemple fait discuter ensemble les 3 plate-formes au travers de 3 r\u00f4les : Alice qui initie la conversation, Bob qui lui r\u00e9pond, et Eve qui se contente d&rsquo;\u00e9couter discr\u00e8tement.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large wp-duotone-unset-2\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"768\" src=\"https:\/\/redero.fr\/wp-content\/uploads\/2024\/01\/multiplatform-1-1024x768.jpg\" alt=\"\" class=\"wp-image-313\" srcset=\"https:\/\/redero.fr\/wp-content\/uploads\/2024\/01\/multiplatform-1-1024x768.jpg 1024w, https:\/\/redero.fr\/wp-content\/uploads\/2024\/01\/multiplatform-1-300x225.jpg 300w, https:\/\/redero.fr\/wp-content\/uploads\/2024\/01\/multiplatform-1-768x576.jpg 768w, https:\/\/redero.fr\/wp-content\/uploads\/2024\/01\/multiplatform-1.jpg 1280w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>Et \u00e7a marche !<\/p>\n\n\n\n<figure class=\"wp-block-video\"><video height=\"1080\" style=\"aspect-ratio: 1920 \/ 1080;\" width=\"1920\" controls src=\"https:\/\/redero.fr\/wp-content\/uploads\/2024\/01\/term.webm\"><\/video><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">4 &#8211; Conclusion<\/h2>\n\n\n\n<p>Comme souvent avec Zephyr, l&rsquo;apprentissage comporte une taxe \u00e0 l&rsquo;entr\u00e9e non n\u00e9gligeable. Cepedant, je suis contente d&rsquo;avoir pris le temps d&rsquo;en venir \u00e0 bout, parce-que pouvoir porter le m\u00eame code sur 3 plate-formes diff\u00e9rentes quasiment instantan\u00e9ment, c&rsquo;est quand m\u00eame assez merveilleux et plein de potentiel.<\/p>\n\n\n\n<pre class=\"wp-block-verse has-text-align-center has-vivid-green-cyan-color has-secondary-background-color has-text-color has-background has-link-color has-small-font-size wp-elements-bd30224f52356db293db33d8dbe47ca9\">Article mis \u00e0 jour le 02\/02\/2025 avec de nouvelles instruction d\u2019installation<\/pre>\n","protected":false},"excerpt":{"rendered":"<p>J\u2019ai test\u00e9 pour vous : d\u00e9velopper un driver pour Zephyr RTOS, et je vais vous donner mes impressions et mes astuces.<\/p>\n","protected":false},"author":1,"featured_media":276,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[37],"tags":[39,25],"class_list":["post-257","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-blog","tag-driver","tag-zephyr"],"_links":{"self":[{"href":"https:\/\/redero.fr\/index.php?rest_route=\/wp\/v2\/posts\/257","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/redero.fr\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/redero.fr\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/redero.fr\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/redero.fr\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=257"}],"version-history":[{"count":24,"href":"https:\/\/redero.fr\/index.php?rest_route=\/wp\/v2\/posts\/257\/revisions"}],"predecessor-version":[{"id":1303,"href":"https:\/\/redero.fr\/index.php?rest_route=\/wp\/v2\/posts\/257\/revisions\/1303"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/redero.fr\/index.php?rest_route=\/wp\/v2\/media\/276"}],"wp:attachment":[{"href":"https:\/\/redero.fr\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=257"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/redero.fr\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=257"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/redero.fr\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=257"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}