LVGL 的移植
1. 参考视频
2. 以下移植硬件准备
- STM32F411 开发板

- 4.0寸 320*480 的 TFT 屏

3. 硬件连接


4. 创建工程.
直接使用 stm32cubemx 创建工程.
5. 先驱动TFT屏及触摸屏.
先驱动TFT屏及触摸屏.完成屏幕及触摸的基本功能.
添加TFT屏驱动文件. 对于TFT屏的驱动主要实现两个函数即可
void lcd_init()函数. 完成初始化硬件以及屏幕初始化.void lcd_flush(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint16_t *color)函数. 完成屏幕刷新. 这个函数中的最后一个参数是一个像素的数组指针.不是单一像素颜色值. 数据填充时,会逐一填充数组中的每一个颜色值. 这个数组的长度是(x1 - x0 + 1) * (y1 - y0 + 1). 这个函数是重点.需要实现好.
添加触摸屏驱动文件. 对于触摸屏的驱动主要实现两个函数即可
void ctp_init()函数. 完成初始化硬件以及触摸屏初始化.void ctp_read_touch_data(FT6336_TouchData *touch_data)函数. 完成获取触摸屏坐标. 这个函数中的参数是坐标的指针.这个函数是重点.需要实现好.
准备一个定时器(如:tim2),定时间隔为1ms.并启动中断. 这个定时器是为 lvgl 的时基用的.
6. 将源码中的有用文件拷到工程中.
需要的文件只需要4部分文件.

将
examples文件夹中的porting文件夹是需要的.其它文件删除.
7. 修改lvgl中的文件改名
将 源码根目录 文件夹中的 lv_conf_template.h 文件改名为 lv_conf.h.
8. 添加 lvgl 源码添加至工程中.
按图片示意将 lvgl 文件夹中的文件添加至工程中.

9. 修改编译选项.(重点)
添加了这么多的文件,需要将添加的这些文件让编译器知道才行. 那就需要修改CMakeLists.txt 文件.
- 添加 "*.c" 文件至
SOURCES列表中.
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 列表中.
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以启动编译这个文件 - 修改屏幕的尺寸:
#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这种方式是首选.
/* 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()函数. 添加如下代码
/*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)函数. 添加如下代码
/*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文件中,包含了除了触摸方式的输入方式外还有鼠标输入\键盘输入\编码器输入和按钮输入的四种输入方式. 需要将其它的所有的输入方式全部注释掉. 只保留触摸输入方式.
- 添加触摸屏的初始化代码.
/*Initialize your touchpad*/
static void touchpad_init(void)
{
/*Your code comes here*/
ctp_reset();
ctp_init();
}- 修改
static bool touchpad_is_pressed(void)函数. 添加获取是否触摸的判断代码
/*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)函数. 添加获取触摸数据的代码.
/*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:在定时器中断中添加如下代码
/* 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中注册一个回调函数.
添加如下代码
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. 移植完成.
可以先编译一下,看有没有错误.
