nRF52840 + SSD1680 (mipi-dbi-spi) + LVGL

Hi,

I'm working on a custom board based on nRF52840 and a 2.9" ePaper display (SSD1680) connected via SPI using the mipi-dbi-spi interface.
I'm using nRF Connect SDK v3.0.0 (Zephyr) and LVGL for graphics rendering.

In the devicetree, the display is marked with zephyr,deferred-init because I only power the display later from main():

.overlay:

&spi1 {
    compatible = "nordic,nrf-spi";
    status = "okay";
    cs-gpios = <&gpio0 22 GPIO_ACTIVE_LOW>;
    pinctrl-0 = <&spi1_default>;
    pinctrl-1 = <&spi1_sleep>;
    pinctrl-names = "default", "sleep";
};
&spi1_default {
    group1 {
        psels = <NRF_PSEL(SPIM_SCK, 0, 20)>,
                <NRF_PSEL(SPIM_MOSI, 0, 17)>; //SDA
                // <NRF_PSEL(SPIM_MISO, 0, 8)>;
    };
};

&spi1_sleep {
    group1 {
        psels = <NRF_PSEL(SPIM_SCK, 0, 20)>,
                <NRF_PSEL(SPIM_MOSI, 0, 17)>; //SDA
                // <NRF_PSEL(SPIM_MISO, 0, 8)>;
    };
};

/ {
    mipi_dbi_waveshare_epaper_gdeh029a1 {
        compatible = "zephyr,mipi-dbi-spi";
        spi-dev = <&spi1>;
        dc-gpios = <&gpio0 24 GPIO_ACTIVE_HIGH>;
        reset-gpios = <&gpio1 0 GPIO_ACTIVE_LOW>;

        #address-cells = <1>;
        #size-cells = <0>;
        // write-only;

        ssd16xx_waveshare_epaper_gdeh029a1: ssd16xxfb@0 {
            compatible = "gooddisplay,gdey0213b74", "solomon,ssd1680";
            mipi-max-frequency = <1000000>;
            reg = <0>;
            width = <296>;
            height = <128>;
            busy-gpios = <&gpio0 11 GPIO_ACTIVE_HIGH>;

            tssv = <0x80>;
            zephyr,deferred-init;

            full {
                border-waveform = <0x05>;
            };

            partial {
                border-waveform = <0x3c>;
            };
        };
    };
};


/ {
    chosen {
        nvs-storage = &storage_partition;
        zephyr,console = &cdc_acm_uart0;
        zephyr,display = &ssd16xx_waveshare_epaper_gdeh029a1;
    };
};



prj.conf:
CONFIG_DISPLAY=y
CONFIG_SSD16XX=y

# Enable SPI
CONFIG_SPI=y
CONFIG_SPI_NRFX=y

CONFIG_MAIN_STACK_SIZE=16384
CONFIG_HEAP_MEM_POOL_SIZE=2048


CONFIG_LVGL=y
CONFIG_LV_Z_MEM_POOL_SIZE=16384
CONFIG_LV_Z_AUTO_INIT=n
CONFIG_LV_USE_LOG=y
CONFIG_LV_USE_LABEL=y
CONFIG_LV_COLOR_DEPTH_1=y
CONFIG_LV_USE_LABEL=y
CONFIG_LV_LOG_LEVEL_TRACE=y
CONFIG_LV_Z_SHELL=y
CONFIG_LV_USE_MONKEY=y
CONFIG_LV_FONT_MONTSERRAT_14=y


K_THREAD_STACK_DEFINE(lvgl_stack_area, 32768);
static struct k_thread lvgl_stack_data;

static lv_obj_t *hello_world_label;
static const struct device *display_dev;

void view_lvgl()
{

	gpio_pin_set_dt(&out, 1);
	k_msleep(1000);

	display_dev = DEVICE_DT_GET(DT_CHOSEN(zephyr_display));
	device_init(display_dev);

	while (!device_is_ready(display_dev))
	{
		printk("Device not ready");
		k_msleep(100);
	}

	hello_world_label = lv_label_create(lv_scr_act());
	lv_label_set_text(hello_world_label, "Hello world!");
	lv_obj_align(hello_world_label, LV_ALIGN_CENTER, 0, 0);

	lv_task_handler();
	display_blanking_off(display_dev);

	while (1)
	{
		lv_task_handler();
		printk("view_lvgl loop\n");
		k_msleep(1000);
	}
}


However, after calling:
hello_world_label = lv_label_create(lv_scr_act());

I get a NULL pointer, and any further use causes a crash (lv_scr_act() returns NULL, and lv_label_create() returns NULL).

I assume this happens because no LVGL display was registered, due to the deferred init.

If I remove zephyr,deferred-init, then the display is not initialized at all

Now I see fast blinking and part of the screen is filled with garbage when initializing.



Here is the initialization after power is applied.


  1. How should I trigger full LVGL registration after enabling the display device at runtime?

  2. Can I manually re-init the display driver (e.g., force call lv_disp_drv_register() or trigger the driver init() after power is applied)?

  3. Is there a known working method to combine zephyr,deferred-init + LVGL for late-powered displays (e.g., ePaper)?

Any advice would be very helpful. Thank you in advance!


*** Booting nRF Connect SDK v3.0.2-89ba1294ac9b ***
*** Using Zephyr OS v4.0.99-f791c49f492c ***
[00:00:00.002,868] <err> lvgl: Display device not ready.
USB CDC ready. Type something...
[00:00:00.108,947] <inf> fs_nvs: 8 Sectors of 4096 bytes
[00:00:00.108,947] <inf> fs_nvs: alloc wra: 0, f30
[00:00:00.108,947] <inf> fs_nvs: data wra: 0, 2c8
[00:00:00.109,344] <inf> bt_sdc_hci_driver: SoftDevice Controller build revision: 
                                            89 9a 50 8a 95 01 9c 58  fc 39 d2 c1 10 04 ee 02 |..P....X .9......
                                            64 ce 25 be                                      |d.%.             
[00:00:00.112,121] <inf> bt_hci_core: HW Platform: Nordic Semiconductor (0x0002)
[00:00:00.112,152] <inf> bt_hci_core: HW Variant: nRF52x (0x0002)
[00:00:00.112,182] <inf> bt_hci_core: Firmware: Standard Bluetooth controller (0x00) Version 137.20634 Build 2617349514
[00:00:00.112,426] <i
view_lvgl loop


  • I want to share my progress and ask for advice about LVGL + Zephyr on a custom board with an SSD1680 E-Paper display (using mipi-dbi-spi), nRF52840 SoC, and NCS v3.0.2.

    Previously, I was getting the error:

    <err> lvgl: Display device not ready.

    This happened during startup because LVGL was trying to initialize before the display had power.

    I fixed this by adding to my prj.conf

    CONFIG_LV_Z_AUTO_INIT=n

    This stopped LVGL from initializing automatically at boot.

    Now I initialize everything manually in my code:

    void view_lvgl()
    {
        LOG_DBG("Begin");
        gpio_pin_configure_dt(&out, GPIO_OUTPUT_ACTIVE);
        gpio_pin_set_dt(&out, 1);
        LOG_DBG("PowerON Disp");
        k_msleep(250);
    
        display_dev = DEVICE_DT_GET(DT_CHOSEN(zephyr_display));
        LOG_DBG("Init Disp");
        device_init(display_dev);
    
        while (!device_is_ready(display_dev)) {
            LOG_WRN("Device not ready...\n");
            k_msleep(100);
        }
        LOG_DBG("Start lvgl_init");
    
        lvgl_init();
    
        LOG_DBG("LVGL started");
        hello_world_label = lv_label_create(lv_scr_act());
        lv_label_set_text(hello_world_label, "Hello world!");
        lv_obj_align(hello_world_label, LV_ALIGN_CENTER, 0, 0);
        LOG_DBG("Label created");
    
        LOG_DBG("lv_task_handler starting");
        lv_task_handler();
        display_blanking_off(display_dev);
        LOG_DBG("lv_task_handler finish");
    
        while (1) {
            printk("view_lvgl loop\n");
            k_msleep(1000);
        }
    }

    Now, the "Display device not ready" error is gone.

    However, I now have a new issue:
    As soon as lv_task_handler() is called for the first time, the system crashes with a fatal error.

    Here is the stack trace from the fault handler:

    k_sys_fatal_error_handler(unsigned int reason, const struct arch_esf * esf) (e:\krisrianna\git\nrf\blinky\build\zephyr\include\generated\zephyr\syscalls\log_ctrl.h:34)
    z_fatal_error(unsigned int reason, const struct arch_esf * esf) (e:\Segger\ncs\v3.0.2\zephyr\kernel\fatal.c:119)
    z_arm_fatal_error(unsigned int reason, const struct arch_esf * esf) (e:\Segger\ncs\v3.0.2\zephyr\arch\arm\core\fatal.c:86)
    z_arm_fault(uint32_t msp, uint32_t psp, uint32_t exc_return, _callee_saved_t * callee_regs) (e:\Segger\ncs\v3.0.2\zephyr\arch\arm\core\cortex_m\fault.c:1107)
    z_arm_usage_fault() (e:\Segger\ncs\v3.0.2\zephyr\arch\arm\core\cortex_m\fault_s.S:102)
    <signal handler called> (Unknown Source:0)
    [Unknown code] (Unknown Source:0)
    display_write(const void * buf, const struct display_buffer_descriptor * desc, const uint16_t y, const uint16_t x, const struct device * dev) (e:\Segger\ncs\v3.0.2\zephyr\include\zephyr\drivers\display.h:254)
    lvgl_flush_cb_mono(lv_display_t * display, const lv_area_t * area, uint8_t * px_map) (e:\Segger\ncs\v3.0.2\zephyr\modules\lvgl\lvgl_display_mono.c:103)
    call_flush_cb(uint8_t * px_map, const lv_area_t * area, lv_display_t * disp) (e:\Segger\ncs\v3.0.2\modules\lib\gui\lvgl\src\core\lv_refr.c:1303)
    draw_buf_flush(lv_display_t * disp) (e:\Segger\ncs\v3.0.2\modules\lib\gui\lvgl\src\core\lv_refr.c:1270)
    refr_invalid_areas() (e:\Segger\ncs\v3.0.2\modules\lib\gui\lvgl\src\core\lv_refr.c:611)
    lv_display_refr_timer(lv_timer_t * tmr) (e:\Segger\ncs\v3.0.2\modules\lib\gui\lvgl\src\core\lv_refr.c:405)
    lv_timer_exec(lv_timer_t * timer) (e:\Segger\ncs\v3.0.2\modules\lib\gui\lvgl\src\misc\lv_timer.c:327)
    lv_timer_handler() (e:\Segger\ncs\v3.0.2\modules\lib\gui\lvgl\src\misc\lv_timer.c:107)
    lv_timer_handler() (e:\Segger\ncs\v3.0.2\modules\lib\gui\lvgl\src\misc\lv_timer.c:63)
    lv_task_handler() (e:\Segger\ncs\v3.0.2\modules\lib\gui\lvgl\src\lv_api_map_v8.h:77)
    view_lvgl() (e:\krisrianna\git\nrf\blinky\src\main.c:136)
    z_thread_entry(k_thread_entry_t entry, void * p1, void * p2, void * p3) (e:\Segger\ncs\v3.0.2\zephyr\lib\os\thread_entry.c:48)

    It seems the crash occurs inside the display driver when LVGL tries to flush the buffer for the first time.

    I did more debugging and found that the crash is caused by a NULL pointer passed to the display driver.

    Just before the crash, in the function display_write() (from include/zephyr/drivers/display.h), the dev pointer is NULL:


    As you can see, when display_write() is called, dev == 0x0 (NULL). This leads to a crash when the API tries to dereference the device pointer.

     

    My questions:

    1. Is it correct to call device_init(display_dev) directly?
      In most Zephyr examples, I don't see this function being called.

    2. Is my sequence of powering up, checking readiness, and then calling lvgl_init() correct?
      Do I need to wait longer after powering up the display, or do anything special for SSD1680?

    3. What is the minimal recommended LVGL event/task handling loop?
      Right now I just call lv_task_handler() once before entering the main loop (which only prints a message). Should I be calling lv_task_handler() in the main loop with some interval, or do I need to process events differently for E-Paper?

    4. What could be causing this crash in display_write()?
      Could it be a problem with my devicetree, buffer sizes, or driver configuration? What is the best way to debug or verify the display initialization and flush pipeline for this type of E-Paper? 

      What could cause LVGL or the display pipeline to pass a NULL device pointer to display_write()?

      Am I missing some required initialization or assignment step for the display device when doing manual LVGL init?

    5. Anything else I should be aware of when using deferred LVGL init on Zephyr with E-Paper displays?

    Thanks for any tips or corrections!

     

  • Hello, happy to hear that you got past one hurdle.

    Unfortunately we do not provide support for the LVGL library, this is something specific from the Zephyr community. You can reach out to them through Discord.

    Good luck in getting a cool display up and running

    Kind regards,
    Øyvind

Related