STM32F1,LVGL簡易DEMO移植

cau_par發表於2024-10-19

簡介

嘗試過在ESP32上移植LVGL之後,再在STM32上面LVGL,確認下是不是可以用
雖然STM32F103ZE的ROM及RAM都沒有ESP32豐富,便對應於LVGL的最低配置要求,應該也可以正常執行的。不過也只能移植簡單的
按鍵顯示,像複雜一些DEMO,在STM32F1不用了,資源不夠,導致編譯不透過。

LVGL

LVGL是一款比較流行的致力於MCU與MPU建立漂亮UI的嵌入式圖形庫,免費且開源。

最低要求

ROM > 64KB
RAM > 2K
STACK > 2K
HEAP > 2K

移植步驟

LVGL移植總的步驟主要是如下幾步
1.呼叫lv_init();
2.初始化驅動
3.註冊顯示與輸入驅動,視訊記憶體的配置,顯示響應回撥函式的響應
4.lv_tick_inc(x) 在中斷中定時更新,x設定取決於lv_tick_inc的呼叫頻率
5.lv_timer_handler,定時呼叫,完成LVGL的響應(更新LVGL的響應)

具體示例

使用的是正點原子STM32F103ZE開發板

複製一個LCD可以正常驅動的工程

複製LVGL原始碼至工程

這裡使用的是V8.3.0
複製完之後需要將原始碼新增至工程,並新增相應的include路徑,STM32的新增LVGL的檔案相對ESP32來說,就麻煩很多
需要每個檔案都新增進工程
主要包括lvgl/src, lvgl/exmaples/porting(這個路徑也可以不新增,在自己定義的移植檔案路徑新增即可)

如果一次沒成功,也沒事,可以根據提示的編譯錯誤慢慢新增
重命令lv_conf.h,並使能#if 0 改為#if 1
LVGL需要編譯器支援C99模式,不然無法編譯透過
因為將lv_conf.h獨立於LVGL的原始碼,需要新增預定義LV_CONF_INCLUDE_SIMPLE,沒加的話,編譯會提示這個錯誤,加上就可以正常編譯
在INCLUDE的路徑下新增LVGL原始碼路徑
lvgl/example/porting目錄下的移植檔案複製到工程目錄下,lv_port_disp_template與lv_port_indev_template並重新命名為lv_port_disp.h, lv_port_disp.c, lv_port_indev.h, lv_port_indev.c
因為改變了路徑,檔案不放在LVGL原始碼的路徑下, 直接include "lvgl.h"即可,並使能兩個檔案#if 0 改為 #if 1

螢幕驅動

分配好視訊記憶體,移植LCD區域點陣更新操作

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

    /* Example for 1) */
    lv_disp_draw_buf_init(&draw_buf_dsc_1, buf_1, NULL, MY_DISP_HOR_RES * 10);   /*Initialize the display buffer*/

    /*-----------------------------------
     * Register the display in LVGL
     *----------------------------------*/

    static lv_disp_drv_t disp_drv;                         /*Descriptor of a display driver*/
    lv_disp_drv_init(&disp_drv);                    /*Basic initialization*/

    ///*Set up the functions to access to your display*/

    ///*Set the resolution of the display*/
    disp_drv.hor_res = MY_DISP_HOR_RES;
    disp_drv.ver_res = MY_DISP_VER_RES;

    ///*Used to copy the buffer's content to the display*/
    disp_drv.flush_cb = disp_flush;

    ///*Set a display buffer*/
    disp_drv.draw_buf = &draw_buf_dsc_1;


    ///*Finally register the driver*/
    lv_disp_drv_register(&disp_drv);
}

static void disp_flush(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p)
{
	LCD_Color_Fill(area->x1, area->y1, area->x2, area->y2, (uint16_t*)color_p);
    /*IMPORTANT!!!
     *Inform the graphics library that you are ready with the flushing*/
    lv_disp_flush_ready(disp_drv);
}

輸入驅動

這裡以電阻觸控式螢幕為例,給到LVGL的觸點資訊必須是和螢幕位置相對應的
校準具體模擬值與感應畫素點的位置的操作,應該在BSP層裡面做好,最後傳給LVGL是畫素邏輯位置

void lv_port_indev_init(void)
{
    static lv_indev_drv_t indev_drv;
    touchpad_init();

    /*Register a touchpad input device*/
    lv_indev_drv_init(&indev_drv);
    indev_drv.type = LV_INDEV_TYPE_POINTER;
    indev_drv.read_cb = touchpad_read;
    indev_touchpad = lv_indev_drv_register(&indev_drv);
}

/*Initialize your touchpad*/
static void touchpad_init(void)
{
    /*Your code comes here*/
}

/*Will be called by the library to read the touchpad*/
static void touchpad_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data)
{
    static lv_coord_t last_x = 0;
    static lv_coord_t last_y = 0;

    /*Save the pressed coordinates and the state*/
    if(touchpad_is_pressed()) {
        touchpad_get_xy(&last_x, &last_y);
        data->state = LV_INDEV_STATE_PR;
    }
    else {
        data->state = LV_INDEV_STATE_REL;
    }

    /*Set the last pressed coordinates*/
    data->point.x = last_x;
    data->point.y = last_y;
}

/*Return true is the touchpad is pressed*/
static bool touchpad_is_pressed(void)
{
    /*Your code comes here*/
	tp_dev.scan(0);
	if(tp_dev.sta&TP_PRES_DOWN)
		return true;
	else
    	return false;
}

/*Get the x and y coordinates if the touchpad is pressed*/
static void touchpad_get_xy(lv_coord_t * x, lv_coord_t * y)
{
    /*Your code comes here*/
    (*x) = tp_dev.x[0];
    (*y) = tp_dev.y[0];
}

初始化及呼叫

在SYSTICK中斷函式里,將LVGL計時增加

void SysTick_Handler(void)
{
    HAL_IncTick();
	lv_tick_inc(1);
}

初始化並呼叫

    lv_init();
	lv_port_disp_init();
	lv_port_indev_init();
	lv_example_btn_1();
	
  	while(1) 
	{		
		lv_timer_handler();
		delay_ms(3);
	} 

存在問題

編譯透過後,燒錄到硬體,發現只顯示部分就卡死了
單步除錯時,發生HardFault錯誤
原因是預設的STACK深度不夠,導致跑LVGL的棧溢位了,整個程式就出錯了
需要修改預設的棧深度,針對按鈕測試DEMO,暫改為0x800的大小

Stack_Size      EQU     0x00000800

                AREA    STACK, NOINIT, READWRITE, ALIGN=3
Stack_Mem       SPACE   Stack_Size

顯示圖片

其他

使用片上SRAM可以使用LVGL,使用外部的SRAM也可以正常使用LVGL

總結

這裡只是驗證了,LVGL在STM32F1上面是可以跑起來的,具體的使用及細節還是需要在實際用到的專案去了解及深入

相關文章