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.

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

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

Children
  • Thanks for all the help, but I have actually found out that it is the display buffer held for the monochrome display that wound up being too large. The memory reserves 8 bytes for the palette, so the memory kept 8 extra bytes in memory, but what I did not know was that these already had skipped the palette bytes, so the 8 bytes were leaking into other parts of the memory.

    All I had to do was alter a function in the file lvgl_display_mono.c to ensure the right size buffer.

    void lvgl_set_mono_conversion_buffer(uint8_t *buffer, uint32_t buffer_size)
    {
    	mono_conv_buf = buffer;
    	
    	// 8 palette bytes do not need to be altered
    +	mono_conv_buf_size = buffer_size - 8;
    }

  • Is this still the issue with nRF Connect SDK version 3.2.3?

Related