{"id":453,"date":"2024-02-15T23:23:21","date_gmt":"2024-02-15T22:23:21","guid":{"rendered":"https:\/\/redero.fr\/?p=453"},"modified":"2025-09-17T14:54:19","modified_gmt":"2025-09-17T12:54:19","slug":"from-schematics-to-application-express-software-bring-up","status":"publish","type":"post","link":"https:\/\/redero.fr\/?p=453&lang=en","title":{"rendered":"From schematics to application: express software bring-up"},"content":{"rendered":"\n<p>I am still experimenting several Zephyr RTOS use case, focusing on tying up together electronics and application software.<\/p>\n\n\n\n<p>With that in mind, I took a discontinued 2016 <a href=\"https:\/\/www.st.com\/en\/evaluation-tools\/st25dv-discovery.html\">ST25DV-DISCOVERY<\/a> kit from my drawer, a kit I got from an electronics lab decluttering. It features a display and a 4-directions button kind of like a joystick, and I wanted to turn this into a mini arcade game thingy. But I didn\u2019t want to write code, and hoped to find a Pong or Tetris to reuse.<\/p>\n\n\n\n<figure data-wp-context=\"{&quot;imageId&quot;:&quot;69f702ef5590f&quot;}\" data-wp-interactive=\"core\/image\" data-wp-key=\"69f702ef5590f\" class=\"wp-block-image aligncenter size-medium wp-duotone-unset-1 wp-lightbox-container\"><img loading=\"lazy\" decoding=\"async\" width=\"300\" height=\"171\" data-wp-class--hide=\"state.isContentHidden\" data-wp-class--show=\"state.isContentVisible\" data-wp-init=\"callbacks.setButtonStyles\" data-wp-on--click=\"actions.showLightbox\" data-wp-on--load=\"callbacks.setButtonStyles\" data-wp-on-window--resize=\"callbacks.setButtonStyles\" src=\"https:\/\/redero.fr\/wp-content\/uploads\/2024\/02\/st25dv_mb1283_disco-1-300x171.png\" alt=\"\" class=\"wp-image-626\" srcset=\"https:\/\/redero.fr\/wp-content\/uploads\/2024\/02\/st25dv_mb1283_disco-1-300x171.png 300w, https:\/\/redero.fr\/wp-content\/uploads\/2024\/02\/st25dv_mb1283_disco-1-1024x584.png 1024w, https:\/\/redero.fr\/wp-content\/uploads\/2024\/02\/st25dv_mb1283_disco-1-768x438.png 768w, https:\/\/redero.fr\/wp-content\/uploads\/2024\/02\/st25dv_mb1283_disco-1.png 1094w\" sizes=\"auto, (max-width: 300px) 100vw, 300px\" \/><button\n\t\t\tclass=\"lightbox-trigger\"\n\t\t\ttype=\"button\"\n\t\t\taria-haspopup=\"dialog\"\n\t\t\taria-label=\"Agrandir\"\n\t\t\tdata-wp-init=\"callbacks.initTriggerButton\"\n\t\t\tdata-wp-on--click=\"actions.showLightbox\"\n\t\t\tdata-wp-style--right=\"state.imageButtonRight\"\n\t\t\tdata-wp-style--top=\"state.imageButtonTop\"\n\t\t>\n\t\t\t<svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"12\" height=\"12\" fill=\"none\" viewBox=\"0 0 12 12\">\n\t\t\t\t<path fill=\"#fff\" d=\"M2 0a2 2 0 0 0-2 2v2h1.5V2a.5.5 0 0 1 .5-.5h2V0H2Zm2 10.5H2a.5.5 0 0 1-.5-.5V8H0v2a2 2 0 0 0 2 2h2v-1.5ZM8 12v-1.5h2a.5.5 0 0 0 .5-.5V8H12v2a2 2 0 0 1-2 2H8Zm2-12a2 2 0 0 1 2 2v2h-1.5V2a.5.5 0 0 0-.5-.5H8V0h2Z\" \/>\n\t\t\t<\/svg>\n\t\t<\/button><\/figure>\n\n\n\n<p>Fortunately, my board has an STM32 flavour and all drivers already supported by Zephyr, I can focus on stitching it all together.<\/p>\n\n\n\n<p>A few links before we start:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"https:\/\/www.st.com\/resource\/en\/user_manual\/um2096-getting-started-with-st25dvdiscovery-stmicroelectronics.pdf\" target=\"_blank\" rel=\"noreferrer noopener\">Kit schematics, page 11 (MB1283)<\/a> <\/li>\n\n\n\n<li><a href=\"https:\/\/github.com\/everedero\/zephyr_lvgl_2048_st25dv\" target=\"_blank\" rel=\"noreferrer noopener\">Project repository<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/cdn-shop.adafruit.com\/datasheets\/ILI9341.pdf\" target=\"_blank\" rel=\"noreferrer noopener\">Display datasheet<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/media.digikey.com\/pdf\/data%20sheets\/st%20microelectronics%20pdfs\/stmpe811.pdf\" target=\"_blank\" rel=\"noreferrer noopener\">Touchpad controller datasheet<\/a><\/li>\n<\/ul>\n\n\n\n<p>So let\u2019s go!<\/p>\n\n\n\n\n\n<h2 class=\"wp-block-heading\">LCD display<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">Error 404 : datasheet not found<\/h3>\n\n\n\n<p>Display is the project crux, even more because I don\u2019t know LCD displays very well, and it looks like a complex world where datasheets are hard to find if you don\u2019t get them from resellers or manufacturers.<\/p>\n\n\n\n<p>They are generally sold as modules, with integrated controllers allowing to access them with serial buses like SPI or I2C, or parallels like MIPI DSI. Most references have compatible drivers, probably because they are using the same type of controllers, but I could not find which driver to actually call, and I don\u2019t want to disassemble the display module. For this project, I got the clue from the eval kit original source code, and found that is is equivalent to Ilitek ILI9341.<\/p>\n\n\n\n<p>This equivalence is available to the general public and documented in Adafruit \/ Sparkfun catalogues.<\/p>\n\n\n\n<p>Another discovery from display world, the new SPI LCD driver was renamed \u00ab\u00a0MIPI DBI\u00a0\u00bb. This standard is unknown to me, I get that it is a LCD command standard based on an SPI bus, plus 2 complementary pins: a reset and a command and data switch. It looks like it is already implemented in lots of things, but I did not see it explicitly mentioned: this a probably a long story involving tech and geopolitics, but I did not dig more into it because I have a Pong to make. <\/p>\n\n\n\n<h3 class=\"wp-block-heading\">DC, WR, DCX, TE, SDA, WTF<\/h3>\n\n\n\n<p>How to fill in the device tree? The display driver needs one SPI bus, and 2 GPIOs, \u00ab\u00a0reset\u00a0\u00bb and \u00ab\u00a0dc\u00a0\u00bb (aka data \/ command).<\/p>\n\n\n\n<figure class=\"wp-block-image size-full wp-duotone-unset-2\"><img loading=\"lazy\" decoding=\"async\" width=\"417\" height=\"228\" src=\"https:\/\/redero.fr\/wp-content\/uploads\/2024\/02\/sch_screen_module.png\" alt=\"\" class=\"wp-image-421\" srcset=\"https:\/\/redero.fr\/wp-content\/uploads\/2024\/02\/sch_screen_module.png 417w, https:\/\/redero.fr\/wp-content\/uploads\/2024\/02\/sch_screen_module-300x164.png 300w\" sizes=\"auto, (max-width: 417px) 100vw, 417px\" \/><figcaption class=\"wp-element-caption\"><em>Display-side schematics<\/em><\/figcaption><\/figure>\n\n\n\n<figure class=\"wp-block-image size-full wp-duotone-unset-3\"><img loading=\"lazy\" decoding=\"async\" width=\"762\" height=\"203\" src=\"https:\/\/redero.fr\/wp-content\/uploads\/2024\/02\/sch_screen_toplevel.png\" alt=\"\" class=\"wp-image-422\" srcset=\"https:\/\/redero.fr\/wp-content\/uploads\/2024\/02\/sch_screen_toplevel.png 762w, https:\/\/redero.fr\/wp-content\/uploads\/2024\/02\/sch_screen_toplevel-300x80.png 300w\" sizes=\"auto, (max-width: 762px) 100vw, 762px\" \/><figcaption class=\"wp-element-caption\"><em>Top-level<\/em><\/figcaption><\/figure>\n\n\n\n<figure class=\"wp-block-image size-full wp-duotone-unset-4\"><img loading=\"lazy\" decoding=\"async\" width=\"472\" height=\"311\" src=\"https:\/\/redero.fr\/wp-content\/uploads\/2024\/02\/sch_screen_soc.png\" alt=\"\" class=\"wp-image-423\" srcset=\"https:\/\/redero.fr\/wp-content\/uploads\/2024\/02\/sch_screen_soc.png 472w, https:\/\/redero.fr\/wp-content\/uploads\/2024\/02\/sch_screen_soc-300x198.png 300w\" sizes=\"auto, (max-width: 472px) 100vw, 472px\" \/><figcaption class=\"wp-element-caption\"><em>STM32-side schematics<\/em><\/figcaption><\/figure>\n\n\n\n<pre class=\"wp-block-code\"><code>        mipi_dbi {\n                compatible = \"zephyr,mipi-dbi-spi\";\n                reset-gpios = &lt;&amp;gpioc 1 GPIO_ACTIVE_HIGH&gt;;\n                dc-gpios = &lt;&amp;gpioc 0 GPIO_ACTIVE_HIGH&gt;;\n                spi-dev = &lt;&amp;spi2&gt;;\n                &#091;...]\n        };\n<\/code><\/pre>\n\n\n\n<p class=\"has-text-align-right has-small-font-size\" style=\"margin-top:0;margin-bottom:0\"><em>boards\/arm\/st25dv_mb1283_disco\/st25dv_mb1283_disco.dts<\/em><\/p>\n\n\n\n<p>Schematics show we are using SPI but 2, except MISO is not connected. MOSI is, but it is renamed \u00ab\u00a0SDA\u00a0\u00bb in top level&#8230; this is no mistake, it indicates this cable can be bidirectional, because <a href=\"https:\/\/en.wikipedia.org\/wiki\/Serial_Peripheral_Interface#Three-wire\">half-duplex SPI does exists<\/a>, and our SDA could be named SISO or MOMI.<\/p>\n\n\n\n<p>Bad news, my nets are called WR and TE in schematics, and don\u2019t know who\u2019s who. <a href=\"https:\/\/cdn-shop.adafruit.com\/datasheets\/ILI9341.pdf\">ILI9341 datasheet<\/a> talks about a WRX, which looks like WR aka Write-Read, but Write-Read is only relevant for another display use case. This pin is also used as D\/CX, it is the DC I was looking for, so DC is WR is GPIO PC0.<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p>WRX (DCX)<\/p>\n\n\n\n<p>&#8211; 8080-I \/8080-II system (WRX): Serves as a write signal and writes data at the rising edge.<\/p>\n\n\n\n<p>&#8211; 4-line system (D\/CX): Serves as command or parameter select.<\/p>\n<\/blockquote>\n\n\n\n<p>TE means Tearing Effect, and is used to synchronize screen writes and refreshing. It is not a reset, but I want to control this pin with the reset so it stays low while I am using the driver, hence the GPIO PC1 connect.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Driver parameters<\/h3>\n\n\n\n<p>Once the virtual MIPI DBI driver created, we add the ili9341 driver to it.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>#include &lt;zephyr\/dt-bindings\/display\/ili9xxx.h&gt;\n&#091;...]\n        mipi_dbi {\n                &#091;...]\n                write-only;\n\n                ili9341: ili9341@0 {\n                        compatible = \"ilitek,ili9341\";\n                        mipi-max-frequency = &lt;20000000&gt;;\n                        reg = &lt;0&gt;;\n                        pixel-format = &lt;ILI9XXX_PIXEL_FORMAT_RGB888&gt;; \/\/ 1\n                        rotation = &lt;0&gt;;\n                        width = &lt;240&gt;;\n                        height = &lt;320&gt;;\n                        duplex = &lt;0x800&gt;;\n            };\n        };\n<\/code><\/pre>\n\n\n\n<p class=\"has-text-align-right has-small-font-size\" style=\"margin-top:0;margin-bottom:0\"><em>boards\/arm\/st25dv_mb1283_disco\/st25dv_mb1283_disco.dts<\/em><\/p>\n\n\n\n<p>\u00ab\u00a0duplex = &lt;0x800&gt;\u00a0\u00bb allows to select half-duplex bus behaviour, which we need because we don\u2019t have any MISO.<\/p>\n\n\n\n<p>\u00ab\u00a0write-only\u00a0\u00bb limits read operations in the driver, which is good in our case, to optimize bus usage.<\/p>\n\n\n\n<p>pixel-format, width, and height, have needed a bit of trial and error. Pixel format default is 0, and my colors got all weird, which was quickly solved by changing this value.<\/p>\n\n\n\n<figure data-wp-context=\"{&quot;imageId&quot;:&quot;69f702ef5657c&quot;}\" data-wp-interactive=\"core\/image\" data-wp-key=\"69f702ef5657c\" class=\"wp-block-image size-large wp-duotone-unset-5 wp-lightbox-container\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"768\" data-wp-class--hide=\"state.isContentHidden\" data-wp-class--show=\"state.isContentVisible\" data-wp-init=\"callbacks.setButtonStyles\" data-wp-on--click=\"actions.showLightbox\" data-wp-on--load=\"callbacks.setButtonStyles\" data-wp-on-window--resize=\"callbacks.setButtonStyles\" src=\"https:\/\/redero.fr\/wp-content\/uploads\/2024\/02\/bad_pixel_format-1024x768.jpeg\" alt=\"\" class=\"wp-image-516\" srcset=\"https:\/\/redero.fr\/wp-content\/uploads\/2024\/02\/bad_pixel_format-1024x768.jpeg 1024w, https:\/\/redero.fr\/wp-content\/uploads\/2024\/02\/bad_pixel_format-300x225.jpeg 300w, https:\/\/redero.fr\/wp-content\/uploads\/2024\/02\/bad_pixel_format-768x576.jpeg 768w, https:\/\/redero.fr\/wp-content\/uploads\/2024\/02\/bad_pixel_format.jpeg 1280w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><button\n\t\t\tclass=\"lightbox-trigger\"\n\t\t\ttype=\"button\"\n\t\t\taria-haspopup=\"dialog\"\n\t\t\taria-label=\"Agrandir\"\n\t\t\tdata-wp-init=\"callbacks.initTriggerButton\"\n\t\t\tdata-wp-on--click=\"actions.showLightbox\"\n\t\t\tdata-wp-style--right=\"state.imageButtonRight\"\n\t\t\tdata-wp-style--top=\"state.imageButtonTop\"\n\t\t>\n\t\t\t<svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"12\" height=\"12\" fill=\"none\" viewBox=\"0 0 12 12\">\n\t\t\t\t<path fill=\"#fff\" d=\"M2 0a2 2 0 0 0-2 2v2h1.5V2a.5.5 0 0 1 .5-.5h2V0H2Zm2 10.5H2a.5.5 0 0 1-.5-.5V8H0v2a2 2 0 0 0 2 2h2v-1.5ZM8 12v-1.5h2a.5.5 0 0 0 .5-.5V8H12v2a2 2 0 0 1-2 2H8Zm2-12a2 2 0 0 1 2 2v2h-1.5V2a.5.5 0 0 0-.5-.5H8V0h2Z\" \/>\n\t\t\t<\/svg>\n\t\t<\/button><figcaption class=\"wp-element-caption\"><em>Psychedelic colours, ugly contours: check your pixel-format<\/em><\/figcaption><\/figure>\n\n\n\n<p>mipi-max-frequency allows to select SPI frequency bus. Here I selected the max SPI bus value allowed, but it was interesting to lower it for some logic analyzer debugging.<\/p>\n\n\n\n<p>Once our driver parameters are set, we need to tell Zephyr where to find the screen.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>zephyr,display = &amp;ili9341;<\/code><\/pre>\n\n\n\n<p class=\"has-text-align-right has-small-font-size\" style=\"margin-top:0;margin-bottom:0\"><em>boards\/arm\/st25dv_mb1283_disco\/st25dv_mb1283_disco.dts<\/em><\/p>\n\n\n\n<p>Then, we don\u2019t forget to ask cmake to actually compile the driver source files, in prj.conf:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>CONFIG_DISPLAY=y<\/code><\/pre>\n\n\n\n<p class=\"has-text-align-right has-small-font-size\" style=\"margin-top:0;margin-bottom:0\"><em><em>game\/prj.conf<\/em><\/em><\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>A glimpse on the protocol<\/strong><\/h3>\n\n\n\n<figure data-wp-context=\"{&quot;imageId&quot;:&quot;69f702ef56b1b&quot;}\" data-wp-interactive=\"core\/image\" data-wp-key=\"69f702ef56b1b\" class=\"wp-block-image size-large wp-duotone-unset-6 wp-lightbox-container\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"228\" data-wp-class--hide=\"state.isContentHidden\" data-wp-class--show=\"state.isContentVisible\" data-wp-init=\"callbacks.setButtonStyles\" data-wp-on--click=\"actions.showLightbox\" data-wp-on--load=\"callbacks.setButtonStyles\" data-wp-on-window--resize=\"callbacks.setButtonStyles\" src=\"https:\/\/redero.fr\/wp-content\/uploads\/2024\/02\/logic_capture-1024x228.png\" alt=\"\" class=\"wp-image-409\" srcset=\"https:\/\/redero.fr\/wp-content\/uploads\/2024\/02\/logic_capture-1024x228.png 1024w, https:\/\/redero.fr\/wp-content\/uploads\/2024\/02\/logic_capture-300x67.png 300w, https:\/\/redero.fr\/wp-content\/uploads\/2024\/02\/logic_capture-768x171.png 768w, https:\/\/redero.fr\/wp-content\/uploads\/2024\/02\/logic_capture-1536x342.png 1536w, https:\/\/redero.fr\/wp-content\/uploads\/2024\/02\/logic_capture.png 1874w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><button\n\t\t\tclass=\"lightbox-trigger\"\n\t\t\ttype=\"button\"\n\t\t\taria-haspopup=\"dialog\"\n\t\t\taria-label=\"Agrandir\"\n\t\t\tdata-wp-init=\"callbacks.initTriggerButton\"\n\t\t\tdata-wp-on--click=\"actions.showLightbox\"\n\t\t\tdata-wp-style--right=\"state.imageButtonRight\"\n\t\t\tdata-wp-style--top=\"state.imageButtonTop\"\n\t\t>\n\t\t\t<svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"12\" height=\"12\" fill=\"none\" viewBox=\"0 0 12 12\">\n\t\t\t\t<path fill=\"#fff\" d=\"M2 0a2 2 0 0 0-2 2v2h1.5V2a.5.5 0 0 1 .5-.5h2V0H2Zm2 10.5H2a.5.5 0 0 1-.5-.5V8H0v2a2 2 0 0 0 2 2h2v-1.5ZM8 12v-1.5h2a.5.5 0 0 0 .5-.5V8H12v2a2 2 0 0 1-2 2H8Zm2-12a2 2 0 0 1 2 2v2h-1.5V2a.5.5 0 0 0-.5-.5H8V0h2Z\" \/>\n\t\t\t<\/svg>\n\t\t<\/button><\/figure>\n\n\n\n<p>Here is what bitmap transmission through SPI looks like. Let\u2019s assume we send a bitmap in a rectangle with coordinates (X1, Y1, X2, Y1):<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>CD goes to 0, we want to send a command<\/li>\n\n\n\n<li>0x2A (Column Address Set, CASET) is sent on SPI bus<\/li>\n\n\n\n<li>CD goes to 1 to send data<\/li>\n\n\n\n<li>Coordinates X1 are X2 are sent on SPI bus<\/li>\n\n\n\n<li>Similarly, 0x2B (Page Address Set, PASET) is sent, followed by coordinates Y1 and Y2<\/li>\n\n\n\n<li>Then 0x2C is transmitted, followed by all the bitmap bytes.<\/li>\n<\/ul>\n\n\n\n<p>Complete protocol is described in <a href=\"https:\/\/cdn-shop.adafruit.com\/datasheets\/ILI9341.pdf\" target=\"_blank\" rel=\"noreferrer noopener\">ILI9341 datasheet<\/a>. <\/p>\n\n\n\n<h3 class=\"wp-block-heading\">\u00ab\u00a0Display\u00a0\u00bb sample<\/h3>\n\n\n\n<p>Display is configured, it is time to test that everything works with a small but highly useful sample, samples\/drivers\/display.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large wp-duotone-unset-7\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"768\" src=\"https:\/\/redero.fr\/wp-content\/uploads\/2024\/02\/display_sample_without_issue-1024x768.jpg\" alt=\"\" class=\"wp-image-410\" srcset=\"https:\/\/redero.fr\/wp-content\/uploads\/2024\/02\/display_sample_without_issue-1024x768.jpg 1024w, https:\/\/redero.fr\/wp-content\/uploads\/2024\/02\/display_sample_without_issue-300x225.jpg 300w, https:\/\/redero.fr\/wp-content\/uploads\/2024\/02\/display_sample_without_issue-768x576.jpg 768w, https:\/\/redero.fr\/wp-content\/uploads\/2024\/02\/display_sample_without_issue.jpg 1280w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Resistive touchpad<\/h2>\n\n\n\n<p>This display is not only a display, it also features a touchpad, albeit a resistive one. Smartphones got us used to capacitive touchpads, based on indium (ITO), and I had forgotten about this older and less sensitive tech. It does not contain any super-rare metals, which is cool. Although the physical, clickety buttons appeal more to me, I add my touchpad to device tree.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full wp-duotone-unset-8\"><img loading=\"lazy\" decoding=\"async\" width=\"587\" height=\"127\" src=\"https:\/\/redero.fr\/wp-content\/uploads\/2024\/02\/sch_capscreen_module.png\" alt=\"\" class=\"wp-image-464\" srcset=\"https:\/\/redero.fr\/wp-content\/uploads\/2024\/02\/sch_capscreen_module.png 587w, https:\/\/redero.fr\/wp-content\/uploads\/2024\/02\/sch_capscreen_module-300x65.png 300w\" sizes=\"auto, (max-width: 587px) 100vw, 587px\" \/><figcaption class=\"wp-element-caption\"><em>Peripheral-side schematics<\/em><\/figcaption><\/figure>\n\n\n\n<figure class=\"wp-block-image size-full wp-duotone-unset-9\"><img loading=\"lazy\" decoding=\"async\" width=\"762\" height=\"203\" src=\"https:\/\/redero.fr\/wp-content\/uploads\/2024\/02\/sch_screen_toplevel-1.png\" alt=\"\" class=\"wp-image-467\" srcset=\"https:\/\/redero.fr\/wp-content\/uploads\/2024\/02\/sch_screen_toplevel-1.png 762w, https:\/\/redero.fr\/wp-content\/uploads\/2024\/02\/sch_screen_toplevel-1-300x80.png 300w\" sizes=\"auto, (max-width: 762px) 100vw, 762px\" \/><figcaption class=\"wp-element-caption\"><em>Top level<\/em><\/figcaption><\/figure>\n\n\n\n<figure class=\"wp-block-image size-full wp-duotone-unset-10\"><img loading=\"lazy\" decoding=\"async\" width=\"485\" height=\"61\" src=\"https:\/\/redero.fr\/wp-content\/uploads\/2024\/02\/sch_capscreen_soc.png\" alt=\"\" class=\"wp-image-466\" srcset=\"https:\/\/redero.fr\/wp-content\/uploads\/2024\/02\/sch_capscreen_soc.png 485w, https:\/\/redero.fr\/wp-content\/uploads\/2024\/02\/sch_capscreen_soc-300x38.png 300w\" sizes=\"auto, (max-width: 485px) 100vw, 485px\" \/><figcaption class=\"wp-element-caption\"><em>STM32-side schematics<\/em><\/figcaption><\/figure>\n\n\n\n<pre class=\"wp-block-code\"><code>&amp;i2c1 {\n        pinctrl-0 = &lt;&amp;i2c1_scl_pb6 &amp;i2c1_sda_pb7&gt;;\n        pinctrl-names = \"default\";\n        status = \"okay\";\n        clock-frequency = &lt;I2C_BITRATE_FAST&gt;;\n\n        stmpe811: stmpe811@41 {\n                compatible = \"st,stmpe811\";\n                status = \"okay\";\n                reg = &lt;0x41&gt;;\n                int-gpios = &lt;&amp;gpiob 5 GPIO_ACTIVE_LOW&gt;;\n                &#091;...]\n        };\n};<\/code><\/pre>\n\n\n\n<p class=\"has-text-align-right has-small-font-size\" style=\"margin-top:0;margin-bottom:0\"><em>boards\/arm\/st25dv_mb1283_disco\/st25dv_mb1283_disco.dts<\/em><\/p>\n\n\n\n<p>The reader got that we got SCL on pin I2C1_SCL PB6, SDA on pin I2C1_SCL PB7, and interrupt GPIO on PB5.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">I2C address<\/h3>\n\n\n\n<p>But how should we find this 0x41 address value? I got it from copy-pasting, but let\u2019s still find it on <a href=\"https:\/\/media.digikey.com\/pdf\/data%20sheets\/st%20microelectronics%20pdfs\/stmpe811.pdf\" target=\"_blank\" rel=\"noreferrer noopener\">STMPE811 datasheet<\/a>.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full wp-duotone-default-filter\"><img loading=\"lazy\" decoding=\"async\" width=\"630\" height=\"232\" src=\"https:\/\/redero.fr\/wp-content\/uploads\/2024\/02\/datasheet_stmpe811_i2c.png\" alt=\"\" class=\"wp-image-411\" srcset=\"https:\/\/redero.fr\/wp-content\/uploads\/2024\/02\/datasheet_stmpe811_i2c.png 630w, https:\/\/redero.fr\/wp-content\/uploads\/2024\/02\/datasheet_stmpe811_i2c-300x110.png 300w\" sizes=\"auto, (max-width: 630px) 100vw, 630px\" \/><\/figure>\n\n\n\n<figure class=\"wp-block-table\"><table><tbody><tr><td>ADDR0 <\/td><td>Address<\/td><\/tr><tr><td>0<\/td><td>0x82<\/td><\/tr><tr><td>1<\/td><td>0x84<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p>ADDR0 is either connected to GND (logical 0) or to VCC (logical 1). This is is given by the schematics: we need to check what is connected to ADDR0. Problem: we don\u2019t have any ADDR0 pin, the closest match is something called A0\/DATA_OUT. Back to datasheet, A0\/DATA_OUT is described like this:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><tbody><tr><td>Pin<\/td><td>Name<\/td><td>Function<\/td><\/tr><tr><td>3<\/td><td>AO\/Data Out<\/td><td>I2C address in Reset, Data out in SPI mode (VCC domain)<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p>A0 is described as \u00ab\u00a0I2C address\u00a0\u00bb, so A0 and ADDR0 are two names for the same pin.<\/p>\n\n\n\n<figure class=\"wp-block-image aligncenter size-full wp-duotone-unset-11\"><img loading=\"lazy\" decoding=\"async\" width=\"403\" height=\"418\" src=\"https:\/\/redero.fr\/wp-content\/uploads\/2024\/02\/sch_touchscreen_i2caddr.png\" alt=\"\" class=\"wp-image-412\" srcset=\"https:\/\/redero.fr\/wp-content\/uploads\/2024\/02\/sch_touchscreen_i2caddr.png 403w, https:\/\/redero.fr\/wp-content\/uploads\/2024\/02\/sch_touchscreen_i2caddr-289x300.png 289w\" sizes=\"auto, (max-width: 403px) 100vw, 403px\" \/><figcaption class=\"wp-element-caption\"><em>So, 0 or 1 ?<\/em><\/figcaption><\/figure>\n\n\n\n<p>R83, between AO and VCC, is marked as \u00ab\u00a0NC\u00a0\u00bb, aka \u00ab\u00a0Not Connected\u00a0\u00bb, so there should be no component stuck here. I can verify that my schematics is accurate on my board:<\/p>\n\n\n\n<figure class=\"wp-block-image aligncenter size-full wp-duotone-unset-12\"><img loading=\"lazy\" decoding=\"async\" width=\"248\" height=\"175\" src=\"https:\/\/redero.fr\/wp-content\/uploads\/2024\/02\/no_r83.png\" alt=\"\" class=\"wp-image-413\"\/><\/figure>\n\n\n\n<p>R86, between AO and GND, is a 0 \u03a9 resistor, same as a wire, so ADDR0 is 0.<\/p>\n\n\n\n<p>Our DTS I2C address is 0x41, and not 0x82. Why? Because in Zephyr RTOS, like in Linux, we use 7-bits I2C addresses, when in our datasheet <a href=\"https:\/\/www.totalphase.com\/support\/articles\/200349176-7-bit-8-bit-and-10-bit-i2c-slave-addressing\/\">8-bits addresses<\/a> format is used. In 8-bit, LSB is always a read\/write bit. The driver will actually use both 0x82 for write and 0x83 for read, but they are automatically selected from the 7-bit 0x41 \u00ab\u00a0root\u00a0\u00bb, 0x41 being 0x82 right-shifted of one bit.<\/p>\n\n\n\n<figure class=\"wp-block-image aligncenter size-full wp-duotone-unset-13\"><a href=\"https:\/\/www.totalphase.com\/support\/articles\/200349176-7-bit-8-bit-and-10-bit-i2c-slave-addressing\/\" target=\"_blank\" rel=\"noreferrer noopener\"><img loading=\"lazy\" decoding=\"async\" width=\"249\" height=\"85\" src=\"https:\/\/redero.fr\/wp-content\/uploads\/2024\/02\/i2c_addr.png\" alt=\"\" class=\"wp-image-414\"\/><\/a><\/figure>\n\n\n\n<p>Note: here, \u00ab\u00a0@41\u00a0\u00bb means @0x41, not 41 in decimal, same syntax as Linux device trees.<\/p>\n\n\n\n<details class=\"wp-block-details is-layout-flow wp-block-details-is-layout-flow\"><summary>Bonus: I2C shell<\/summary>\n<p>There is a quick way to check I2C addresses: the shell provides a command to scan the I2C bus.<\/p>\n\n\n\n<p>First, activate the shell and its I2C option:<\/p>\n\n\n\n<pre id=\"block-1e65e097-6984-4e16-987d-ac54f689a715\" class=\"wp-block-code\"><code>CONFIG_SHELL=y<br>CONFIG_I2C=y<br>CONFIG_I2C_SHELL=y<\/code><\/pre>\n\n\n\n<p class=\"has-text-align-right has-small-font-size\" style=\"margin-top:0;margin-bottom:0\"><em><em>game\/prj.conf<\/em><\/em><\/p>\n\n\n\n<p>Then, once connected:<\/p>\n\n\n\n<pre id=\"block-2a367449-9dd8-4fb4-ad2f-647f19561f33\" class=\"wp-block-code\"><code>uart:~$ device list<br>devices:<br>&#091;...]<br>- i2c@40005400 (READY)<\/code><\/pre>\n\n\n\n<pre id=\"block-fad3d0cf-d076-4bcd-9ee7-b1fef6010c97\" class=\"wp-block-code\"><code>uart:~$ i2c scan i2c@40005400<br>     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f<br>00:             -- -- -- -- -- -- -- -- -- -- -- -- <br>10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- <br>20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- <br>30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- <br>40: -- 41 -- -- -- -- -- -- -- -- -- -- -- -- -- -- <br>50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- <br>60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- <br>70: -- -- -- -- -- -- -- --                         <br>1 devices found on i2c@40005400<\/code><\/pre>\n<\/details>\n\n\n\n<h3 class=\"wp-block-heading\">Interrupt level<\/h3>\n\n\n\n<p>Another detail, the PB5 interrupt, is it active low or active high? Datasheet says it\u2019s open drain, but do not mention polarity.<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><tbody><tr><td>Pin<\/td><td>Name<\/td><td>Function<\/td><\/tr><tr><td>2<\/td><td>INT<\/td><td>Interrupt output (VCC domain), open drain<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<figure class=\"wp-block-image aligncenter size-full wp-duotone-unset-14\"><img loading=\"lazy\" decoding=\"async\" width=\"541\" height=\"242\" src=\"https:\/\/redero.fr\/wp-content\/uploads\/2024\/02\/sch_capscreen_int.png\" alt=\"\" class=\"wp-image-415\" srcset=\"https:\/\/redero.fr\/wp-content\/uploads\/2024\/02\/sch_capscreen_int.png 541w, https:\/\/redero.fr\/wp-content\/uploads\/2024\/02\/sch_capscreen_int-300x134.png 300w\" sizes=\"auto, (max-width: 541px) 100vw, 541px\" \/><\/figure>\n\n\n\n<p>The electronics engineer did not forget the pull-up, so by default, this pin will be a 1. In software, it has to be active low. <\/p>\n\n\n\n<p>If you configured stmpe811 output to be active high instead, you would force to 1 a line which was already set to 1, and it is an open-drain output, so logical 0 would be just leaving this line be&#8230; so it would be a 1 too.<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><tbody><tr><td>Logical level<\/td><td>0<\/td><td>1<\/td><\/tr><tr><td>Electrical level<\/td><td>1<\/td><td>1<\/td><\/tr><\/tbody><\/table><figcaption class=\"wp-element-caption\"><em>Pretty useless truth table<\/em><\/figcaption><\/figure>\n\n\n\n<p>Datasheet also indicates that polarity has to be selected in INT_CTRL register (0x09):<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p>[2] INT_POLARITY: This bit sets the INT pin polarity<\/p>\n\n\n\n<p>1: Active high\/rising edge<\/p>\n\n\n\n<p>0: Active low\/falling edge<\/p>\n<\/blockquote>\n\n\n\n<p>Default register value is 0x00, so 3rd register bit would be 0, I only want to check that the driver does not change this value to invert polarity.<\/p>\n\n\n\n<p>In STMPE811 driver:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>#define STMPE811_INT_CTRL_REG         0x09U\n&#091;...]\n#define STMPE811_INT_CTRL_BIT_GLOBAL_INT BIT(0)\n&#091;...]\n    \/* Enable global interrupts *\/\n    err = i2c_reg_write_byte_dt(&amp;config-&gt;bus, \n                  STMPE811_INT_CTRL_REG,\n                  STMPE811_INT_CTRL_BIT_GLOBAL_INT);\n&#091;...]<\/code><\/pre>\n\n\n\n<p class=\"has-text-align-right has-small-font-size\" style=\"margin-top:0;margin-bottom:0\"><em>zephyrproject\/zephyr\/drivers\/input\/input_stmpe811.c<\/em><\/p>\n\n\n\n<p>We write \u00ab\u00a0BIT(0)\u00a0\u00bb, aka 1, in the register, and the other bits are 0, including our 3rd bit. The driver agrees to be active-low.<\/p>\n\n\n\n<p>In order to request input source file compilation:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>CONFIG_INPUT=y<\/code><\/pre>\n\n\n\n<p class=\"has-text-align-right has-small-font-size\" style=\"margin-top:0;margin-bottom:0\"><em>game\/prj.conf<\/em><\/p>\n\n\n\n<h2 class=\"wp-block-heading\">4 directions + 1 clic button<\/h2>\n\n\n\n<p>This is not actually a joystick, it is not analog, but it is a clever little push button with 4 directions and 1 vertical click.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full wp-duotone-unset-15\"><img loading=\"lazy\" decoding=\"async\" width=\"737\" height=\"499\" src=\"https:\/\/redero.fr\/wp-content\/uploads\/2024\/02\/sch_joy_comp.png\" alt=\"\" class=\"wp-image-416\" srcset=\"https:\/\/redero.fr\/wp-content\/uploads\/2024\/02\/sch_joy_comp.png 737w, https:\/\/redero.fr\/wp-content\/uploads\/2024\/02\/sch_joy_comp-300x203.png 300w\" sizes=\"auto, (max-width: 737px) 100vw, 737px\" \/><figcaption class=\"wp-element-caption\"><em>Button-side schematics<\/em><\/figcaption><\/figure>\n\n\n\n<figure class=\"wp-block-image size-full wp-duotone-unset-16\"><img loading=\"lazy\" decoding=\"async\" width=\"401\" height=\"109\" src=\"https:\/\/redero.fr\/wp-content\/uploads\/2024\/02\/sch_joy_soc.png\" alt=\"\" class=\"wp-image-417\" srcset=\"https:\/\/redero.fr\/wp-content\/uploads\/2024\/02\/sch_joy_soc.png 401w, https:\/\/redero.fr\/wp-content\/uploads\/2024\/02\/sch_joy_soc-300x82.png 300w\" sizes=\"auto, (max-width: 401px) 100vw, 401px\" \/><figcaption class=\"wp-element-caption\"><em>STM32-side schematics<\/em> <em>&#8211; no top level, it\u2019s on the same page<\/em><\/figcaption><\/figure>\n\n\n\n<pre class=\"wp-block-code\"><code>        buttons: gpio_keys {\n                compatible = \"gpio-keys\";\n                sel_button: button_2 {\n                        label = \"Sel\";\n                        gpios = &lt;&amp;gpioe 8 GPIO_ACTIVE_LOW&gt;;\n                };\n                left_button: button_3 {\n                        label = \"Left\";\n                        gpios = &lt;&amp;gpioe 9 GPIO_ACTIVE_LOW&gt;;\n                };\n                right_button: button_4 {\n                        label = \"Right\";\n                        gpios = &lt;&amp;gpioe 11 GPIO_ACTIVE_LOW&gt;;\n                };\n                up_button: button_5 {\n                        label = \"Up\";\n                        gpios = &lt;&amp;gpioe 10 GPIO_ACTIVE_LOW&gt;;\n                };\n                down_button: button_6 {\n                        label = \"Down\";\n                        gpios = &lt;&amp;gpioe 12 GPIO_ACTIVE_LOW&gt;;\n                };\n        };<\/code><\/pre>\n\n\n\n<p class=\"has-text-align-right has-small-font-size\" style=\"margin-top:0;margin-bottom:0\"><em>boards\/st25dv_lvgl.overlay<\/em><\/p>\n\n\n\n<p>Wait, PE11 is named JOY_UP in schematics and \u00ab\u00a0right\u00a0\u00bb in my DTS, and PE10 is JOY_RIGHT and \u00ab\u00a0up\u00a0\u00bb in DTS. The error is not in DTS but in the schematics component, with this <a href=\"http:\/\/www.hy1688.com.tw\/SWITCH\/TACT%20SWITCH\/TACT_File\/MT-008A.pdf\" target=\"_blank\" rel=\"noreferrer noopener\">datasheet<\/a>. If we decide that pin A is \u00ab\u00a0LEFT\u00a0\u00bb, because we are free to place this component as we want in our board, pin order will be LEFT, UP, RIGHT, DOWN, and it matches A(1), B(4), D(6) et C(3).<\/p>\n\n\n\n<figure class=\"wp-block-gallery has-nested-images columns-default is-cropped wp-block-gallery-18 is-layout-flex wp-block-gallery-is-layout-flex\">\n<figure class=\"wp-block-image size-large wp-duotone-default-filter\"><img loading=\"lazy\" decoding=\"async\" width=\"232\" height=\"128\" data-id=\"419\" src=\"https:\/\/redero.fr\/wp-content\/uploads\/2024\/02\/datasheet_button2.png\" alt=\"\" class=\"wp-image-419\"\/><\/figure>\n\n\n\n<figure class=\"wp-block-image size-large wp-duotone-unset-17\"><img loading=\"lazy\" decoding=\"async\" width=\"347\" height=\"205\" data-id=\"418\" src=\"https:\/\/redero.fr\/wp-content\/uploads\/2024\/02\/datasheet_button.png\" alt=\"\" class=\"wp-image-418\" srcset=\"https:\/\/redero.fr\/wp-content\/uploads\/2024\/02\/datasheet_button.png 347w, https:\/\/redero.fr\/wp-content\/uploads\/2024\/02\/datasheet_button-300x177.png 300w\" sizes=\"auto, (max-width: 347px) 100vw, 347px\" \/><\/figure>\n<\/figure>\n\n\n\n<figure class=\"wp-block-image size-full wp-duotone-unset-19\"><img loading=\"lazy\" decoding=\"async\" width=\"123\" height=\"139\" src=\"https:\/\/redero.fr\/wp-content\/uploads\/2024\/02\/sch_joy_zoom_pin.png\" alt=\"\" class=\"wp-image-475\"\/><\/figure>\n\n\n\n<p>We can also tell Zephyr which keyboard code are associated to our pins with zephyr,code.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>#include &lt;zephyr\/dt-bindings\/input\/input-event-codes.h&gt;\n&#091;...]\n                left_button: button_3 {\n                        label = \"Left\";\n                        gpios = &lt;&amp;gpioe 9 GPIO_ACTIVE_LOW&gt;;\n                        zephyr,code = &lt;INPUT_KEY_LEFT&gt;;\n                };<\/code><\/pre>\n\n\n\n<p class=\"has-text-align-right has-small-font-size\" style=\"margin-top:0;margin-bottom:0\"><em>boards\/arm\/st25dv_mb1283_disco\/st25dv_mb1283_disco.dts<\/em><\/p>\n\n\n\n<p>Those codes are mostly the same as in Linux standards, if you launch \u00ab\u00a0evtest\u00a0\u00bb, you will see code 103 (KEY_UP) and code 108 (KEY_DOWN), in decimal this time.<\/p>\n\n\n\n<p>In zephyrproject\/zephyr\/include\/zephyr\/dt-bindings\/input\/input-event-codes.h :<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>#define INPUT_KEY_LEFT 105 \/**&lt; Left Key *\/\n#define INPUT_KEY_DOWN 108 \/**&lt; Down Key *\/<\/code><\/pre>\n\n\n\n<p class=\"has-text-align-right has-small-font-size\" style=\"margin-top:0;margin-bottom:0\"><em>zephyrproject\/zephyr\/include\/zephyr\/dt-bindings\/input\/input-event-codes.h<\/em><\/p>\n\n\n\n<p>In order to compile drivers, we set the prj.conf:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>CONFIG_GPIO=y<\/code><\/pre>\n\n\n\n<p class=\"has-text-align-right has-small-font-size\" style=\"margin-top:0;margin-bottom:0\"><em>game\/prj.conf<\/em><\/p>\n\n\n\n<details class=\"wp-block-details is-layout-flow wp-block-details-is-layout-flow\"><summary>Bonus: GPIO shell<\/summary>\n<p>The shell is very helpful to debug GPIO issues.<\/p>\n\n\n\n<p>First, activate shell and options:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>CONFIG_SHELL=y\nCONFIG_GPIO=y\nCONFIG_GPIO_SHELL=y<\/code><\/pre>\n\n\n\n<p class=\"has-text-align-right has-small-font-size\" style=\"margin-top:0;margin-bottom:0\">game\/prj.conf<\/p>\n\n\n\n<p>Once connected:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>uart:~$ gpio info \nLine         Reserved Device           Pin\n                         gpio@40020000     0\n                         gpio@40020000     1\n                         gpio@40020000     2\n                         gpio@40020000     3\n&#091;...]<\/code><\/pre>\n\n\n\n<p>I do not know register addresses by heart, but this is easy to find in the datasheet or in the pre-built DTS, zephyr.dts.pre:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>   gpioc: gpio@40020800 {\n    compatible = \"st,stm32-gpio\";\n    gpio-controller;\n    #gpio-cells = &lt;2&gt;;\n    reg = &lt;0x40020800 0x400&gt;;\n    clocks = &lt;&amp;rcc 0x030 0x00000004&gt;;\n   };  <\/code><\/pre>\n\n\n\n<p class=\"has-text-align-right has-small-font-size\" style=\"margin-top:0;margin-bottom:0\">build\/zephyr\/zephyr.dts.pre<\/p>\n\n\n\n<p>I want to check that I have the right GPIO and level for my blue button:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>gpios = &lt;&amp;gpioc 14 GPIO_ACTIVE_LOW&gt;;<\/code><\/pre>\n\n\n\n<p class=\"has-text-align-right has-small-font-size\" style=\"margin-top:0;margin-bottom:0\">boards\/arm\/st25dv_mb1283_disco\/st25dv_mb1283_disco.dts<\/p>\n\n\n\n<p>With button released:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>uart:~$ gpio get gpio@40020800 14\n1<\/code><\/pre>\n\n\n\n<p>I use gymnastics to simultaneously press the blue button and invoke the shell command:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>uart:~$ gpio get gpio@40020800 14\n0<\/code><\/pre>\n\n\n\n<p>My button setup is correct!<\/p>\n<\/details>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>LVGL, the middleware<\/strong><\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">Plumbing and pseudo-drivers<\/h3>\n\n\n\n<p>Once hardware is set up, I wanted to try the graphical module everyone talks about, LVGL.<\/p>\n\n\n\n<p>LVGL will find the display thanks to the zephyr,display we indicated, but we still have some pseudo-drivers to define to connect Zephyr system inputs to LVGL module.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>#include &lt;zephyr\/dt-bindings\/input\/input-event-codes.h&gt;\n#include &lt;zephyr\/dt-bindings\/lvgl\/lvgl.h&gt;\n\n\/ {\n        pointer {\n                compatible = \"zephyr,lvgl-pointer-input\";\n                input = &lt;&amp;stmpe811&gt;;\n                invert-y;\n        };\n\n        keypad {\n                compatible = \"zephyr,lvgl-keypad-input\";\n                input = &lt;&amp;buttons&gt;;\n                input-codes = &lt;INPUT_KEY_RIGHT INPUT_KEY_LEFT INPUT_KEY_UP INPUT_KEY_DOWN INPUT_KEY_ENTER&gt;;\n                lvgl-codes =  &lt;LV_KEY_DOWN LV_KEY_UP LV_KEY_RIGHT LV_KEY_LEFT LV_KEY_ENTER&gt;;\n        };\n};<\/code><\/pre>\n\n\n\n<p class=\"has-text-align-right has-small-font-size\" style=\"margin-top:0;margin-bottom:0\"><em>boards\/st25dv_lvgl.overlay<\/em><\/p>\n\n\n\n<p>Small adjustments were made: invert-y puts the touchscreen in the same direction as the display. I also rotated buttons by replacing names.<\/p>\n\n\n\n<p>As expected:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>CONFIG_LVGL=y<\/code><\/pre>\n\n\n\n<p class=\"has-text-align-right has-small-font-size\" style=\"margin-top:0;margin-bottom:0\"><em>game\/prj.conf<\/em><\/p>\n\n\n\n<p>But there are also lots of parameters to add to prj.conf for LVGL, as detailed to documentation and samples.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">LVGL input drivers<\/h3>\n\n\n\n<p>LVGL selects touchpad as its default input, controlled by \u00ab\u00a0zephyr,lvgl-pointer-input\u00a0\u00bb pseudo-driver. Other input peripherals can be added, in my case, pseudo-joystick button is handled by keyboard driver, that you can activate with:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>CONFIG_LV_Z_KEYPAD_INPUT=y<\/code><\/pre>\n\n\n\n<p class=\"has-text-align-right has-small-font-size\" style=\"margin-top:0;margin-bottom:0\"><em>game\/prj.conf<\/em><\/p>\n\n\n\n<p>In order to test this, a small sample able to display digits and move around them with directional buttons: zephyrproject\/zephyr\/samples\/subsys\/display\/lvgl<\/p>\n\n\n\n<figure class=\"wp-block-video\"><video height=\"1280\" style=\"aspect-ratio: 720 \/ 1280;\" width=\"720\" controls src=\"https:\/\/redero.fr\/wp-content\/uploads\/2024\/02\/hw_button_sample2.mp4\"><\/video><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">And now, an app<\/h3>\n\n\n\n<p>Now I can choose from all the existing LVGL app, I find a <a href=\"https:\/\/github.com\/100askTeam\/lv_lib_100ask.git\" target=\"_blank\" rel=\"noreferrer noopener\">Github LVGL project with a 2048 game<\/a>.<\/p>\n\n\n\n<p>Several copy-paste and minor adaptations later, 2048 displays on my screen, but I only have touchpad control, and resistive touchpad swipe is not fun.<\/p>\n\n\n\n<p>Button interrupts are received by my system, but I never jump in \u00ab\u00a0lv_100ask_2048_event\u00a0\u00bb, the LVGL application event handler. Yet, this function is ready to handle a push button even:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  else if(code == LV_EVENT_KEY)\n    {\n        game_2048-&gt;game_over = game_over(game_2048-&gt;matrix);\n        if (!game_2048-&gt;game_over)\n        {\n            switch(*((uint8_t *)lv_event_get_param(e)))\n            {\n                case LV_KEY_UP:\n                    success = move_left(&amp;(game_2048-&gt;score),           game_2048-&gt;matrix);\n                    break;<\/code><\/pre>\n\n\n\n<p class=\"has-text-align-right has-small-font-size\" style=\"margin-top:0;margin-bottom:0\"><em>game\/src\/lv_100ask_2048.c<\/em><\/p>\n\n\n\n<p>I was missing some lines:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>    lv_group_t *btn_matrix_group;\n    btn_matrix_group = lv_group_create();\n    lv_group_add_obj(btn_matrix_group, game_2048-&gt;btnm);\n    lv_indev_set_group(lvgl_input_get_indev(lvgl_keypad), btn_matrix_group);<\/code><\/pre>\n\n\n\n<p class=\"has-text-align-right has-small-font-size\" style=\"margin-top:0;margin-bottom:0\"><em>game\/src\/lv_100ask_2048.c<\/em><\/p>\n\n\n\n<p>Those lines create a widget group, add the main 2048 widget  (game_2048-&gt;btnm), and link the new group with my input peripheral. And now it works, since button click events are transmitted to event handler.<\/p>\n\n\n\n<h1 class=\"wp-block-heading\">Conclusion<\/h1>\n\n\n\n<p>I really wanted a Tetris&#8230;. but I learned cool new techs, I have a portable 2048, I got an excuse to go pestering the Zephyr community (many thanks to them!), I got my contributor badge, and I have an excuse to keep obsolete electronics in my drawer: it was a cool project.<\/p>\n\n\n\n<figure class=\"wp-block-video\"><video height=\"720\" style=\"aspect-ratio: 1280 \/ 720;\" width=\"1280\" controls src=\"https:\/\/redero.fr\/wp-content\/uploads\/2024\/02\/stm_2048.mp4\"><\/video><figcaption class=\"wp-element-caption\">Source code here: <a href=\"https:\/\/github.com\/everedero\/zephyr_lvgl_2048_st25dv\" target=\"_blank\" rel=\"noreferrer noopener\">https:\/\/github.com\/everedero\/zephyr_lvgl_2048_st25dv<\/a><\/figcaption><\/figure>\n\n\n\n<p>Update: board was merged in mainline! You can now use \u00ab\u00a0st25dv_mb1283_disco\u00a0\u00bb from tree<\/p>\n","protected":false},"excerpt":{"rendered":"<p>I refactor an old eval kit firmware with my new favorite RTOS, starting again from schematics, until I get a tiny game app running.<\/p>\n","protected":false},"author":1,"featured_media":456,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[49],"tags":[58,60,45,47],"class_list":["post-453","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-blog_en","tag-lvgl-en","tag-stm32-en","tag-video-en","tag-zephyr-en"],"_links":{"self":[{"href":"https:\/\/redero.fr\/index.php?rest_route=\/wp\/v2\/posts\/453","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=453"}],"version-history":[{"count":71,"href":"https:\/\/redero.fr\/index.php?rest_route=\/wp\/v2\/posts\/453\/revisions"}],"predecessor-version":[{"id":1301,"href":"https:\/\/redero.fr\/index.php?rest_route=\/wp\/v2\/posts\/453\/revisions\/1301"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/redero.fr\/index.php?rest_route=\/wp\/v2\/media\/456"}],"wp:attachment":[{"href":"https:\/\/redero.fr\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=453"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/redero.fr\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=453"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/redero.fr\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=453"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}