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


Parents
  • 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!

     

Reply
  • 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!

     

Children
Related