Skip to content

LVGL 的移植

1. 参考视频

LVGL 的移植

2. 以下移植硬件准备

  • STM32F411 开发板
  • 4.0寸 320*480 的 TFT 屏

3. 硬件连接

4. 创建工程.

直接使用 stm32cubemx 创建工程.

5. 先驱动TFT屏及触摸屏.

先驱动TFT屏及触摸屏.完成屏幕及触摸的基本功能.

  1. 添加TFT屏驱动文件. 对于TFT屏的驱动主要实现两个函数即可

    1. void lcd_init() 函数. 完成初始化硬件以及屏幕初始化.
    2. void lcd_flush(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint16_t *color) 函数. 完成屏幕刷新. 这个函数中的最后一个参数是一个像素的数组指针.不是单一像素颜色值. 数据填充时,会逐一填充数组中的每一个颜色值. 这个数组的长度是 (x1 - x0 + 1) * (y1 - y0 + 1). 这个函数是重点.需要实现好.
  2. 添加触摸屏驱动文件. 对于触摸屏的驱动主要实现两个函数即可

    1. void ctp_init() 函数. 完成初始化硬件以及触摸屏初始化.
    2. void ctp_read_touch_data(FT6336_TouchData *touch_data) 函数. 完成获取触摸屏坐标. 这个函数中的参数是坐标的指针.这个函数是重点.需要实现好.
  3. 准备一个定时器(如:tim2),定时间隔为1ms.并启动中断. 这个定时器是为 lvgl 的时基用的.

6. 将源码中的有用文件拷到工程中.

  1. 需要的文件只需要4部分文件.

  2. examples 文件夹中的 porting 文件夹是需要的.其它文件删除.

7. 修改lvgl中的文件改名

源码根目录 文件夹中的 lv_conf_template.h 文件改名为 lv_conf.h.

8. 添加 lvgl 源码添加至工程中.

按图片示意将 lvgl 文件夹中的文件添加至工程中.

9. 修改编译选项.(重点)

添加了这么多的文件,需要将添加的这些文件让编译器知道才行. 那就需要修改CMakeLists.txt 文件.

  1. 添加 "*.c" 文件至 SOURCES 列表中.
cmake
   file(GLOB_RECURSE LVGL_SOURCES
    "Middlewares/Third_Party/lvgl/lvgl_src/lvgl/*.c"
    )
   target_sources(${CMAKE_PROJECT_NAME} PRIVATE ${LVGL_SOURCES})

这里使用了GLOB_RECURSE,这个选项,可以递归的查找文件. 否则只能是GLOB,只能查找一层目录. 2. 添加 "*.h" 文件至 INCLUDES 列表中.

cmake
   target_include_directories(${CMAKE_PROJECT_NAME} PRIVATE
    # Add user defined include paths
    "Middlewares/Third_Party/lvgl/lvgl_src/lvgl"
    "Middlewares/Third_Party/lvgl/lvgl_src/lvgl/src"
    "Middlewares/Third_Party/lvgl/lvgl_src/lvgl/examples/porting"
)

只需要包含上面的三个目录即可,其它的目录中的.h文件不是全局的,可以不用加.

10. 修改 lv_conf.h 文件.

将开头的 #if 0 修改为 #if 1. 来启动lvgl 的配置文件.(相应的.h文件也要做相同的修改)

11. 修改 显示相关的函数 文件.

修改: Middlewares/Third_Party/lvgl/lvgl_src/lvgl/examples/porting/lv_port_disp.c 文件.

  • #if 0 修改为 #if 1 以启动编译这个文件
  • 修改屏幕的尺寸:
c
#ifndef MY_DISP_HOR_RES
   // #warning Please define or replace the macro MY_DISP_HOR_RES with the actual screen width, default value 320 is used for now.
    #define MY_DISP_HOR_RES  320
#endif

#ifndef MY_DISP_VER_RES
   // #warning Please define or replace the macro MY_DISP_VER_RES with the actual screen height, default value 240 is used for now.
    #define MY_DISP_VER_RES   480
#endif

这个尺寸的值要与屏幕的尺寸一致.并且屏幕的方向也要一致.这一点很重要.

  • 修改刷新屏幕的方式: 一般对于mcu来讲使用静态缓冲区刷新.Example for 1 这种方式是首选.
c
    /* Example for 1) */
    static lv_disp_draw_buf_t draw_buf_dsc_1;
    static lv_color_t buf_1[MY_DISP_HOR_RES * 10];                          /*A buffer for 10 rows*/
    lv_disp_draw_buf_init(&draw_buf_dsc_1, buf_1, NULL, MY_DISP_HOR_RES * 10);   /*Initialize the display buffer*/

    // /* Example for 2) */
    // static lv_disp_draw_buf_t draw_buf_dsc_2;
    // static lv_color_t buf_2_1[MY_DISP_HOR_RES * 10];                        /*A buffer for 10 rows*/
    // static lv_color_t buf_2_2[MY_DISP_HOR_RES * 10];                        /*An other buffer for 10 rows*/
    // lv_disp_draw_buf_init(&draw_buf_dsc_2, buf_2_1, buf_2_2, MY_DISP_HOR_RES * 10);   /*Initialize the display buffer*/

    // /* Example for 3) also set disp_drv.full_refresh = 1 below*/
    // static lv_disp_draw_buf_t draw_buf_dsc_3;
    // static lv_color_t buf_3_1[MY_DISP_HOR_RES * MY_DISP_VER_RES];            /*A screen sized buffer*/
    // static lv_color_t buf_3_2[MY_DISP_HOR_RES * MY_DISP_VER_RES];            /*Another screen sized buffer*/
    // lv_disp_draw_buf_init(&draw_buf_dsc_3, buf_3_1, buf_3_2,
    //                       MY_DISP_VER_RES * LV_VER_RES_MAX);   /*Initialize the display buffer*/
  • 修改 staic void * disp_init() 函数. 添加如下代码
c
/*Initialize your display and the required peripherals.*/
static void disp_init(void)
{
    /*You code here*/
    lcd_init();  //这是新添加的代码, 初始化屏幕
}
  • 修改 static void disp_flush(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p) 函数. 添加如下代码
c
/*Flush the content of the internal buffer the specific area on the display
 *You can use DMA or any hardware acceleration to do this operation in the background but
 *'lv_disp_flush_ready()' has to be called when finished.*/
static void disp_flush(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p)
{
    if(disp_flush_enabled) {
        /*The most simple case (but also the slowest) to put all pixels to the screen one-by-one*/

        //这是新添加的代码, 刷新屏幕.
        lcd_fill_rectangle(area->x1, area->y1, area->x2, area->y2,
                           *(uint16_t *)color_p);

        // 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, *color_p)*/
        //         color_p++;
        //     }
        // }
    }

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

12. 添加 lv_port_indev.c 文件.

这个文件是添加触摸屏的驱动文件.在这个文件中添加相关的驱动 .

  • #if 0 改为 #if 1,(相应的.h文件也要做相同的修改)
  • 这个indev.c文件中,包含了除了触摸方式的输入方式外还有鼠标输入\键盘输入\编码器输入和按钮输入的四种输入方式. 需要将其它的所有的输入方式全部注释掉. 只保留触摸输入方式.
  • 添加触摸屏的初始化代码.
c
/*Initialize your touchpad*/
static void touchpad_init(void)
{
    /*Your code comes here*/
    ctp_reset();
    ctp_init();
    
}
  • 修改 static bool touchpad_is_pressed(void) 函数. 添加获取是否触摸的判断代码
c

/*Return true is the touchpad is pressed*/
static bool touchpad_is_pressed(void)
{
    /*Your code comes here*/
    if (ctp_read_touch_data(&touch_data) == 0)
    {
        if (touch_data.touch_count > 0) {
            return true;
        }
    }

      return false;
}
  • 修改 static void touchpad_get_xy(lv_coord_t * x, lv_coord_t * y) 函数. 添加获取触摸数据的代码.
c
/*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*/
    if (touch_data.touch_count > 0) {
      (*x) = touch_data.points[0].x;
      (*y) = touch_data.points[0].y;
    }else{
        (*x) = 0;
        (*y) = 0;
    }

}

13. 给 LVGL 添加一个时基.也就是每1ms执行一个lvgl的函数

这个时基是lvgl为了动画和一些操作,所必需要的一个心跳包. 我们使用一个定时器来产生一个时期 我们通过 stm32cubemx 来配置一个简单定时器.

  • 配置一个定时器,定时器周期为1ms,定时器中断优先级为1,定时器时钟为80Mhz
  • 方式1:在定时器中断中添加如下代码
c
/* USER CODE BEGIN 2 */
/*This function will be called 1ms */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
    lv_tick_inc(1);
}
  • 方式2: 如果启动了 TIM的注册回调的话, 那就在 main.c中注册一个回调函数.

添加如下代码

c
void MyTim2Callback(TIM_HandleTypeDef *htim) {
  if (htim->Instance == TIM2) {
    // 在这里处理TIM2的定时器事件
    //HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin); // 切换PA5上的LED状态
    lv_tick_inc(1); // 每1ms调用一次lv_tick_inc函数,通知LVGL系统时间已经过去了1ms
  }
  return;
}

  if (HAL_TIM_RegisterCallback(&htim2, HAL_TIM_PERIOD_ELAPSED_CB_ID,
                               MyTim2Callback) != HAL_OK) {
    Error_Handler();
  }

  //还需要开启定时器,并启动中断
    // 使能TIM2定时器并使能中断
  HAL_TIM_Base_Start_IT(&htim2);

14. 移植完成.

可以先编译一下,看有没有错误.