LVGL sample failing to display properly on SHARP displayl

Hello, I am using the nRF54L15 board, and trying to connect to the Adafruit SHARP 400x240 monochrome LCD display.

I got the display driver alone to function just fine after a while, but when I attempted to implement LVGL in the project using the Zephyr sample, I wound up with a BUS FAULT. The full error is below:

[00:00:00.014,616] <err> os: ***** BUS FAULT *****
[00:00:00.014,628] <err> os:   Precise data bus error
[00:00:00.014,632] <err> os:   BFAR Address: 0x1bf9f08
[00:00:00.014,642] <err> os: r0/a1:  0xffffffff  r1/a2:  0x20011264  r2/a3:  0x20011264
[00:00:00.014,649] <err> os: r3/a4:  0x01bf9f00 r12/ip:  0xffffda80 r14/lr:  0x00019275
[00:00:00.014,654] <err> os:  xpsr:  0x09000000
[00:00:00.014,658] <err> os: Faulting instruction address (r15/pc): 0x000192b0
[00:00:00.014,677] <err> os: >>> ZEPHYR FATAL ERROR 25: Unknown error on CPU 0
[00:00:00.014,691] <err> os: Current thread: 0x20010940 (unknown)
[00:00:00.076,313] <err> os: Halting system

The hardware seems to be fine, on the display I have connected GND, VIN, CS to port 1, pin 8, CLK to port 1, pin 11, and MOSI to port 1, pin 12. The Adafruit display datasheet said that these were all of the pins required.

Here are the files as of now:

main.c (unchanged sample code)

/*
 * Copyright (c) 2018 Jan Van Winkel <[email protected]>
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#include <zephyr/device.h>
#include <zephyr/devicetree.h>
#include <zephyr/drivers/display.h>
#include <zephyr/drivers/gpio.h>
#include <lvgl.h>
#include <stdio.h>
#include <string.h>
#include <zephyr/kernel.h>
#include <lvgl_input_device.h>

#define LOG_LEVEL CONFIG_LOG_DEFAULT_LEVEL
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(app);

static uint32_t count;

#ifdef CONFIG_RESET_COUNTER_SW0
static struct gpio_dt_spec button_gpio = GPIO_DT_SPEC_GET_OR(
		DT_ALIAS(sw0), gpios, {0});
static struct gpio_callback button_callback;

static void button_isr_callback(const struct device *port,
				struct gpio_callback *cb,
				uint32_t pins)
{
	ARG_UNUSED(port);
	ARG_UNUSED(cb);
	ARG_UNUSED(pins);

	count = 0;
}
#endif /* CONFIG_RESET_COUNTER_SW0 */

#ifdef CONFIG_LV_Z_ENCODER_INPUT
static const struct device *lvgl_encoder =
	DEVICE_DT_GET(DT_COMPAT_GET_ANY_STATUS_OKAY(zephyr_lvgl_encoder_input));
#endif /* CONFIG_LV_Z_ENCODER_INPUT */

#ifdef CONFIG_LV_Z_KEYPAD_INPUT
static const struct device *lvgl_keypad =
	DEVICE_DT_GET(DT_COMPAT_GET_ANY_STATUS_OKAY(zephyr_lvgl_keypad_input));
#endif /* CONFIG_LV_Z_KEYPAD_INPUT */

static void lv_btn_click_callback(lv_event_t *e)
{
	ARG_UNUSED(e);

	count = 0;
}

int main(void)
{
	char count_str[11] = {0};
	const struct device *display_dev;
	lv_obj_t *hello_world_label;
	lv_obj_t *count_label;

	display_dev = DEVICE_DT_GET(DT_CHOSEN(zephyr_display));
	if (!device_is_ready(display_dev)) {
		LOG_ERR("Device not ready, aborting test");
		return 0;
	}

#ifdef CONFIG_RESET_COUNTER_SW0
	if (gpio_is_ready_dt(&button_gpio)) {
		int err;

		err = gpio_pin_configure_dt(&button_gpio, GPIO_INPUT);
		if (err) {
			LOG_ERR("failed to configure button gpio: %d", err);
			return 0;
		}

		gpio_init_callback(&button_callback, button_isr_callback,
				   BIT(button_gpio.pin));

		err = gpio_add_callback(button_gpio.port, &button_callback);
		if (err) {
			LOG_ERR("failed to add button callback: %d", err);
			return 0;
		}

		err = gpio_pin_interrupt_configure_dt(&button_gpio,
						      GPIO_INT_EDGE_TO_ACTIVE);
		if (err) {
			LOG_ERR("failed to enable button callback: %d", err);
			return 0;
		}
	}
#endif /* CONFIG_RESET_COUNTER_SW0 */

#ifdef CONFIG_LV_Z_ENCODER_INPUT
	lv_obj_t *arc;
	lv_group_t *arc_group;

	arc = lv_arc_create(lv_screen_active());
	lv_obj_align(arc, LV_ALIGN_CENTER, 0, -15);
	lv_obj_set_size(arc, 150, 150);

	arc_group = lv_group_create();
	lv_group_add_obj(arc_group, arc);
	lv_indev_set_group(lvgl_input_get_indev(lvgl_encoder), arc_group);
#endif /* CONFIG_LV_Z_ENCODER_INPUT */

#ifdef CONFIG_LV_Z_KEYPAD_INPUT
	lv_obj_t *btn_matrix;
	lv_group_t *btn_matrix_group;
	static const char *const btnm_map[] = {"1", "2", "3", "4", ""};

	btn_matrix = lv_buttonmatrix_create(lv_screen_active());
	lv_obj_align(btn_matrix, LV_ALIGN_CENTER, 0, 70);
	lv_buttonmatrix_set_map(btn_matrix, (const char **)btnm_map);
	lv_obj_set_size(btn_matrix, 100, 50);

	btn_matrix_group = lv_group_create();
	lv_group_add_obj(btn_matrix_group, btn_matrix);
	lv_indev_set_group(lvgl_input_get_indev(lvgl_keypad), btn_matrix_group);
#endif /* CONFIG_LV_Z_KEYPAD_INPUT */

	if (IS_ENABLED(CONFIG_LV_Z_POINTER_INPUT)) {
		lv_obj_t *hello_world_button;

		hello_world_button = lv_button_create(lv_screen_active());
		lv_obj_align(hello_world_button, LV_ALIGN_CENTER, 0, -15);
		lv_obj_add_event_cb(hello_world_button, lv_btn_click_callback, LV_EVENT_CLICKED,
				    NULL);
		hello_world_label = lv_label_create(hello_world_button);
	} else {
		hello_world_label = lv_label_create(lv_screen_active());
	}

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

	count_label = lv_label_create(lv_screen_active());
	lv_obj_align(count_label, LV_ALIGN_BOTTOM_MID, 0, 0);

	lv_timer_handler();
	display_blanking_off(display_dev);

	while (1) {
		if ((count % 100) == 0U) {
			sprintf(count_str, "%d", count/100U);
			lv_label_set_text(count_label, count_str);
		}
		lv_timer_handler();
		++count;
		k_sleep(K_MSEC(10));
	}
}

prj.conf

CONFIG_MAIN_STACK_SIZE=4096

CONFIG_GPIO=y
CONFIG_SPI=y

CONFIG_LS0XX=y

CONFIG_DISPLAY=y
CONFIG_DISPLAY_LOG_LEVEL_ERR=y

CONFIG_LOG=y

CONFIG_LVGL=y
CONFIG_LV_USE_LOG=y
CONFIG_LV_USE_LABEL=y
CONFIG_LV_USE_ARC=y
CONFIG_LV_USE_MONKEY=y
CONFIG_LV_FONT_MONTSERRAT_14=y

CONFIG_LV_Z_MEM_POOL_SIZE=16384

CONFIG_LV_Z_BITS_PER_PIXEL=1
CONFIG_LV_COLOR_DEPTH_1=y

CONFIG_PRINTK=y
CONFIG_LOG_BUFFER_SIZE=50240

app.overlay (had to edit the ls013b7dh03 board to match the nRF54L15 pinout)

/ {
    chosen {
		zephyr,display = &ls0xx;
	};
};

&spi21 {
    compatible = "nordic,nrf-spim";
    status = "okay";
    cs-gpios = <&gpio1 8 GPIO_ACTIVE_HIGH>;
    pinctrl-0 = <&spi21_default>;
    pinctrl-1 = <&spi21_sleep>;
    pinctrl-names = "default", "sleep";

    ls0xx: ls0xx@0 {
        compatible = "sharp,ls0xx";
        spi-max-frequency = <2000000>;
        reg = <0>;
        width = <400>;
        height = <240>;
        extcomin-gpios = <&gpio1 9 GPIO_ACTIVE_HIGH>; /* D8 */
        extcomin-frequency = <60>; /* required if extcomin-gpios is defined */
        disp-en-gpios = <&gpio1 10 GPIO_ACTIVE_HIGH>; /* D6 */
    };
};

&pinctrl {
    spi21_default: spi21_default {
        group1 {
            psels = <NRF_PSEL(SPIM_SCK, 1, 11)>,
                    <NRF_PSEL(SPIM_MOSI, 1, 12)>/*,
                    <NRF_PSEL(SPIM_MISO, 1, 14)>*/;
        };
    };
    spi21_sleep: spi21_sleep {
        group1 {
            psels = <NRF_PSEL(SPIM_SCK, 1, 11)>,
                    <NRF_PSEL(SPIM_MOSI, 1, 12)>/*,
                    <NRF_PSEL(SPIM_MISO, 1, 14)>*/;
            low-power-enable;
        };
    };
};

A note is that after getting the bus fault, using addr2line, the error seems to spawn from display.h, notably this function: 

static inline int display_write(const struct device *dev, const uint16_t x,
				const uint16_t y,
				const struct display_buffer_descriptor *desc,
				const void *buf)
{
	struct display_driver_api *api =
		(struct display_driver_api *)dev->api;
	
	return api->write(dev, x, y, desc, buf);
}

I believe 'dev' is an invalid value, but I cannot understand how that happens.

  • Hello,

    The Bus error seems display driver is not fully compatible with the zephyr display API as expected by LVGL which results a NULL or invalid function pointer dereference in display_write(). 

    Is this a custom board? which NCS you are working on?

  • If you are referring to the LCD, the board is a LS0XX board, one which the driver had already come with the install of Zephyr and nRF Connect. When building the project, I use the nrf54l15/nrf54l15/cpuapp board target.

    I am also using ncs v3.0.2.

  • Hello Zeke,

    Is this a custom board? Could you please share the application file so I can try to reproduce the issue?

  • Hello Kazi,

    The board is the nRF54L15 DK. I do not know quite what you mean by application file, but I have added everything needed to reproduce the issue. I have attached a zip folder as well with the files.

    lvgl_2025.zip

  • Hi, 

    Kazi is out of the office, so I take this case. 

    I am no expert in LVGL, but my colleague has just been doing display_write makes LVGL take one buffer 16 or 18 bit. It seems like on 1bpp display, he was trying to read this discussion in the github issue number 1427 and it is possible that your LVGL buffer stays all 0x00 (black) because LVGL is writing a single‐bit brightness into each lv_color_t (which ends up in the LSB), and then the Zephyr flush simply spits out those bytes without repacking them into MSB-first pixel order. You might need to pack 8 pixels into one byte from MSB to LSB something like below

    +#include <lvgl.h>
     #include <stdio.h>
    
    -static void flush_cb(struct _disp_drv_t *disp_drv,
    -                     const lv_area_t *area,
    -                     lv_color_t *color_map)
    -{
    -    display_write(display_dev,
    -                  area->x1, area->y1,
    -                  &disp_buf_desc,
    -                  (const void *)color_map);
    -    lv_disp_flush_ready(disp_drv);
    -}
    +static void sharp_flush_cb(lv_disp_drv_t *drv,
    +                           const lv_area_t *area,
    +                           lv_color_t *buf)
    +{
    +    int w = area->x2 - area->x1 + 1; // important change, pack 8 LVGL pixels into 1 byte
    +    uint8_t line[400/8];
    +
    +    for (int y = area->y1; y <= area->y2; y++) {
    +        memset(line, 0, sizeof(line));
    +        for (int x = 0; x < w; x++) {
    +            if (lv_color_brightness(buf[x + (y-area->y1)*w]) > 128) {
    +                line[x>>3] |= 0x80 >> (x&7);
    +            }
    +        }
    +        display_write(display_dev, area->x1, y, &row_desc, line);
    +    }
    +    lv_disp_flush_ready(drv);
    +}
    +
    +static struct display_buffer_descriptor row_desc = {
    +    .pitch = 400,
    +    .height = 1,
    +    .buf_size = 400/8,
    +};
    +
    +void main(void)
    +{
    +    disp_dev = device_get_binding(DT_LABEL(DT_CHOSEN(zephyr_display)));
    +    if (!device_is_ready(disp_dev)) return;
    +
    +    lv_init();
    +
    +    static uint8_t buf[400*240/8 + 8];
    +    static lv_disp_draw_buf_t draw_buf;
    +    lv_disp_draw_buf_init(&draw_buf, buf, NULL, sizeof(buf));
    +
    +    lv_disp_drv_t disp_drv;
    +    lv_disp_drv_init(&disp_drv);
    +    disp_drv.hor_res = 400;
    +    disp_drv.ver_res = 240;
    +    disp_drv.draw_buf = &draw_buf;
    +    disp_drv.flush_cb = sharp_flush_cb;
    +    disp_drv.color_format = LV_COLOR_FORMAT_I1;
    +    lv_disp_drv_register(&disp_drv);
    +
    +    lv_obj_t *lbl = lv_label_create(lv_scr_act());
    +    lv_label_set_text(lbl, "Hello world!");
    +    lv_obj_align(lbl, LV_ALIGN_CENTER, 0, 0);
    +
    +    while (1) {
    +        lv_timer_handler();
    +        k_sleep(K_MSEC(10));
    +    }
    +}
     
    -void main(void)
    -{
    -    display_dev = DEVICE_DT_GET(DT_CHOSEN(zephyr_display));
    -    if (!device_is_ready(display_dev)) {
    -        LOG_ERR("Device not ready, aborting");
    -        return;
    -    }
    -
    -    lv_init();
    -    lv_disp_draw_buf_init(&draw_buf, buf, NULL, sizeof(buf));
    -    lv_disp_drv_init(&disp_drv);
    -    disp_drv.hor_res = 400;
    -    disp_drv.ver_res = 240;
    -    disp_drv.draw_buf = &draw_buf;
    -    disp_drv.flush_cb = flush_cb;
    -    lv_disp_drv_register(&disp_drv);
    -
    -    /* … rest of sample … */
    -}

    instead of using Zephyr LVGL handling, he is using lv_disp_drv API here, use it as a template.

    Regards,
    Amanda H.

Related