目录
介绍
1. 删除源码
2. 导入lvgl到项目screen_mcu中
3. keil添加分组和头文件
4. 移植显示
5. 移植触摸
6. 添加测试案例
6.1. 测试按钮
6.2. 测试音乐界面
7. 提供时钟
错误处理
L6218E错误
出现花屏
屏幕颜色不对
内存分配
介绍
本文 主要介绍GD32移植LVGL的过程,以及移植过程中出现的问题以及解决方法,写的非常详细,基本上都截图了。 移植前需要准备下列:
- 准备包含触摸功能的屏幕
- 实现触摸屏的显示和触摸驱动,确保屏幕没有问题。
- 下载lvgl 8.3版本源码下载地址:https://github.com/lvgl/lvgl
本次移植开发板
- 立创天空星GD32F407VET6
- 1.69寸电容触摸屏 触摸芯片CS816t,显示:st7789
1. 删除源码
删除源码中不需要的文件夹,仅保留如下内容
- demos : lvgl综合案例
- examples :单个功能案例
- src : 源代码
- lv_conf_template.h : 重要的配置文件,里面存在非常多的开关
- lvgl.h : 头文件
将上述内容存放到名为lvgl的文件夹中
2. 导入lvgl到项目screen_mcu中
1、在screen_mcu
项目中新建third_party
文件夹,该文件夹用于存放第三方驱动。
2、将第1个步骤中的lvgl
文件夹拷入其中
3、将lvgl文件夹中的lv_conf_tempalate.h修改
为lv_conf.h
4、把lv_conf.h
的条件编译指令#if 0
修改成#if 1
3. keil添加分组和头文件
1、使用keil打开screen_mcu项目,添加如下分组
third_party/lvgl/example/porting
third_party/lvgl/src/core
third_party/lvgl/src/draw
third_party/lvgl/src/extra
third_party/lvgl/src/font
third_party/lvgl/src/gpu
third_party/lvgl/src/hal
third_party/lvgl/src/misc
third_party/lvgl/src/widgets
2、添加LVGL相关的.c文件到相应分组,如下:
3、添加头文件路径
4、开启C99模式
4. 移植显示
把lv_port_disp_template.c/h的条件编译指令#if 0修改成#if 1
lv_port_disp_template.h中包含输出设备驱动头文件
#include "st7789.h"
lv_port_disp_template.h中宏定义水平和竖直分辨率(默认横屏)
#define MY_DISP_HOR_RES 240 //水平分辨率
#define MY_DISP_VER_RES 280 //垂直分辨率
修改 lv_port_disp_template.c中 的 lv_port_disp_init 函数
配置图形数据缓冲模式
在lv_port_disp_init函数中选择一种缓冲模式,注释掉其它两种模式
在lv_port_disp_template.c中void disp_init函数添加 屏幕显示驱动初始化函数,这里也就是 ST7789_Init();
/*Initialize your display and the required peripherals.*/
static void disp_init(void)
{
/*You code here*/
ST7789_Init();
}
在disp_flush函数中配置打点输出
static void disp_flush(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p)
{
if(disp_flush_enabled) {
ST7789_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);
}
5. 移植触摸
1、把lv_port_indev_tempalte.c/h的条件编译#if 0修改成# if 1
2、在lv_port_indev_tempalte.c中裁剪输入设备
- 只保留touchpad_xxx相关的方法,删除其它方法
- lv_port_indev_init中只保留touchpad相关的代码
- 在touchpad_init方法中执行触摸屏初始化CST816T_Init();
(包含驱动)
/*Initialize your touchpad*/
static void touchpad_init(void)
{
CST816T_Init(); //触摸屏初始化
}
3、配置触摸检测函数
/*Return true is the touchpad is pressed*/
static bool touchpad_is_pressed(void)
{
/*Your code comes here*/
return CST816T_is_pressed();
// return false;
}
4、配置坐标获取函数
/*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*/
CST816T_get_xy((uint16_t*)x,(uint16_t*)y);
//(*x) = 0;
//(*y) = 0;
}
lv_port_indev_template.c
修改之后代码如下:
/**
* @file lv_port_indev_templ.c
*
*/
/*Copy this file as "lv_port_indev.c" and set this value to "1" to enable content*/
#if 1
/*********************
* INCLUDES
*********************/
#include "lv_port_indev_template.h"
//#include "../../lvgl.h"
#include "cst816t.h"
/*********************
* DEFINES
*********************/
/**********************
* TYPEDEFS
**********************/
/**********************
* STATIC PROTOTYPES
**********************/
static void touchpad_init(void);
static void touchpad_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data);
static bool touchpad_is_pressed(void);
static void touchpad_get_xy(lv_coord_t * x, lv_coord_t * y);
static void mouse_init(void);
static void mouse_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data);
static bool mouse_is_pressed(void);
static void mouse_get_xy(lv_coord_t * x, lv_coord_t * y);
static void keypad_init(void);
static void keypad_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data);
static uint32_t keypad_get_key(void);
static void encoder_init(void);
static void encoder_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data);
static void encoder_handler(void);
static void button_init(void);
static void button_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data);
static int8_t button_get_pressed_id(void);
static bool button_is_pressed(uint8_t id);
/**********************
* STATIC VARIABLES
**********************/
lv_indev_t * indev_touchpad;
lv_indev_t * indev_mouse;
lv_indev_t * indev_keypad;
lv_indev_t * indev_encoder;
lv_indev_t * indev_button;
static int32_t encoder_diff;
static lv_indev_state_t encoder_state;
/**********************
* MACROS
**********************/
/**********************
* GLOBAL FUNCTIONS
**********************/
void lv_port_indev_init(void)
{
/**
* Here you will find example implementation of input devices supported by LittelvGL:
* - Touchpad
* - Mouse (with cursor support)
* - Keypad (supports GUI usage only with key)
* - Encoder (supports GUI usage only with: left, right, push)
* - Button (external buttons to press points on the screen)
*
* The `..._read()` function are only examples.
* You should shape them according to your hardware
*/
static lv_indev_drv_t indev_drv;
/*------------------
* Touchpad
* -----------------*/
/*Initialize your touchpad if you have*/
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);
}
/**********************
* STATIC FUNCTIONS
**********************/
/*------------------
* Touchpad
* -----------------*/
/*Initialize your touchpad*/
static void touchpad_init(void)
{
/*Your code comes here*/
CST816T_Init();
}
/*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*/
return CST816T_is_pressed();
// 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*/
CST816T_get_xy((uint16_t*)x,(uint16_t*)y);
//(*x) = 0;
//(*y) = 0;
}
#else /*Enable this file at the top*/
/*This dummy typedef exists purely to silence -Wpedantic.*/
typedef int keep_pedantic_happy;
#endif
6. 添加测试案例
初始化LVGL,包含LVGL相关头文件
6.1. 测试按钮
#include "gd32f4xx.h"
#include "systick.h"
#include <stdio.h>
#include <string.h>
#include "USART.h"
#include "I2C.h"
#include "st7789.h"
#include "cst816t.h"
#include "lvgl.h"
#include "lv_port_disp.h"
#include "lv_port_indev.h"
// 事件回调
void event_handler(lv_event_t* e) {
lv_event_code_t code = lv_event_get_code(e);
if (code == LV_EVENT_VALUE_CHANGED) {
printf("toggled btn\n");
}
}
void demo_button_checkable() {
// 获取显示图层
lv_obj_t* screen = lv_scr_act();
// 创建按钮
lv_obj_t* btn = lv_btn_create(screen);
// 设置按钮尺寸
lv_obj_set_size(btn, 120, 50);
// 4. 在按钮上创建文本并居中显示
lv_obj_t* label = lv_label_create(btn);
lv_label_set_text(label, "Toggle");
// lv_obj_center(label);
lv_obj_align(label, LV_ALIGN_CENTER, 0, 0);
// 5. 设置按钮可选中
lv_obj_add_flag(btn, LV_OBJ_FLAG_CHECKABLE);
// 默认选中
// lv_obj_add_state(btn, LV_STATE_CHECKED);
lv_obj_add_event_cb(btn, event_handler, LV_EVENT_VALUE_CHANGED, NULL);
// 居中
lv_obj_center(btn);
}
主函数
int main(void) {
// 配置全局中断分组
nvic_priority_group_set(NVIC_PRIGROUP_PRE2_SUB2);
// 初始化系统嘀嗒定时器
systick_config();
// 初始化USART
USART_init();
I2C_init();
// 1. 初始化LVGL
lv_init();
// 2.显示屏驱动初始化
lv_port_disp_init();
// 3. 触摸屏(输入设备要在屏幕初始化之后再init,否则会失效)
lv_port_indev_init();
demo_button_checkable();
while(1) {
// 4. 每隔x毫秒调用一次心跳
lv_tick_inc(1);
// 5. 执行定时任务(屏幕渲染,事件处理)
lv_timer_handler();
// 休眠1ms
delay_1ms(1);
}
}
6.2. 测试音乐界面
音乐界面需要接近1M的内存,天空星内存小了,无法测试,下载进去会报几百个错误,显示内存不足,可以使用梁山派。
1、keil添加lvgl/demos/music文件夹下所有.c文件
2、添加相关头文件路径
..\..\third_party\lvgl\demos
..\..\third_party\lvgl\demos\music
3、修改lv_conf.h中的
#define LV_USE_DEMO_MUSIC 0改为#define LV_USE_DEMO_MUSIC 1
#define LV_FONT_MONTSERRAT_12 0改为#define LV_FONT_MONTSERRAT_12 1
#define LV_FONT_MONTSERRAT_16 0改为#define LV_FONT_MONTSERRAT_16 1
#define LV_USE_DEMO_MUSIC 0
改为
#define LV_USE_DEMO_MUSIC 1
#define LV_FONT_MONTSERRAT_12 0
改为
#define LV_FONT_MONTSERRAT_12 1
#define LV_FONT_MONTSERRAT_16 0
改为
#define LV_FONT_MONTSERRAT_16 1
main.c主函数
#include "gd32f4xx.h"
#include "systick.h"
#include <stdio.h>
#include "main.h"
#include "bsp_basic_timer.h"
#include "lcd.h"
#include "touch.h"
#include "lvgl.h"
#include "lv_demo_music.h"
#include "lv_conf.h"
#include "lv_port_disp_template.h"
#include "lv_port_indev_template.h"
int main(void)
{
nvic_priority_group_set(NVIC_PRIGROUP_PRE2_SUB2); // 优先级分组
systick_config();
lv_init();
// 这里必须先初始化disp再初始化indev
lv_port_disp_init();
lv_port_indev_init();
lv_demo_music();
while(1)
{
lv_tick_inc(1);
lv_timer_handler();
delay_1ms(1);
}
}
7. 提供时钟
可以不用定时器时钟,直接放在while循环中,后面的定时器就不需要配置
while(1) {
lv_tick_inc(5);
lv_timer_handler();
delay_1ms(5);
}
在Hardware下创建timer文件夹,文件夹下定义bsp_basic_timer.h和bsp_basic_timer.c文件,内容如下
#ifndef _BSP_BASIC_TIMER_H
#define _BSP_BASIC_TIMER_H
#include "gd32f4xx.h"
#include "systick.h"
#include "stdio.h"
#include "lvgl.h"
#define BSP_TIMER_RCU RCU_TIMER5 // 定时器时钟
#define BSP_TIMER TIMER5 // 定时器
#define BSP_TIMER_IRQ TIMER5_DAC_IRQn // 定时器中断
#define BSP_TIMER_IRQHANDLER TIMER5_DAC_IRQHandler // 定时器中断服务函数
//#define BSP_TIMER_RCU RCU_TIMER2 // 定时器时钟
//#define BSP_TIMER TIMER2 // 定时器
//#define BSP_TIMER_IRQ TIMER2_IRQn // 定时器中断
//#define BSP_TIMER_IRQHANDLER TIMER2_IRQHandler // 定时器中断服务函数
void basic_timer_config(uint16_t pre,uint16_t per); // 基本定时器配置
#endif /* BSP_BASIC_TIMER_H */
#include "bsp_basic_timer.h"
//#include "bsp_led.h"
/************************************************
函数名称 : basic_timer_config
功 能 : 基本定时器配置
参 数 : pre:时钟预分频值
per:周期
*************************************************/
void basic_timer_config(uint16_t pre,uint16_t per)
{
/* 一个周期的时间T = 1/f, 定时时间time = T * 周期
设预分频值位pre,周期位per
time = (pre + 1) * (per + 1) / psc_clk
*/
timer_parameter_struct timere_initpara; // 定义定时器结构体
/* 开启时钟 */
rcu_periph_clock_enable(BSP_TIMER_RCU); // 开启定时器时钟
/* CK_TIMERx = 4 x CK_APB1 = 4x50M = 200MHZ */
rcu_timer_clock_prescaler_config(RCU_TIMER_PSC_MUL4); // 配置定时器时钟
timer_deinit(BSP_TIMER); // 复位定时器
/* 配置定时器参数 */
timere_initpara.prescaler = pre-1; // 时钟预分频值 0-65535 psc_clk = CK_TIMER / pre
timere_initpara.alignedmode = TIMER_COUNTER_EDGE; // 边缘对齐
timere_initpara.counterdirection = TIMER_COUNTER_UP; // 向上计数
timere_initpara.period = per-1; // 周期
/* 在输入捕获的时候使用 数字滤波器使用的采样频率之间的分频比例 */
timere_initpara.clockdivision = TIMER_CKDIV_DIV1; // 分频因子
/* 只有高级定时器才有 配置为x,就重复x+1次进入中断 */
timere_initpara.repetitioncounter = 0; // 重复计数器 0-255
timer_init(BSP_TIMER,&timere_initpara); // 初始化定时器
/* 配置中断优先级 */
nvic_irq_enable(BSP_TIMER_IRQ,3,2); // 设置中断优先级为 3,2
/* 使能中断 */
timer_interrupt_enable(BSP_TIMER,TIMER_INT_UP); // 使能更新事件中断
/* 使能定时器 */
timer_enable(BSP_TIMER);
}
/************************************************
函数名称 : BSP_TIMER_IRQHandler
功 能 : 基本定时器中断服务函数
参 数 : 无
返 回 值 : 无
作 者 : LC
*************************************************/
void BSP_TIMER_IRQHANDLER(void)
{
/* 这里是定时器中断 */
if(timer_interrupt_flag_get(BSP_TIMER,TIMER_INT_FLAG_UP) == SET)
{
timer_interrupt_flag_clear(BSP_TIMER,TIMER_INT_FLAG_UP); // 清除中断标志位
/* 执行功能 */
lv_tick_inc(1);
}
}
定义之后,keil添加.c文件和头文件即可
错误处理
L6218E错误
.\Objects\GD32F450.axf: Error: L6218E: Undefined symbol __aeabi_assert (referred from qrcodegen.o).
解决方案:
添加NDEBUG
宏定义
设置如下即可
出现花屏
出现该现象是由于LVGL相对占用内存较大,需要修改内存问题,在startup_gd32f407_427.s中修改内存大小即可
原始:
Stack_Size EQU 0x00000400
修改后:
Stack_Size EQU 0x00000800
屏幕颜色不对
正常颜色应该和下发模拟器图片一致,按钮是蓝色的,出现该问题的原因:LVGL配置的color depth颜色深度参数和屏幕不一致导致,在配置文件lv_conf.h里面修改LV_COLOR_DEPTH、LV_COLOR_16_SWAP的宏定义。
内存分配
在LVGL中,除了注释不使用相关业务外,有3个位置可以实现修改内存修改,根据自己情况进行来。
1、startup_gd32f407_427.s中修改内存大小,当移植出现花屏的时候,修改这里即可解决。
2、在配置文件lv_conf.h中:LV_MEM_SIZE定义LVGL内部使用的内存池的大小。该内存池用于LVGL的对象、样式、动画等动态分配的内存需求。决定了LVGL可以使用的总内存量
3、显示缓存(Display Buffer)
在配置文件lv_conf.h中,lv_disp_draw_buf_init函数中指定了显示的缓存大小(即MY_DISP_HOR_RES * MY_DISP_VER_RES)直接决定了一次可以绘制的最大像素数量。