LVGL 9.2 with RP2040 and Monochrome Display

Description

Code is hanging after calling “lv_timer_handler_run_in_period”

What MCU/Processor/Board and compiler are you using?

Raspberry Pi Pico RP2040 with GCC and Visual Studio Code

What do you want to achieve?

A working framework that I can then use to get the monochrome display driver (sh1106) working with the LVGL display driver.

What have you tried so far?

Tried following the porting instructions in the docs and the lv_disp_port_template.c

Code to reproduce

The complete code can be found here “https://github.com/sdwood68/Pico_lvgl

main code:

/* Includes ----------------------------------------------------------------*/
#include <stdio.h>
#include "pico/stdlib.h"
#include "lvgl/lvgl.h"
#include "pico/binary_info.h"
#include "hardware/i2c.h"
#include "lv_port_disp.h"

/*********************
 *      DEFINES
 *********************/
#define LED_PIN 		25
// I2C defines
#define I2C_PORT 		i2c0
#define I2C_SDA 		16
#define I2C_SCL 		17

static uint32_t get_millis(void) {
	return to_ms_since_boot(get_absolute_time());
}

// lv_tick_get_cb_t get_millis_cb = get_millis;
static uint32_t my_tick_get_cb(void) {
	return get_millis();
}

void toggle_led(uint32_t timeout) {
	static uint32_t last_timeout;
	uint32_t millis = get_millis();
	if (millis - last_timeout > timeout) {
		last_timeout += timeout;
		// printf("[%6d]: ", millis);
		// printf("Hello, world!\n");
		gpio_put(LED_PIN, !gpio_get(LED_PIN));
	}
}

int main()
{
    stdio_init_all();

	sleep_ms(5000);

	// uint8_t buffer[sh1106_BUF_SIZE] = { 0xFF };  // all elements 0xFF
	
	// Default LED

    gpio_init(LED_PIN);
	gpio_set_dir(LED_PIN, GPIO_OUT);

	// I2C Initialisation. Using it at 100Khz.
    i2c_init(I2C_PORT, 400*1000);
    gpio_set_function(I2C_SDA, GPIO_FUNC_I2C);
    gpio_set_function(I2C_SCL, GPIO_FUNC_I2C);
    gpio_pull_up(I2C_SDA);
    gpio_pull_up(I2C_SCL);

	// scanI2C();

	/*
	 * LVGL Initialization
	 */

	lv_init();
	lv_port_disp_init();

	// Tick Initialization
	// lv_tick_get_cb_t get_millis_cb = get_millis;
	lv_tick_set_cb(my_tick_get_cb);

	printf("Starting Main loop\n");
	static bool invert = false;
	uint32_t millis = 0;
	uint32_t old_millis = 0;
	while (true) {
		toggle_led(1000);
		lv_timer_handler_run_in_period(10);                     
    }
}

lv_port_disp.c

/**
 * @file lv_port_disp_templ.c
 *
 */

/*Copy this file as "lv_port_disp.c" and set this value to "1" to enable content*/
#if 1

/*********************
 *      INCLUDES
 *********************/
#include "lv_port_disp.h"
#include "sh1106.h"
#include <stdbool.h>
#include "images/Box.h"
#include "images/circle_16x16x3.h"


/*********************
 *      DEFINES
 *********************/
#ifndef MY_DISP_HOR_RES
    #define MY_DISP_HOR_RES    128
#endif

#ifndef MY_DISP_VER_RES
    #define MY_DISP_VER_RES    64
#endif

#define BYTE_PER_PIXEL (LV_COLOR_FORMAT_GET_SIZE(LV_COLOR_FORMAT_I1)) /*will be 2 for RGB565 */

/**********************
 *      TYPEDEFS
 **********************/

/**********************
 *  STATIC PROTOTYPES
 **********************/
static void disp_init(void);
static void disp_flush(lv_display_t * disp, const lv_area_t * area, uint8_t * px_map);

/**********************
 *  STATIC VARIABLES
 **********************/
static sh1106_t sh1106;

/**********************
 *      MACROS
 **********************/

/**********************
 *   GLOBAL FUNCTIONS
 **********************/

void lv_port_disp_init(void)
{
    /*-------------------------
     * Initialize your display
     * -----------------------*/
    disp_init();

	printf("Starting LVGL Init\n");
    printf("LVGL Init\n");

    /*------------------------------------
     * Create a display and set a flush_cb
     * -----------------------------------*/
    lv_display_t * disp = lv_display_create(MY_DISP_HOR_RES, MY_DISP_VER_RES);
    lv_display_set_flush_cb(disp, disp_flush);

    // Allocate the display buffer
	LV_ATTRIBUTE_MEM_ALIGN
	static uint8_t disp_buf_1[MY_DISP_HOR_RES * MY_DISP_VER_RES * BYTE_PER_PIXEL];
	LV_ATTRIBUTE_MEM_ALIGN
	static uint8_t disp_buf_2[MY_DISP_HOR_RES * MY_DISP_VER_RES * BYTE_PER_PIXEL];
	lv_display_set_buffers(disp, disp_buf_1, disp_buf_2, sizeof(disp_buf_1), LV_DISPLAY_RENDER_MODE_FULL);
}

/**********************
 *   STATIC FUNCTIONS
 **********************/

/*Initialize your display and the required peripherals.*/
static void disp_init(void)
{
	printf("Starting sh1106 Display Init\n");
	sh1106_init(&sh1106, I2C_PORT, 0x3C, 128, 64, false, false, false);
	sh1106_flush_buffer(&sh1106, BOX);
 	sh1106_inverted(&sh1106, true);
	// sleep_ms(2000);
	// sh1106_flush_area(&sh1106, 8, 8, 16, 16, CIRCLE_16X16X3);
	// sleep_ms(2000);
	// sh1106_flush_area(&sh1106, 24, 12, 16, 16, CIRCLE_16X16X3);
}

volatile bool disp_flush_enabled = true;

/* Enable updating the screen (the flushing process) when disp_flush() is called by LVGL */
void disp_enable_update(void)
{
    disp_flush_enabled = true;
}

/* Disable updating the screen (the flushing process) when disp_flush() is called by LVGL */
void disp_disable_update(void)
{
    disp_flush_enabled = false;
}

/*Flush the content of the internal buffer the specific area on the display.
 *`px_map` contains the rendered image as raw pixel map and it should be copied to `area` on the display.
 *You can use DMA or any hardware acceleration to do this operation in the background but
 *'lv_display_flush_ready()' has to be called when it's finished.*/
static void disp_flush(lv_display_t * disp_drv, const lv_area_t * area, uint8_t * px_map)
{
    if(disp_flush_enabled) {
        /*The most simple case (but also the slowest) to put all pixels to the screen one-by-one*/

        // int32_t x;
        // int32_t y;
        // for(y = area->y1; y <= area->y2; y++) {
        //     for(x = area->x1; x <= area->x2; x++) {
        //         /*Put a pixel to the display. For example:*/
        //         /*put_px(x, y, *px_map)*/
        //         px_map++;
        //     }
        // }
        
        sh1106_flush_buffer(&sh1106, px_map);
    }

    /*IMPORTANT!!!
     *Inform the graphics library that you are ready with the flushing*/
    lv_display_flush_ready(disp_drv);
}

#else /*Enable this file at the top*/
/*This dummy typedef exists purely to silence -Wpedantic.*/
typedef int keep_pedantic_happy;
#endif

Screenshot and/or video

If possible, add screenshots and/or videos about the current state.

By moving lv_tick_set_cb() before lv_init() the main look does not hang at v_timer_handler_run_in_period(); but I’m unable to get the "hello world exable to display anything.
The new main function is below. Code has been updated on github too.

int main()
{
    stdio_init_all();

	sleep_ms(5000);

	// Default LED
    gpio_init(LED_PIN);
	gpio_set_dir(LED_PIN, GPIO_OUT);

	// I2C Initialisation. Using it at 100Khz.
    i2c_init(I2C_PORT, 400*1000);
    gpio_set_function(I2C_SDA, GPIO_FUNC_I2C);
    gpio_set_function(I2C_SCL, GPIO_FUNC_I2C);
    gpio_pull_up(I2C_SDA);
    gpio_pull_up(I2C_SCL);

	/*
	 * LVGL Initialization
	 */
	// Tick Initialization must come first before lv_init()
	lv_tick_set_cb(my_tick);
	lv_init();
	lv_port_disp_init();

	lv_obj_t *label = lv_label_create(lv_screen_active());
	lv_label_set_text(label, "Hello World");
	lv_obj_set_style_text_color(lv_screen_active(), lv_color_black(), LV_PART_MAIN);
	lv_obj_align(label, LV_ALIGN_CENTER, 0, 0);

	printf("Starting Main loop\n");
	while (true) {
		toggle_led(1000);
		lv_timer_handler_run_in_period(10);                     
    } 
}

I’m starting to think that the issue is with the tick interface.
The docs say that lv_init() must come first before any other lv calls, but when I do this lv_timer_handler_run_in_pertod() never returns and I never see a call to disp_flush().
At this point disp_flush() doesn’t do anything. except print a message and then calls lv_display_flush_ready();

Could someone confirm that my assumptions are correct, and does anyone have an example of a raspberry RP2040 tick implementation?

I’ve now got “Hello World” Working on the display. Please refer to Raspberry Pico with Monochrome SH1106 Display Driver for any additional progress.