{"id":1172,"date":"2025-02-02T13:12:52","date_gmt":"2025-02-02T12:12:52","guid":{"rendered":"https:\/\/redero.fr\/?p=1172"},"modified":"2025-09-19T21:53:05","modified_gmt":"2025-09-19T19:53:05","slug":"display-on-zephyr","status":"publish","type":"post","link":"https:\/\/redero.fr\/?p=1172&lang=en","title":{"rendered":"Display software implementation using Zephyr RTOS"},"content":{"rendered":"\n<p>Now that you know everything on display modules, controllers, and what you can buy on the market thanks to my previous article <a href=\"https:\/\/redero.fr\/?p=1012&amp;lang=en\" data-type=\"post\" data-id=\"1012\">here<\/a>, let us see in details how to add it to your project with an example.<\/p>\n\n\n\n<p>Here is my use case: I have a DIY project for which I need an HMI. I need to display parameters, text, numbers, and maybe simple graphics. I want a cheap option and I don\u2019t need anything fancy like RGB or animations.<\/p>\n\n\n\n<p>Furthermore, I want out-of-the box Zephyr support, which leads to the next question: how do I know if a reference is supported or not?<\/p>\n\n\n\n\n\n<h2 class=\"wp-block-heading\">Is my display module supported?<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">Need a controller reference<\/h3>\n\n\n\n<p>I found this <a href=\"https:\/\/www.lcsc.com\/product-detail\/OLED-Display_Newvisio-X154-2864KLBTG01-C24_C5123568.html?s_z=n_X154-2864KLBTG01-C24\" target=\"_blank\" rel=\"noreferrer noopener\">NewVision X154-2864KLBTG01-C24<\/a> on LCSC, it is a monochrome 128&#215;64 OLED display for less than 6 \u20ac, which is fine by me.<\/p>\n\n\n\n<p><a href=\"https:\/\/www.lcsc.com\/datasheet\/lcsc_datasheet_2411200137_Newvisio-X154-2864KLBTG01-C24_C5123568.pdf\" target=\"_blank\" rel=\"noreferrer noopener\">Here is its datasheet<\/a>.<\/p>\n\n\n\n<p>There is a display controller mentioned somewhere, you can play Where\u2019s Waldo with the datasheet if you like games. Waldo looks like this:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Driver IC: SSD1309<\/code><\/pre>\n\n\n\n<details class=\"wp-block-details is-layout-flow wp-block-details-is-layout-flow\"><summary>Click here to see the solution<\/summary>\n<p>On page 5, under the mechanical drawing, bottom left side \u00ab\u00a0Notes\u00a0\u00bb section if you turn the page in landscape mode.<\/p>\n<\/details>\n\n\n\n<p>So my controller is \u00ab\u00a0SSD1309\u00a0\u00bb, and a quick Internet search gives me its manufacturer name, which is Solomon, and <a href=\"https:\/\/www.solomon-systech.com\/product\/ssd1309\/\">its datasheet.<\/a><\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Yes, but is it supported?<\/h3>\n\n\n\n<p>Zephyr support can sometimes be tricky to determine.<\/p>\n\n\n\n<p>The reason is that products are declined into product ranges, so, the same software driver can handle different products. You will have different scenarios:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Wildcard driver names making things difficult. Solomon SSD1608 or SSD1681 support is provided by ssd16xx.c, so you will need grep to find if ssd1681 is actually mentioned somewhere.<\/li>\n<\/ul>\n\n\n\n<pre class=\"wp-block-code\"><code>&gt; grep -rnwi .\/ -e \".*ssd1681.*\"\n.\/drivers\/display\/ssd16xx.c:994:#if DT_HAS_COMPAT_STATUS_OKAY(solomon_ssd1681)<\/code><\/pre>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Your driver support has never been tested and is not anywhere to be found, but there is something pretty close that could be compatible. It sounds far-fetched, so why would it work?\n<ul class=\"wp-block-list\">\n<li>Manufacturers often use the same logic core and same register map when they create a range, just changing a few peripheral and memory things.<\/li>\n\n\n\n<li>Sometimes chips are updated and the reference is changed, but the global behaviour is pretty similar to the previous version.<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n\n\n\n<p>In my care, grepping SSD1309 gave out nothing. <\/p>\n\n\n\n<p class=\"has-medium-font-size\"><em>It does now, actually, but it was merged afterwards, so let\u2019s pretend it does not exist.<\/em><\/p>\n\n\n\n<p>There is an interesting file called ssd1306.c in the display drivers folder, that I found out by gradually removing numbers from the product number. Same manufacturer, same type of product: maybe luck is on our side.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>grep -rnwi .\/ -e \".*ssd1309.*\"\ngrep -rnwi .\/ -e \".*ssd130.*\"\ngrep -rnwi .\/ -e \".*ssd13.*\"\ngrep -rnwi .\/ -e \".*ssd1.*\"\ngrep -rnwi .\/ -e \".*ssd.*\"\ngrep -rnwi .\/ -e \".*s.*\" \/\/ Despair<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Okay cool, but is it supported?<\/h3>\n\n\n\n<p>So we got a driver that could maybe bring support for our controller.<\/p>\n\n\n\n<p>The detective work is not over! We are now going to look for the <a href=\"https:\/\/cdn-shop.adafruit.com\/datasheets\/SSD1306.pdf\" target=\"_blank\" rel=\"noreferrer noopener\">SSD1306 datasheet<\/a>, and compare it with <a href=\"https:\/\/orientdisplay.com\/wp-content\/uploads\/2020\/11\/SSD1309.pdf\" target=\"_blank\" rel=\"noreferrer noopener\">SSD1309<\/a>. Do you know the game of differences? Same here, but with register maps.<\/p>\n\n\n\n<figure data-wp-context=\"{&quot;imageId&quot;:&quot;69f34e7ba19a8&quot;}\" data-wp-interactive=\"core\/image\" data-wp-key=\"69f34e7ba19a8\" class=\"wp-block-image size-large wp-duotone-unset-1 wp-lightbox-container\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"493\" 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\/2025\/02\/display_diff-1024x493.png\" alt=\"\" class=\"wp-image-1177\" srcset=\"https:\/\/redero.fr\/wp-content\/uploads\/2025\/02\/display_diff-1024x493.png 1024w, https:\/\/redero.fr\/wp-content\/uploads\/2025\/02\/display_diff-300x144.png 300w, https:\/\/redero.fr\/wp-content\/uploads\/2025\/02\/display_diff-768x370.png 768w, https:\/\/redero.fr\/wp-content\/uploads\/2025\/02\/display_diff-1536x739.png 1536w, https:\/\/redero.fr\/wp-content\/uploads\/2025\/02\/display_diff.png 1855w\" 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 class=\"has-text-align-right has-small-font-size\" style=\"margin-top:0;margin-bottom:0\">Left: SSD1309, right: SSD1306<\/p>\n\n\n\n<p>For instance, we can see 0x81 is \u00ab\u00a0Set Constrast Control\u00a0\u00bb, and 0xA5 is \u00ab\u00a0Entire display ON\u00a0\u00bb, in both case. If most commands are identical in both products, the driver will most certainly be compatible.<\/p>\n\n\n\n<p>Another technique consists in comparing the SSD1309 datasheet directly with the ssd1306 header file.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>#define SSD1306_SET_CONTRAST_CTRL       0x81 \/* double byte command *\/\n#define SSD1306_SET_ENTIRE_DISPLAY_OFF      0xa4\n#define SSD1306_SET_ENTIRE_DISPLAY_ON       0xa5<\/code><\/pre>\n\n\n\n<p class=\"has-text-align-right has-small-font-size\" style=\"margin-top:0;margin-bottom:0\">ssd1306_regs.h<\/p>\n\n\n\n<p>I like to do a little bit of both. <\/p>\n\n\n\n<p>Also, now that I have both datasheets, I can try to guess what is the difference between the two references. My guess is hidden, if you want to play detective yourself.<\/p>\n\n\n\n<details class=\"wp-block-details is-layout-flow wp-block-details-is-layout-flow\"><summary>My guess<\/summary>\n<figure data-wp-context=\"{&quot;imageId&quot;:&quot;69f34e7ba205d&quot;}\" data-wp-interactive=\"core\/image\" data-wp-key=\"69f34e7ba205d\" class=\"wp-block-image size-large wp-duotone-unset-2 wp-lightbox-container\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"349\" 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\/2025\/02\/controller_size_compare-1024x349.png\" alt=\"\" class=\"wp-image-1178\" srcset=\"https:\/\/redero.fr\/wp-content\/uploads\/2025\/02\/controller_size_compare-1024x349.png 1024w, https:\/\/redero.fr\/wp-content\/uploads\/2025\/02\/controller_size_compare-300x102.png 300w, https:\/\/redero.fr\/wp-content\/uploads\/2025\/02\/controller_size_compare-768x262.png 768w, https:\/\/redero.fr\/wp-content\/uploads\/2025\/02\/controller_size_compare-1536x524.png 1536w, https:\/\/redero.fr\/wp-content\/uploads\/2025\/02\/controller_size_compare.png 1753w\" 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>The die size is different, SSD1309 is smaller. Also, the datasheet release for SSD1306 is \u00ab\u00a0Apr 2008\u00a0\u00bb for SSD1306 and the one for SSD1309 is \u00ab\u00a0Jul 2011\u00a0\u00bb. So I would say it was re-printed with a more miniaturized tech.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n<\/details>\n\n\n\n<h3 class=\"wp-block-heading\">But are you really sure it is supported?<\/h3>\n\n\n\n<p>In the first datasheet, <a href=\"https:\/\/www.lcsc.com\/product-detail\/OLED-Display_Newvisio-X154-2864KLBTG01-C24_C5123568.html?s_z=n_X154-2864KLBTG01-C24\" target=\"_blank\" rel=\"noreferrer noopener\">the module one<\/a>, we managed to find a controller reference. But is it reliable, not a typo or a deprecated information?<\/p>\n\n\n\n<p>And what happens if there is no such reference in the datasheet?<\/p>\n\n\n\n<p>And also, there is no register map section, so how do we know all the module specific parameters to put into the device tree?<\/p>\n\n\n\n<p>Look closely at the pseudo-code section on pages 20 and 21.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>write_i(0x81); \/* set contrast control *\/\nwrite_i(0x32);<\/code><\/pre>\n\n\n\n<p>Here, the pseudo-code tells to set register 0x81, the one Zephyr names SSD1306_SET_CONTRAST_CTRL, to 0x32. You can decipher this pseudo-code with the SSD1309 datasheet and see if it makes sense.<\/p>\n\n\n\n<p>\u24d8 I have been doing a lot of CTRL-F search in PDF files. If you adopt this technique, remember that sometimes hexadecimal values are written \u00ab\u00a0XXh\u00a0\u00bb instead of \u00ab\u00a00xXX\u00a0\u00bb, like here. So you have to look for 81h: \u00ab\u00a0Set Contrast Control for BANK0 (81h)\u00a0\u00bb.<\/p>\n\n\n\n<p>I am now pretty sure that the SSD1306 driver will also apply to my SSD1309-based module!<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Set up the device tree and driver configuration<\/h2>\n\n\n\n<p>The display has been chosen, the board has been designed, ordered and received, it is now time to actually configure and test the Zephyr driver.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Module-specific configuration<\/h3>\n\n\n\n<p>There are a lot of parameters to feed to the device tree, that will be specific to the module, for instance the display size and horizontality\/verticality status.<\/p>\n\n\n\n<p>For this we will get back to the pseudo-code provided by the display manufacturer. How do we convert it to Zephyr?<\/p>\n\n\n\n<details class=\"wp-block-details is-layout-flow wp-block-details-is-layout-flow\"><summary>See full pseudo-code here<\/summary>\n<pre class=\"wp-block-code\"><code>void SSD1309 ()\n{\nRES=0;\ndelay(1000);\nRES=1;\ndelay(1000);\nwrite_i(0xae); \/* set display off *\/\nwrite_i(0x00); \/* set lower column start address *\/\nwrite_i(0x10); \/* set higher column start address *\/\nwrite_i(0x40); \/* set display start line *\/\n\nwrite_i(0x81); \/* set contrast control *\/\nwrite_i(0x32);\n\nwrite_i(0xa1); \/* set segment remap *\/ 127 to seg 0, remapped = segment-remap\nwrite_i(0xa6); \/* set normal display *\/ A6 = normal, not reversed SET_SEGMENT_MAP_REMAPED SET_NORMAL_DISPLAY (bit inverse or not) inversion-on;\n\nwrite_i(0xa8); \/* set multiplex ratio *\/\nwrite_i(0x3f); \/* 1\/64 *\/\n\nwrite_i(0xc8); \/* set com scan direction *\/ C8 = remapped SET_COM_OUTPUT_SCAN_FLIPPED com_invdir\nwrite_i(0xd3); \/* set display offset\nwrite_i(0x00); *\/\n\nwrite_i(0xd5); \/* set display clock divide\/oscillator frequency *\/\nwrite_i(0xa0); \/* clock div ratio *\/\n\nwrite_i(0xD9); \/* set charge period\nwrite_i(0xF1); \/* prechargep *\/\n\nwrite_i(0xda); \/* set com pin configuartion *\/ ?\nleft\/right remap (A&#091;4] et A&#091;5] - Scan direction top or bottom (C0h ou C8h)\nwrite_i(0x12); \/\/ Enable COM Left\/Right remap (DAh A&#091;5] =1)\n\nwrite_i(0x91);\nwrite_i(0x3F);\nwrite_i(0x3F);\nwrite_i(0x3F);\nwrite_i(0x3F);\n<\/code><\/pre>\n<\/details>\n\n\n\n<h4 class=\"wp-block-heading\">Configuration parameters<\/h4>\n\n\n\n<p>Let\u2019s see this contrast setting again.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>write_i(0x81); \/* set contrast control *\/\nwrite_i(0x32);<\/code><\/pre>\n\n\n\n<ul class=\"wp-block-list\">\n<li>In ssd1306_regs.h, the 0x81 magic number is given a cool name, SSD1306_SET_CONTRAST_CTRL.<\/li>\n<\/ul>\n\n\n\n<pre class=\"wp-block-code\"><code>#define SSD1306_SET_CONTRAST_CTRL 0x81 \/* double byte command *\/<\/code><\/pre>\n\n\n\n<p class=\"has-text-align-right has-small-font-size\" style=\"margin-top:0;margin-bottom:0\">ssd1306_regs.h<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>In ssd1306.c, the SSD1306_SET_CONTRAST_CTRL byte is written by the function ssd1306_set_contrast.<\/li>\n\n\n\n<li>This function is called at init by:<\/li>\n<\/ul>\n\n\n\n<pre class=\"wp-block-code\"><code>ssd1306_set_contrast(dev, CONFIG_SSD1306_DEFAULT_CONTRAST)<\/code><\/pre>\n\n\n\n<p class=\"has-text-align-right has-small-font-size\" style=\"margin-top:0;margin-bottom:0\">ssd1306.c<\/p>\n\n\n\n<p>CONFIG_SSD1306_DEFAULT_CONTRAST is a configuration parameter. It can be modified via any of your project Kconfig file, but I would suggest using the one living close to your dts file.<\/p>\n\n\n\n<p>I can now add the recommended manufacturer value 0x32 to  myboard.conf, in decimal, so it is a 50.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>CONFIG_SSD1306_DEFAULT_CONTRAST=50<\/code><\/pre>\n\n\n\n<p class=\"has-text-align-right has-small-font-size\" style=\"margin-top:0;margin-bottom:0\">myboard.conf<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Device tree parameters<\/h4>\n\n\n\n<p>Another pseudo-code line states:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>write_i(0xa8); \/* set multiplex ratio *\/\nwrite_i(0x3f); \/* 1\/64 *\/<\/code><\/pre>\n\n\n\n<p>Multiplex ratio is either width or height, minus one, depending on how your display is wired to the controller.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>#define SSD1306_SET_MULTIPLEX_RATIO 0xa8 \/* double byte command *\/<\/code><\/pre>\n\n\n\n<p class=\"has-text-align-right has-small-font-size\" style=\"margin-top:0;margin-bottom:0\">ssd1306_regs.h<\/p>\n\n\n\n<p>This time, the byte is written by the function ssd1306_set_hardware_config. It writes a value called config-&gt;multiplex_ratio, and config is from the driver API, the struct device config field. So, it is read from the device tree.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&#091;...]\nSSD1306_SET_MULTIPLEX_RATIO,\nconfig-&gt;multiplex_ratio,\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\">ssd1306.c<\/p>\n\n\n\n<p>You can also find it in the driver bindings, and it is required:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>multiplex-ratio:\n    type: int\n    required: true\n    description: Multiplex Ratio<\/code><\/pre>\n\n\n\n<p class=\"has-text-align-right has-small-font-size\" style=\"margin-top:0;margin-bottom:0\">dts\/bindings\/display\/solomon,ssd1306fb-common.yaml<\/p>\n\n\n\n<p>Here is the 0x3f value I put in the device tree. It has to be decimal too, and 0x3f = 63.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>ssd1306: ssd1306@0 {\n    &#091;...]\n    multiplex-ratio = &lt;63&gt;;<\/code><\/pre>\n\n\n\n<p class=\"has-text-align-right has-small-font-size\" style=\"margin-top:0;margin-bottom:0\">myboard.dts<\/p>\n\n\n\n<p>\u24d8 Note that the device tree configuration for this category of drivers include 2 different types of hardware information:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Module-specific parameters<\/li>\n\n\n\n<li>Board-specific parameters<\/li>\n<\/ul>\n\n\n\n<h4 class=\"wp-block-heading\">What if there is no pseudo-code or instructions in the datasheet?<\/h4>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Look on the Internet. Maybe your module has some actual driver code somewhere? An Arduino implementation, maybe?<\/li>\n\n\n\n<li>Send an e-mail to the manufacturer.<\/li>\n<\/ul>\n\n\n\n<p class=\"has-small-font-size\">Be polite, concise, don\u2019t make drama and just ask for what you need.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Board-specific configuration<\/h3>\n\n\n\n<p>Some of those parameters are related to your board, and not to the module. For instance, maximum SPI speed depends on your SoC SPI peripheral max speed, the data\/command GPIO and reset GPIO depends on your schematics.<\/p>\n\n\n\n<p>Some drivers are even implemented over multiple interfaces, with bindings variants. The SSD1306 driver supports both I2C and SPI:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>solomon,ssd1306fb-common.yaml<\/li>\n\n\n\n<li>solomon,ssd1306fb-i2c.yaml<\/li>\n\n\n\n<li>solomon,ssd1306fb-spi.yaml<\/li>\n<\/ul>\n\n\n\n<p>The protocol used to communicate (SPI \/ I2C \/ 8080-I or II) is usually selected by a resistor, so the schematics, resistor option and driver configuration have to match.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&amp;spi2 {\n        status = \"okay\";\n        pinctrl-0 = &lt;&amp;spi2_sck_pb10 &amp;spi2_mosi_pb15&gt;;\n        pinctrl-names = \"default\";\n        cs-gpios = &lt;&amp;gpioc 7 (GPIO_ACTIVE_LOW | GPIO_PULL_DOWN)&gt;;\n\n        ssd1306: ssd1306@0 {\n        &#091;...]\n        }\n}<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Debugging<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">Todo list<\/h3>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Check power supply voltages<\/li>\n\n\n\n<li>Check the reset line voltage. Try inverting it, just in case you got lost with the not-not thing (again)\n<ul class=\"wp-block-list\">\n<li>nRST = 0 means \u00ab\u00a0not reset is false\u00a0\u00bb which means \u00ab\u00a0reset is not active\u00a0\u00bb which means \u00ab\u00a0chip is active\u00a0\u00bb<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li>Also check that the other GPIOs (data\/command, SPI chip select) are not inverted too.<\/li>\n\n\n\n<li>For displays in particular, debug with your eyes. If something psychedelic is displayed, try to understand why. Change the sample, play with some parameters, and try to make sense of that.<\/li>\n\n\n\n<li>Set up your logic analyzer to spy on the SPI port and the other relevant GPIOs like D\/C. In my case it involved soldering cables on testpoints.<\/li>\n<\/ol>\n\n\n\n<h3 class=\"wp-block-heading\">Case study: I have an issue<\/h3>\n\n\n\n<p>I checked voltages and resets and got the display to light on and display weird stuff.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Open your eyes<\/h4>\n\n\n\n<p>The monochrome driver\/display sample from Zephyr is supposed to look like this:<\/p>\n\n\n\n<figure class=\"wp-block-video\"><video height=\"42\" style=\"aspect-ratio: 84 \/ 42;\" width=\"84\" controls src=\"https:\/\/redero.fr\/wp-content\/uploads\/2025\/02\/small_mono01_64x128_ok.mp4\"><\/video><\/figure>\n\n\n\n<p>And I got this:<\/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\/2025\/02\/display_bug1.webm\"><\/video><\/figure>\n\n\n\n<p>Most of the display is covered by few random black pixels on a white background, like some kind of noise. It turns out it is what appears after a hard reset, and power does not come back immediately. I suppose this it what happens when the module RAM is erased.<\/p>\n\n\n\n<p>But weirdly a small area of the display is active, and we can partly see the blinking rectangle.<\/p>\n\n\n\n<p>It is not only one pixel line&#8230; by looking closely, I could count the number of vertical pixels in this area: 8. 8, like a byte. On monochrome displays each pixel is a bit, and we are sending bytes, so accessing a single pixel line is impossible. We can only access a bunch of 8 lines, called a \u00ab\u00a0page\u00a0\u00bb in the datasheet. This display has 64 vertical bits, so 8 pages. And here what we see is page 0 being written and not the other ones!<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Logic analyzer<\/h4>\n\n\n\n<p>After I soldered small cables to the SPI testpoints and slowed down my SPI bus speed for a better capture, I could spy on the bus.<\/p>\n\n\n\n<p>I verified once again my startup command values, in hexadecimal magic numbers, so it is a good time to check if the Zephyr configuration is all right.<\/p>\n\n\n\n<p>Then I moved on to data transfers.<\/p>\n\n\n\n<figure data-wp-context=\"{&quot;imageId&quot;:&quot;69f34e7ba3a98&quot;}\" data-wp-interactive=\"core\/image\" data-wp-key=\"69f34e7ba3a98\" class=\"wp-block-image size-large wp-duotone-unset-3 wp-lightbox-container\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"453\" 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\/2025\/02\/display_driver_origins_data-1024x453.png\" alt=\"\" class=\"wp-image-1189\" srcset=\"https:\/\/redero.fr\/wp-content\/uploads\/2025\/02\/display_driver_origins_data-1024x453.png 1024w, https:\/\/redero.fr\/wp-content\/uploads\/2025\/02\/display_driver_origins_data-300x133.png 300w, https:\/\/redero.fr\/wp-content\/uploads\/2025\/02\/display_driver_origins_data-768x340.png 768w, https:\/\/redero.fr\/wp-content\/uploads\/2025\/02\/display_driver_origins_data.png 1235w\" 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 class=\"has-text-align-right has-small-font-size\" style=\"margin-top:0;margin-bottom:0\">D\/C line not shown because I tore the testpoint away&#8230; <br>The first batch is command, the 0x0F is data<\/p>\n\n\n\n<p>Each data batch starts by the following command:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>20 00 12 00 7F 22 05 05<\/code><\/pre>\n\n\n\n<p>20h sets up the memory addressing mode. Mode 0x00 means that the pixel pointer auto-increments across columns, then across lines, so you just have to set start and stop coordinates and send all of your pixel in a rows. Different drivers can have different communication requirements, and sometimes you can only update line by line.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full is-resized wp-duotone-unset-4\"><img loading=\"lazy\" decoding=\"async\" width=\"497\" height=\"172\" src=\"https:\/\/redero.fr\/wp-content\/uploads\/2025\/02\/mode_0_auto_increment.png\" alt=\"\" class=\"wp-image-1205\" style=\"width:600px\" srcset=\"https:\/\/redero.fr\/wp-content\/uploads\/2025\/02\/mode_0_auto_increment.png 497w, https:\/\/redero.fr\/wp-content\/uploads\/2025\/02\/mode_0_auto_increment-300x104.png 300w\" sizes=\"auto, (max-width: 497px) 100vw, 497px\" \/><\/figure>\n\n\n\n<p>When I finally understood that the SSD1309 supports 3 different ways of batch sending data, I was getting the urge to try out another one. I thought I had to change the driver code but I was luckier that expected.<\/p>\n\n\n\n<p>The SSD1306 driver happens to bring support for not only Solomon products, but also for something called Sinowealth SH1106. Let\u2019s compare <a href=\"https:\/\/www.displayfuture.com\/Display\/datasheet\/controller\/SH1106.pdf\" target=\"_blank\" rel=\"noreferrer noopener\">its datasheet<\/a> with SSD1306\u2019s.<\/p>\n\n\n\n<figure data-wp-context=\"{&quot;imageId&quot;:&quot;69f34e7ba407a&quot;}\" data-wp-interactive=\"core\/image\" data-wp-key=\"69f34e7ba407a\" class=\"wp-block-image size-large wp-duotone-unset-5 wp-lightbox-container\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"469\" 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\/2025\/02\/datasheet_compare_sinowealth-1024x469.png\" alt=\"\" class=\"wp-image-1218\" srcset=\"https:\/\/redero.fr\/wp-content\/uploads\/2025\/02\/datasheet_compare_sinowealth-1024x469.png 1024w, https:\/\/redero.fr\/wp-content\/uploads\/2025\/02\/datasheet_compare_sinowealth-300x138.png 300w, https:\/\/redero.fr\/wp-content\/uploads\/2025\/02\/datasheet_compare_sinowealth-768x352.png 768w, https:\/\/redero.fr\/wp-content\/uploads\/2025\/02\/datasheet_compare_sinowealth-1536x704.png 1536w, https:\/\/redero.fr\/wp-content\/uploads\/2025\/02\/datasheet_compare_sinowealth.png 1793w\" 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 class=\"has-text-align-right has-small-font-size\" style=\"margin-top:0;margin-bottom:0\">Left: SH1106 datasheet, right: SSD1306 datasheet<\/p>\n\n\n\n<p>In the SH1106 version, we only have support for \u00ab\u00a0page addressing mode\u00a0\u00bb, where data has to be sent <s>line by line<\/s> 8 lines by 8 lines. By default, the Zephyr driver uses the fancy auto-increment, but it has a specific \u00ab\u00a0ssd1306_write_sh1106\u00a0\u00bb if you set your compatible to sinowealth,sh1106.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full is-resized wp-duotone-unset-6\"><img loading=\"lazy\" decoding=\"async\" width=\"652\" height=\"226\" src=\"https:\/\/redero.fr\/wp-content\/uploads\/2025\/02\/ssd1306_page_addressing.png\" alt=\"\" class=\"wp-image-1207\" style=\"width:600px\" srcset=\"https:\/\/redero.fr\/wp-content\/uploads\/2025\/02\/ssd1306_page_addressing.png 652w, https:\/\/redero.fr\/wp-content\/uploads\/2025\/02\/ssd1306_page_addressing-300x104.png 300w\" sizes=\"auto, (max-width: 652px) 100vw, 652px\" \/><\/figure>\n\n\n\n<p>So I tried with the sinowealth,sh1106 compatible, which uses the \u00ab\u00a0page addressing mode\u00a0\u00bb, and it worked.<\/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\/2025\/02\/ok_monochrome_big.mp4\"><\/video><\/figure>\n\n\n\n<p>I think my display actually ships with the Sinowealth chip, or another brand, not the Solomon. Maybe they are marked as equivalent references and it depends on production runs, maybe the datasheet is wrong or not updated, or maybe the manufacturer bought chips from a shady component broker who changed the labels to get more margin. Anyway, now I know that this module will not work with the auto-page increment!<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Zephyr display subsystems and samples<\/h2>\n\n\n\n<p>Zephyr has 2 main display abstractions:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>CFB or Character Frame Buffer allows you to create text or simple graphics easily<\/li>\n\n\n\n<li>LVGL is a complete GUI API, including keys or touchscreen interactions.<\/li>\n<\/ul>\n\n\n\n<p>Before trying out any of those:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Run the samples\/drivers\/display code for your board<\/li>\n\n\n\n<li>Set up the <a href=\"https:\/\/docs.zephyrproject.org\/latest\/boards\/native\/native_sim\/doc\/index.html\">Zephyr simulator<\/a> to be able to compare expectations VS reality.<\/li>\n<\/ol>\n\n\n\n<pre class=\"wp-block-code\"><code>west build -b native_sim\/native\/64 -p always .\/samples\/driver\/display\n.\/build\/zephyr\/zephyr.exe --rt-ratio=10<\/code><\/pre>\n\n\n\n<p>The drivers\/display should look like this on RGB 64&#215;128:<\/p>\n\n\n\n<figure class=\"wp-block-video\"><video height=\"42\" style=\"aspect-ratio: 84 \/ 42;\" width=\"84\" controls src=\"https:\/\/redero.fr\/wp-content\/uploads\/2025\/02\/small_default_64x128_ok.mp4\"><\/video><\/figure>\n\n\n\n<p class=\"has-text-align-right has-small-font-size\" style=\"margin-top:0;margin-bottom:0\">samples\/driver\/display<\/p>\n\n\n\n<p>And on monochrome 64&#215;128:<\/p>\n\n\n\n<figure class=\"wp-block-video\"><video height=\"42\" style=\"aspect-ratio: 84 \/ 42;\" width=\"84\" controls src=\"https:\/\/redero.fr\/wp-content\/uploads\/2025\/02\/small_mono01_64x128_ok-1.mp4\"><\/video><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">CFB sample<\/h3>\n\n\n\n<p>Monochrome 64&#215;128:<\/p>\n\n\n\n<figure class=\"wp-block-video\"><video height=\"42\" style=\"aspect-ratio: 84 \/ 42;\" width=\"84\" controls src=\"https:\/\/redero.fr\/wp-content\/uploads\/2025\/02\/sample_cfb_64x128_ok.mp4\"><\/video><\/figure>\n\n\n\n<p class=\"has-text-align-right has-small-font-size\" style=\"margin-top:0;margin-bottom:0\">samples\/subsys\/display\/cfb<\/p>\n\n\n\n<p>Monochrome 360&#215;240:<\/p>\n\n\n\n<figure class=\"wp-block-video\"><video height=\"160\" style=\"aspect-ratio: 210 \/ 160;\" width=\"210\" controls src=\"https:\/\/redero.fr\/wp-content\/uploads\/2025\/02\/sample_cfb_360x240_ok.mp4\"><\/video><\/figure>\n\n\n\n<p class=\"has-text-align-right has-small-font-size\" style=\"margin-top:0;margin-bottom:0\">samples\/subsys\/display\/cfb<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">LVGL sample<\/h3>\n\n\n\n<p>\u26a0 Don\u2019t forget to disable the CFB module first. Both subsystems cannot coexist.<\/p>\n\n\n\n<p>Here is the monochrome LVGL Hello World sample. If you add input subsystems, it will also display them and make them react to inputs.<\/p>\n\n\n\n<figure class=\"wp-block-video\"><video height=\"42\" style=\"aspect-ratio: 84 \/ 42;\" width=\"84\" controls src=\"https:\/\/redero.fr\/wp-content\/uploads\/2025\/02\/lvgl_hello_64x128_ok.mp4\"><\/video><\/figure>\n\n\n\n<p class=\"has-text-align-right has-small-font-size\" style=\"margin-top:0;margin-bottom:0\">samples\/subsys\/display\/lvgl\/<\/p>\n\n\n\n<p>There is another LVGL sample, more optimized for big RGB displays, here in 360&#215;240:<\/p>\n\n\n\n<figure class=\"wp-block-video\"><video height=\"240\" style=\"aspect-ratio: 320 \/ 240;\" width=\"320\" controls src=\"https:\/\/redero.fr\/wp-content\/uploads\/2025\/02\/lvgl_musicplayer_default_ok.mp4\"><\/video><\/figure>\n\n\n\n<p class=\"has-text-align-right has-small-font-size\" style=\"margin-top:0;margin-bottom:0\">samples\/modules\/lvgl\/<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Conclusion<\/h2>\n\n\n\n<p>As a complete display newbie, buying my first display and making it work sure was an adventure. However, I did not have to write one single line of code, I could choose the display I really wanted, and it all worked out beautifully and in no time. I am convinced that Zephyr\u2019s excellent support will change the experience we have with displays in embedded systems.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Project repository<\/h2>\n\n\n\n<p>There is a lot going on here, but the display support part is working: <a href=\"https:\/\/github.com\/everedero\/asynthosc_fw\" target=\"_blank\" rel=\"noreferrer noopener\">https:\/\/github.com\/everedero\/asynthosc_fw<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Let\u2019s talk about Zephyr RTOS support for displays, how to configure and debug. You can play some escape games with datasheets.<\/p>\n","protected":false},"author":1,"featured_media":1216,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[49],"tags":[98,58,47],"class_list":["post-1172","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-blog_en","tag-display","tag-lvgl-en","tag-zephyr-en"],"_links":{"self":[{"href":"https:\/\/redero.fr\/index.php?rest_route=\/wp\/v2\/posts\/1172","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=1172"}],"version-history":[{"count":53,"href":"https:\/\/redero.fr\/index.php?rest_route=\/wp\/v2\/posts\/1172\/revisions"}],"predecessor-version":[{"id":1321,"href":"https:\/\/redero.fr\/index.php?rest_route=\/wp\/v2\/posts\/1172\/revisions\/1321"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/redero.fr\/index.php?rest_route=\/wp\/v2\/media\/1216"}],"wp:attachment":[{"href":"https:\/\/redero.fr\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=1172"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/redero.fr\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=1172"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/redero.fr\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=1172"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}