写在前面:本菜鸟结合了许多大佬的文章,成功实现了基于LVGL的GUI设计,小开心~浅浅记录一下!~
本文以单片机STM32F103VET6为核心,利用ST7796芯片驱动分辨率为480*320的LCD液晶屏模块,移植LVGL,对接显示接口,对接外部接口——旋转编码器,完成以上两步,就可以实现LVGL的显示和控制啦!Emmm可以开始你的创作了!~
一、显示接口对接
具体步骤:
下载源码文件lvgl-master8.3进行移植
这里我们以LCD显示工程为基础进行移植。
1.新建四个组,分别存放源文件(source)、配置文件(config)、接口层文件(port)、示例(app)。
2.添加文件,lvgl/src下的.c文件移到source中,lv.conf.h添加到config中,lv_port_xxx.c文件添加到port中,示例文件添加到app中。
3.添加路径,勾选C99,此时进行编译会有四个错误,因为C99的原因,在部分函数前要加static,否则会报错,再进行编译会发现还有一个错误:
解决方法是取消勾选Use MicroLIB,再编译就会发现零错误。
4.lv_conf.h配置:主要是一些宏定义,只需要修改一些宏定义即可进行一些定制配置,如显示器的宽度高度、色彩深度、DPI、提供给LVGL的空间等,可根据自己的需求打开即可使用。
5.修改lv_port_disp.c接口文件:
使能后开始对其进行配置,修改头文件名称并添加LCD头文件,添加两个宏,分别是屏幕的宽(480)和高(320)。
修改lv_port_disp_init()显示接口初始化函数,它是一个最顶层的初始化显示设备的函数。然后选择一种写缓存的方式及设置显示分辨,这里我们选择保留第一种写缓存的方式:只使用一个缓冲区
/* 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);
该文件内还有两个函数disp_init()和disp_flush()。在disp_init()中,需要提供屏幕的初始化代码,这里直接调用驱动成功的LCD模块初始化代码LCD_init()。接下来是disp_flush屏幕刷新函数,这个函数需要调用底层LCD的操作接口,这里调用一个画点的函数,因为屏幕是逐个绘制像素点,从而填充一块区域的,所以也可以直接调用一个绘制方形函数填充区域显示。
/*Initialize your display and the required peripherals.*/
static void disp_init(void)
{
/*You code here*/
LCD_Init();
}
/*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)
{
/*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, *color_p)*/
LCD_DrawPoint(x,y,color_p->full);
color_p++;
}
}
/*IMPORTANT!!!
*Inform the graphics library that you are ready with the flushing*/
lv_disp_flush_ready(disp_drv);
}
6.最后需要配置一个定时器为LVGL提供心跳,这里可以使用系统滴答定时器,也可以使用其他定时器配置,然后在主循环中调用lv_task_handler()函数,这样需要显示的内容才会实时更新到屏幕上,至此LVGL的显示配置完成。
while(1)
{
lv_tick_inc(5);
lv_task_handler();
delay_ms(5);
}
二、旋转编码器接口对接
首先在lv_port_indev.c的接口初始化中保留Encoder部分,在encoder_init()中编写旋转编码器相关的初始化代码,再指定用于读取按键状态的函数encoder_read,将旋转编码器注册到LVGL中。最后对encoder相关函数进行编写,主要是encoder_read,分为三种情形,分别是旋转编码器左旋、右旋和按下时对应控制界面的功能。
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;
/*------------------
* Encoder
* -----------------*/
/*Initialize your encoder if you have*/
encoder_init();
/*Register a encoder input device*/
lv_indev_drv_init(&indev_drv);
indev_drv.type = LV_INDEV_TYPE_ENCODER;
indev_drv.read_cb = encoder_read;
indev_encoder = lv_indev_drv_register(&indev_drv);
/*Later you should create group(s) with `lv_group_t * group = lv_group_create()`,
*add objects to the group with `lv_group_add_obj(group, obj)`
*and assign this input device to group to navigate in it:
*`lv_indev_set_group(indev_encoder, group);`*/
lv_group_t * group= lv_group_create();
lv_indev_set_group(indev_encoder, group);
/*------------------
* Encoder
* -----------------*/
/*Initialize your keypad*/
static void encoder_init(void)
{
/*Your code comes here*/
Encoder_Init();
}
/*Will be called by the library to read the encoder*/
static void encoder_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data)
{
static uint32_t last_key = 0;
uint32_t act_key = Encoder_Scan(0);
if(act_key != 0) {
switch(act_key) {
case 1:
act_key = LV_KEY_ENTER;
encoder_state = LV_INDEV_STATE_PR;
break;
case 2:
act_key = LV_KEY_LEFT;
encoder_diff = -1;
encoder_state = LV_INDEV_STATE_REL;
break;
case 3:
act_key = LV_KEY_RIGHT;
encoder_state = LV_INDEV_STATE_REL;
encoder_diff = 1;
break;
}
last_key = act_key;
}
else{
encoder_diff = 0;
encoder_state = LV_INDEV_STATE_REL;
}
data->key = last_key;
data->enc_diff = encoder_diff;
data->state = encoder_state;
encoder_diff=0;
}
/*Call this function in an interrupt to process encoder events (turn, press)*/
static void encoder_handler(void)
{
/*Your code comes here*/
encoder_diff += 0;
encoder_state = LV_INDEV_STATE_REL;
}
与其他输入设备接口不同的是,旋转编码器较特殊,光移植完还无法使用,如需使用还要创建“组”。这里引入“组”这一概念,当需要用键盘或者编码器来模拟按键控制对象时,就需要把控制对象添加进“组”。例如,如果一个旋钮被聚焦,当向左或向右旋转编码器时,旋钮的值会随之改变。这种操作的前提便是将输入设备与“组”关联起来,即通过lv_group_add_obj()函数将控件添加进“组”,才能实现编码器与LVGL接口对接和实现界面控制。但是需要注意的是,并非所有控件都能够加入“组”并且被使用。比如我们添加“标签”这个控件时,即使将它加入到“组”中,用编码器旋转聚焦时也不会切换到这个“标签”。
将所需控件加入“组”旋转编码器即可正常使用,即可完成编码器接口与LVGL的对接。