1 前言
克服了这么多困难终于使用lvgl点亮屏幕了,到了这步也算是拨开云雾见天日了。因为打通了从软件到硬件的整个流程,所以后面有时间也可以搞一些花活了。
2 基于framebuffer的lvgl移植
屏幕显示驱动采用framebuffer框架,移植lvgl时也要基于此来配置。因为官方教程非常全面,我也只是适配时修改了一些代码而已;所以图省事,这里就基于官方教程编写我的博客了。
2.1 lvgl的移植
2.1.1 所需资源
资源名称资源名称 | 仓库地址 | 描述 |
---|---|---|
lvgl | https://github.com/lvgl/lvgl.git | LVGL图形界面控件的源码以及少量例程 |
lv_drivers | https://github.com/lvgl/lv_drivers.git | 驱动LVGL图形界面的驱动接口源代码 |
lv_demos | https://github.com/lvgl/lv_demos.git | LVGL的例程 |
lv_port_linux_frame_buffer | https://github.com/lvgl/lv_port_linux_frame_buffer.git | 适配有Framebuffer的linux系统的接口 |
2.1.2 拉取资源
在根目录下创建一个文件夹存放官方源码:
luckfox@luckfox:~$ mkdir lvgl
luckfox@luckfox:~$ cd lvgl
拉取资源:
git clone -b v8.1.0 https://github.com/lvgl/lvgl.git
git clone -b v8.1.0 https://github.com/lvgl/lv_drivers.git
git clone -b v8.1.0 https://github.com/lvgl/lv_demos.git
git clone --branch release/v8.2 --single-branch https://github.com/lvgl/lv_port_linux_frame_buffer.git
成功拉取后,在根目录下创建一个工程目录,并创建一个工程:
luckfox@luckfox:~$ mkdir lvgl_project
luckfox@luckfox:~$ cd lvgl_project/
luckfox@luckfox:~/lvgl_project$ mkdir project_01
luckfox@luckfox:~/lvgl_project$ cd project_01/
2.1.3 复制文件
- 复制根目录下的 lvgl 文件夹中的 lvgl、lv_drivers 目录
- 复制 lv_port_linux_frame_buffer 中的 main.c 与 Makefile
- 复制 lvgl 中的 lv_conf_template.h 重命名为 lv_conf.h
- 复制 lv_drivers 中的 lv_drv_conf_template.h 重命名为 lv_drv_conf.h
cp -r ~/lvgl/lvgl ./
cp -r ~/lvgl/lv_drivers ./
cp ~/lvgl/lvgl/lv_conf_template.h ./lv_conf.h
cp ~/lvgl/lv_drivers/lv_drv_conf_template.h ./lv_drv_conf.h
cp ~/lvgl/lv_port_linux_frame_buffer/main.c ./
cp ~/lvgl/lv_port_linux_frame_buffer/Makefile ./
查看 project_01 工程目录下文件:
luckfox@luckfox:~/lvgl_project/project_01$ ls -l
总用量 64
-rw-rw-r-- 1 luckfox luckfox 29023 12月 27 19:14 lv_conf.h
drwxrwxr-x 12 luckfox luckfox 4096 12月 27 19:14 lv_drivers
-rw-rw-r-- 1 luckfox luckfox 15184 12月 27 19:14 lv_drv_conf.h
drwxrwxr-x 12 luckfox luckfox 4096 12月 27 19:14 lvgl
-rw-rw-r-- 1 luckfox luckfox 2350 12月 27 19:14 main.c
-rw-rw-r-- 1 luckfox luckfox 2321 12月 27 19:14 Makefile
2.1.4 修改文件
lv_conf.h
1.使能
将一开始的 #if 0 改成 #if 1。
/* clang-format off */
#if 1 /*Set it to "1" to enable content*/
2.分配显存
使能 LV_MEM_CUSTOM ,选择自行分配显存。
/*1: use custom malloc/free, 0: use the built-in lv_mem_alloc()
and lv_mem_free()
*/#define LV_MEM_CUSTOM 1
3.刷新时间
将原本的 30ms 调整成了 10ms。
/*Default display refresh period. LVG will redraw changed areas with this period time*/#define LV_DISP_DEF_REFR_PERIOD 10 /*[ms]*//*Input device read period in milliseconds*/#define LV_INDEV_DEF_READ_PERIOD 10 /*[ms]*/
4.TICK配置
使能 LV_TICK_CUSTOM ,选择在应用程序中自定义 Tick 定时器配置函数。
原内容:
#define LV_TICK_CUSTOM 0#if LV_TICK_CUSTOM
#define LV_TICK_CUSTOM_INCLUDE "Arduino.h" /*Header for the system time function*/
#define LV_TICK_CUSTOM_SYS_TIME_EXPR (millis()) /*Expression evaluating to current system time in ms*/
#endif /*LV_TICK_CUSTOM*/
更改为:
#define LV_TICK_CUSTOM 1
#if LV_TICK_CUSTOM
#define LV_TICK_CUSTOM_INCLUDE /*Header for the system time function*/
#define LV_TICK_CUSTOM_SYS_TIME_EXPR (custom_tick_get()) /*Expression evaluating to current system time in ms*/
#endif /*LV_TICK_CUSTOM*/
lv_drv_conf.h
1.使能
将一开始的 #if 0 改成 #if 1。
/* clang-format off */
#if 1 /*Set it to "1" to enable the content*/
2.支持设备
使能 USE_FBDEV ,支持Framebuffer设备。
/*----------------------------------------- * Linux frame buffer device (/dev/fbx) *-----------------------------------------*/
#ifndef USE_FBDEV
# define USE_FBDEV 1
#endif
#if USE_FBDEV# define FBDEV_PATH "/dev/fb0"
#endif
Makefile
替换原 Makefile 内容,将 Makefile 中
#
# Makefile
#
CC = /tools/linux/toolchain/arm-rockchip830-linux-uclibcgnueabihf/bin/arm-rockchip830-linux-uclibcgnueabihf-gcc
LVGL_DIR_NAME ?= lvgl
LVGL_DIR ?= .
WARNINGS := -Wall -Wshadow -Wundef -Wmissing-prototypes -Wno-discarded-qualifiers -Wall -Wextra -Wno-unused-function -Wno-error=strict-prototypes -Wpointer-arith \
-fno-strict-aliasing -Wno-error=cpp -Wuninitialized -Wmaybe-uninitialized -Wno-unused-parameter -Wno-missing-field-initializers -Wtype-limits -Wsizeof-pointer-memaccess \
-Wno-format-nonliteral -Wno-cast-qual -Wunreachable-code -Wno-switch-default -Wreturn-type -Wmultichar -Wformat-security -Wno-ignored-qualifiers -Wno-error=pedantic \
-Wno-sign-compare -Wno-error=missing-prototypes -Wdouble-promotion -Wclobbered -Wdeprecated -Wempty-body -Wtype-limits -Wshift-negative-value -Wstack-usage=2048 \
-Wno-unused-value -Wno-unused-parameter -Wno-missing-field-initializers -Wuninitialized -Wmaybe-uninitialized -Wall -Wextra -Wno-unused-parameter \
-Wno-missing-field-initializers -Wtype-limits -Wsizeof-pointer-memaccess -Wno-format-nonliteral -Wpointer-arith -Wno-cast-qual -Wmissing-prototypes \
-Wunreachable-code -Wno-switch-default -Wreturn-type -Wmultichar -Wno-discarded-qualifiers -Wformat-security -Wno-ignored-qualifiers -Wno-sign-compare -std=c99
CFLAGS ?= -O3 -g0 -I$(LVGL_DIR)/ $(WARNINGS)
LDFLAGS ?= -lm
BIN = demo
BUILD_DIR = ./build
BUILD_OBJ_DIR = $(BUILD_DIR)/obj
BUILD_BIN_DIR = $(BUILD_DIR)/bin
prefix ?= /usr
bindir ?= $(prefix)/bin
#Collect the files to compile
MAINSRC = $(wildcard ./*.c)
include $(LVGL_DIR)/lvgl/lvgl.mk
include $(LVGL_DIR)/lv_drivers/lv_drivers.mk
# CSRCS +=$(LVGL_DIR)/mouse_cursor_icon.c
OBJEXT ?= .o
AOBJS = $(ASRCS:.S=$(OBJEXT))
COBJS = $(CSRCS:.c=$(OBJEXT))
MAINOBJ = $(MAINSRC:.c=$(OBJEXT))
SRCS = $(ASRCS) $(CSRCS) $(MAINSRC)
OBJS = $(AOBJS) $(COBJS) $(MAINOBJ)
TARGET = $(addprefix $(BUILD_OBJ_DIR)/, $(patsubst ./%, %, $(OBJS)))
## MAINOBJ -> OBJFILES
all: default
$(BUILD_OBJ_DIR)/%.o: %.c
@mkdir -p $(dir $@)
@$(CC) $(CFLAGS) -c $< -o $@
@echo "CC $<"
default: $(TARGET)
@mkdir -p $(dir $(BUILD_BIN_DIR)/)
$(CC) -o $(BUILD_BIN_DIR)/$(BIN) $(TARGET) $(LDFLAGS)
clean:
rm -rf $(BUILD_DIR)
install:
install -d $(DESTDIR)$(bindir)
install $(BUILD_BIN_DIR)/$(BIN) $(DESTDIR)$(bindir)
uninstall:
$(RM) -r $(addprefix $(DESTDIR)$(bindir)/,$(BIN))
main.c
1.注释头文件
// #include "lvgl/demos/lv_demos.h"
// #include "lv_drivers/indev/evdev.h"
1.修改屏幕分辨率
#define DISP_BUF_SIZE (240 * 240)...disp_drv.hor_res = 240;disp_drv.ver_res = 240;
照着做完后,因为屏幕尺寸问题,所以这里修改一下。
//#include "lv_demos/lv_demo.h"
#define DISP_BUF_SIZE (480 * 320)
2.注释下面代码
无外部输入设备,注释下面代码:
evdev_init();
static lv_indev_drv_t indev_drv_1;
lv_indev_drv_init(&indev_drv_1); /*Basic initialization*/indev_drv_1.type = LV_INDEV_TYPE_POINTER;
/*This function will be called periodically (by the library) to get the mouse position and state*/
indev_drv_1.read_cb = evdev_read;
lv_indev_t *mouse_indev = lv_indev_drv_register(&indev_drv_1);
未移植鼠标样式,注释下面代码:
/*Set a cursor for the mouse*/
LV_IMG_DECLARE(mouse_cursor_icon)lv_obj_t * cursor_obj = lv_img_create(lv_scr_act()); /*Create an image object for the cursor */
lv_img_set_src(cursor_obj, &mouse_cursor_icon);/*Set the image source*/lv_indev_set_cursor(mouse_indev, cursor_obj); /*Connect the image object to the driver*/
不使用官方demo,注释下面代码:
/*Create a Demo*/lv_demo_widgets();
2.1.5 编译和运行
上述文件修改完成后,在命令行输入make命令编译工程,编译完成后会在/build/bin目录下生成一个可执行文件demo,编译后的目录结构如下:
project_01/
├── build
│ ├── bin
│ │ └── demo
│ └── obj
│ ├── lv_drivers
│ ├── lvgl
│ └── ...
├── lv_conf.h
├── lv_drivers
├── lv_drv_conf.h
├── lvgl
├── main.c
└── Makefile
我们只需要将demo拷贝至开发板,即可在板子上运行程序。后续可以根据需求自行修改 main.c 文件和添加文件,实现自己想要的显示效果。
2.2 配置自己的项目
2.2.1 插入图片
因为lvgl显示的图片需要特殊处理,需要去官网转换格式(点我跳转)。
(注意,PNG图片颜色格式选择CF_TRUE_COLOR_ALPHA,JPG图片颜色格式选择CF_TRUE_COLOR,转换完成后将c文件拷贝至工程目录。)
如下是我实际存放图片的路径:
lvgl_project/project_01/yuanse.c
2.2.2 编写自己的mian.c主文件
如下是我自己基于我的屏幕编写的主文件代码,主要功能是显示自己的图片和按键。
main.c
#include "lvgl/lvgl.h"
//#include "DEV_Config.h"
#include "lv_drivers/display/fbdev.h"
#include
#include
#include
#include
//#include "lv_demos/lv_demo.h"
#define DISP_BUF_SIZE (480 * 320)
/*Image declare*/
LV_IMG_DECLARE(zhuomian);
LV_IMG_DECLARE(cat);
LV_IMG_DECLARE(galaxy);
LV_IMG_DECLARE(saint);
void fbdev_flush(lv_disp_drv_t * drv, const lv_area_t * area, lv_color_t * color_p);
/**
* A meter with multiple arcs
*/
void lv_show_img(lv_obj_t * img,const lv_img_dsc_t img_dat){
lv_obj_clean(img);
lv_img_set_src(img, &img_dat);
lv_obj_center(img);
}
int main(void)
{
/*LittlevGL init*/
lv_init();
/*Linux frame buffer device init*/
fbdev_init();
/*A small buffer for LittlevGL to draw the screen's content*/
static lv_color_t buf[DISP_BUF_SIZE];
/*Initialize a descriptor for the buffer*/
static lv_disp_draw_buf_t disp_buf;
lv_disp_draw_buf_init(&disp_buf, buf, NULL, DISP_BUF_SIZE);
/*Initialize and register a display driver*/
static lv_disp_drv_t disp_drv;
lv_disp_drv_init(&disp_drv);
disp_drv.draw_buf = &disp_buf;
disp_drv.flush_cb = fbdev_flush;
disp_drv.hor_res = 320;
disp_drv.ver_res = 480;
lv_disp_drv_register(&disp_drv);
/*Initialize pin*/
// DEV_ModuleInit();
/*Show an image*/
lv_obj_t *scr = lv_disp_get_scr_act(NULL);
lv_obj_t *img = lv_img_create(scr);
printf("显示zhuomian的图片\n");
lv_show_img(img,zhuomian);
lv_obj_center(img);
printf("显示zhuomian的图片完成\n");
// /*Create a cursor*/
// lv_obj_t *cursor = lv_img_create(scr);
// lv_img_set_src(cursor, LV_SYMBOL_GPS);
// lv_obj_set_pos(cursor, 70, 120);
// int x=70,y=120,move=0;
//lv_demo_widgets();
// lv_show_img(img,zhuomian);
/*Handle LitlevGL tasks (tickless mode)*/
//lv_demo_widgets();
//创建按键
lv_obj_t *obj = lv_obj_create(lv_scr_act());
lv_obj_set_size(obj,40,40);
lv_obj_set_pos(obj,240,150);
while(1) {
// printf("进入while死循环\n");
lv_show_img(img,zhuomian);
lv_timer_handler();
usleep(5000);
}
return 0;
}
/*Set in lv_conf.h as LV_TICK_CUSTOM_SYS_TIME_EXPR
*/
uint32_t custom_tick_get(void)
{
static uint64_t start_ms = 0;
if(start_ms == 0) {
struct timeval tv_start;
gettimeofday(&tv_start, NULL);
start_ms = (tv_start.tv_sec * 1000000 + tv_start.tv_usec) / 1000;
}
struct timeval tv_now;
gettimeofday(&tv_now, NULL);
uint64_t now_ms;
now_ms = (tv_now.tv_sec * 1000000 + tv_now.tv_usec) / 1000;
uint32_t time_ms = now_ms - start_ms;
return time_ms;
}
至此就可以简单的显示一个图片,创建一个自己的按钮了。如下是显示效果:
2.3 适配lvgl的注意事项
2.3.1 颜色数据转换
在读ili9488的产品手册时发现屏幕默认使用的是大端序,而机器默认使用的是小端序(这里在硬件篇做了详细解释);用户需要额外编写转换代码调整数据顺序。lvgl针对屏幕做了专门的优化,如果驱动没有编写特定的转换函数,可以使用lvgl附带的如下接口(内核的接口在framebuffer驱动篇里有提)。我觉得不用内核接口就用官方接口,因为自己写的转换数据代码效率大概率是低于官方内嵌代码的!
/*Swap the 2 bytes of RGB565 color. Useful if the display has an 8-bit interface (e.g. SPI)*/
#define LV_COLOR_16_SWAP 0
2.3.2 lvgl支持颜色格式问题
查询lv_conf.h文件,可以看到lvgl支持的颜色格式,我使用的是RGB565。需要注意的是没写代表不支持,lvgl不支持的颜色数据格式有18位(rgb666)、24位(rgb888);如果想使用这两种格式,需要额外进行数据转换,这会导致传输效率的降低,进而影响显示效果,所以最好在一开始选型阶段就避免此问题。
/*Color depth: 1 (1 byte per pixel), 8 (RGB332), 16 (RGB565), 32 (ARGB8888)*/
#define LV_COLOR_DEPTH 16
注意在图片显示无问题后,建议在网上找个三原色的图片用lvgl显示一下;查看自己移植的代码有没有问题,三原色显示有没有偏差。否则花费半天时间设计出的各种效果,会因为这个问题重新返工(图片是因为尺寸问题才显示不对的,只是为了看三原色)。
至此显示功能颜色无偏差、图片正常显示,完成了lvgl显示部分移植工作。
3 移植触摸功能
因为工作问题,没有完全适配完触摸功能,这里简单介绍一下官方的触摸屏驱动接口。
3.1 xpt2046框架基本介绍
如下是lv_drivers/indev/XPT2046.c中的触摸驱动框架,只需要实现几个接口函数即可实现触摸功能。
/**
* @file XPT2046.c
*
*/
/*********************
* INCLUDES
*********************/
#include "XPT2046.h"
#if USE_XPT2046
#include
#include LV_DRV_INDEV_INCLUDE
#include LV_DRV_DELAY_INCLUDE
/*********************
* DEFINES
*********************/
#define CMD_X_READ 0b10010000 //芯片x轴读取命令0x90
#define CMD_Y_READ 0b11010000 //芯片y轴读取命令0xd0
/**********************
* TYPEDEFS
**********************/
/**********************
* STATIC PROTOTYPES
**********************/
static void xpt2046_corr(int16_t * x, int16_t * y);
static void xpt2046_avg(int16_t * x, int16_t * y);
/**********************
* STATIC VARIABLES
**********************/
int16_t avg_buf_x[XPT2046_AVG];
int16_t avg_buf_y[XPT2046_AVG];
uint8_t avg_last;
/**********************
* MACROS
**********************/
/**********************
* GLOBAL FUNCTIONS
**********************/
/**
* Initialize the XPT2046
// 触摸驱动的初始化函数,需要自己填充。
*/
void xpt2046_init(void)
{
}
/**
* Get the current position and state of the touchpad
* @param data store the read data here
* @return false: because no ore data to be read
//坐标读取函数
*/
bool xpt2046_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data)
{
static int16_t last_x = 0;
static int16_t last_y = 0;
uint8_t buf;
int16_t x = 0;
int16_t y = 0;
uint8_t irq = LV_DRV_INDEV_IRQ_READ; //中断的宏,这个获取中断的宏函数需要自己实现
if(irq == 0) {
//向内核驱动发送片选信号,选择触摸屏,宏函数自己实现
LV_DRV_INDEV_SPI_CS(0);
//向内核驱动发送读取x轴的命令,宏函数自己实现
LV_DRV_INDEV_SPI_XCHG_BYTE(CMD_X_READ); /*Start x read*/
//读取x轴数据,宏函数自己实现
buf = LV_DRV_INDEV_SPI_XCHG_BYTE(0); /*Read x MSB*/
x = buf << 8;
buf = LV_DRV_INDEV_SPI_XCHG_BYTE(CMD_Y_READ); /*Until x LSB converted y command can be sent*/
x += buf;
buf = LV_DRV_INDEV_SPI_XCHG_BYTE(0); /*Read y MSB*/
y = buf << 8;
buf = LV_DRV_INDEV_SPI_XCHG_BYTE(0); /*Read y LSB*/
y += buf;
/*Normalize Data*/
x = x >> 3;
y = y >> 3;
xpt2046_corr(&x, &y);
xpt2046_avg(&x, &y);
last_x = x;
last_y = y;
data->state = LV_INDEV_STATE_PR;//更新状态为按下
LV_DRV_INDEV_SPI_CS(1);
} else {
x = last_x;
y = last_y;
avg_last = 0;
data->state = LV_INDEV_STATE_REL;
}
data->point.x = x;
data->point.y = y;
return false;
}
/**********************
* STATIC FUNCTIONS
**********************/
static void xpt2046_corr(int16_t * x, int16_t * y)
{
#if XPT2046_XY_SWAP != 0
int16_t swap_tmp;
swap_tmp = *x;
*x = *y;
*y = swap_tmp;
#endif
if((*x) > XPT2046_X_MIN)(*x) -= XPT2046_X_MIN;
else(*x) = 0;
if((*y) > XPT2046_Y_MIN)(*y) -= XPT2046_Y_MIN;
else(*y) = 0;
(*x) = (uint32_t)((uint32_t)(*x) * XPT2046_HOR_RES) /
(XPT2046_X_MAX - XPT2046_X_MIN);
(*y) = (uint32_t)((uint32_t)(*y) * XPT2046_VER_RES) /
(XPT2046_Y_MAX - XPT2046_Y_MIN);
#if XPT2046_X_INV != 0
(*x) = XPT2046_HOR_RES - (*x);
#endif
#if XPT2046_Y_INV != 0
(*y) = XPT2046_VER_RES - (*y);
#endif
}
//平均滤波函数,对读取到的坐标数据进行平均滤波,减少噪声和提高稳定性。
//说白了就是消抖
static void xpt2046_avg(int16_t * x, int16_t * y)
{
/*Shift out the oldest data*/
uint8_t i;
for(i = XPT2046_AVG - 1; i > 0 ; i--) {
avg_buf_x[i] = avg_buf_x[i - 1];
avg_buf_y[i] = avg_buf_y[i - 1];
}
/*Insert the new point*/
avg_buf_x[0] = *x;
avg_buf_y[0] = *y;
if(avg_last < XPT2046_AVG) avg_last++;
/*Sum the x and y coordinates*/
int32_t x_sum = 0;
int32_t y_sum = 0;
for(i = 0; i < avg_last ; i++) {
x_sum += avg_buf_x[i];
y_sum += avg_buf_y[i];
}
/*Normalize the sums*/
(*x) = (int32_t)x_sum / avg_last;
(*y) = (int32_t)y_sum / avg_last;
}
#endif
通读上面的代码可知,需要自己实现与内核驱动交互的接口。具体的接口如下:
lv_drv_conf.h
/*----------
* Common
*----------*/
#define LV_DRV_INDEV_INCLUDE /*Dummy include by default*/
#define LV_DRV_INDEV_RST(val) /*pin_x_set(val)*/ /*Set the reset pin to 'val'*/
#define LV_DRV_INDEV_IRQ_READ 0 /*pn_x_read()*/ /*Read the IRQ pin*/
/*---------
* SPI
*---------*/
#define LV_DRV_INDEV_SPI_CS(val) /*spi_cs_set(val)*/ /*Set the SPI's Chip select to 'val'*/
#define LV_DRV_INDEV_SPI_XCHG_BYTE(data) 0 /*spi_xchg(val)*/ /*Write 'val' to SPI and give the read value*/
这部分代码我没实现;如果这些函数实在不知道咋写的,可以借鉴官方framebuffer框架与内核接口的写法(注意:如果借鉴了接口的写法,xpt2046的内核驱动操作方法结构体也要做相应的改动)。
3.2 lvgl触摸屏中断逻辑分析
如果看了我中断的博客可以知道驱动层获取中断的方法,而驱动往应用层传输中断状态的方法无外乎就是操作方法结构体的那几个函数。所以读完驱动模块的代码后,我有个问题是我传过来的中断信号lvgl什么时候读取呢?
如下是上面xpt2046关于中断的宏接口,是中断代码的分析起点。它的作用是获取中断的状态(前文已说需要自己实现驱动到应用层的逻辑)。
lv_drv_conf.h:61:#define LV_DRV_INDEV_IRQ_READ 0 /*pn_x_read()*/ /*Read the IRQ pin*/
如下是xpt2046_read函数中对于中断状态判断的代码,那么谁调用它?什么时候调用呢?
xpt2046.c
uint8_t irq = LV_DRV_INDEV_IRQ_READ; //中断的宏,这个获取中断的宏函数需要自己实现
if(irq == 0) {
继续查找代码发现没有任何函数调用它。查阅各种资料发现是在main.c中被调用,因为我还没有实现触摸功能,所以查找不到调用;再读取官方的lvgl/docs/porting/indev.md文档,学习触摸事件的使用方式,发现和上文里我那些framebuffer和按键事件写法一样,需要初始化、赋值、注册等操作。(越研究lvlg越发现和qt神似啊)
如下是官方文档给出的基础写法:
indev.md
static lv_indev_drv_t indev_drv;
lv_indev_drv_init(&indev_drv); /*Basic initialization*/
indev_drv.type =... /*See below.*/
indev_drv.read_cb =... /*See below.*/
/*Register the driver in LVGL and save the created input device object*/
lv_indev_t * my_indev = lv_indev_drv_register(&indev_drv);
了解触摸事件的写法后,发现xpt2046_read是被赋值给了indev_drv.read_cb,然后注册触摸事件。那么会不会是注册后,它会像framebuffer和按键事件一样后台运行呢?继续分析lv_indev_drv_register函数:
lvgl/src/hal/lv_hal_indev.c
lv_indev_t * lv_indev_drv_register(lv_indev_drv_t * driver)
{
if(driver->disp == NULL) driver->disp = lv_disp_get_default();
if(driver->disp == NULL) {
LV_LOG_WARN("lv_indev_drv_register: no display registered hence can't attach the indev to "
"a display");
return NULL;
}
lv_indev_t * indev = _lv_ll_ins_head(&LV_GC_ROOT(_lv_indev_ll));
if(!indev) {
LV_ASSERT_MALLOC(indev);
return NULL;
}
lv_memset_00(indev, sizeof(lv_indev_t));
indev->driver = driver;
indev->proc.reset_query = 1;
//如下是定时器创建函数
indev->driver->read_timer = lv_timer_create(lv_indev_read_timer_cb, LV_INDEV_DEF_READ_PERIOD, indev);
return indev;
}
从上面可以看出官方在注册函数内已经使用定时器函数将触摸事件与定时器绑定(有兴趣的可以继续深扒实现代码)。
而官方在main.c中的while循环中已经添加的如下的定时器相关的代码,作用就是遍历定时器链表,执行每个定时器的回调函数。
while(1) {
lv_timer_handler();
usleep(5000);
}
至此也就基本明白了屏幕刷新、触摸判断的后台实时调用逻辑了。就是在while循环里定时调用绑定的相关函数,然后读取中断状态,一旦中断值变化,就会进一步读取/dev/input/event0文件中的坐标值。
3.3 官方触摸代码示例
如下是官方例程里注释的触摸相关代码,主要功能就是注册一个触摸事件。
evdev_init();//初始化触摸驱动
static lv_indev_drv_t indev_drv_1;//定义触摸事件变量
lv_indev_drv_init(&indev_drv_1); /*Basic initialization*/ //初始化变量
indev_drv_1.type = LV_INDEV_TYPE_POINTER; //将变量定义为指针类型(触摸屏/鼠标)
/*This function will be called periodically (by the library) to get the mouse position and state*/
indev_drv_1.read_cb = evdev_read;//设置读取回调函数,这也是需要自己实现的读取逻辑
lv_indev_t *mouse_indev = lv_indev_drv_register(&indev_drv_1); //注册触摸事件
从上面代码可以看出,需要自己实现的就是evdev_init()函数和evdev_read函数。
首先分析evdev_init()函数。其在lv_drivers/indev/evdev.c下,与xpt2046.c文件同一层级;再结合它的名字就可以知道它是一个通用的触摸驱动,因此可以顺便看看它的初始化是怎么写的,可以拿来编写自己的xpt2046的初始化接口。
/**
* Initialize the evdev interface
*/
void evdev_init(void)
{
#if USE_BSD_EVDEV
evdev_fd = open(EVDEV_NAME, O_RDWR | O_NOCTTY);
//系统调用打开输入设备节点/dev/input/eventX
//O_RDWR:以读写模式打开设备(某些设备需要双向通信)。
//O_NOCTTY:禁止将设备作为控制终端。
//O_NDELAY(非BSD系统):非阻塞模式,避免读取时程序卡住。
#else
evdev_fd = open(EVDEV_NAME, O_RDWR | O_NOCTTY | O_NDELAY);
#endif
if(evdev_fd == -1) {
perror("unable open evdev interface:");
return;
}
#if USE_BSD_EVDEV
fcntl(evdev_fd, F_SETFL, O_NONBLOCK);
//设置文件描述符为非阻塞模式。
//O_NONBLOCK:确保read()操作立即返回,避免阻塞主线程。
#else
fcntl(evdev_fd, F_SETFL, O_ASYNC | O_NONBLOCK);
//O_ASYNC(非BSD系统):启用异步I/O,允许信号驱动I/O(如SIGIO)。
//适配高实时性场景(如嵌入式GUI),避免输入事件处理延迟。
#endif
//初始化变量
evdev_root_x = 0;
evdev_root_y = 0;
evdev_key_val = 0;
evdev_button = LV_INDEV_STATE_REL;//初始化输入设备状态为释放(LV_INDEV_STATE_REL),对应LVGL的释放状态宏
#if USE_XKB
xkb_init();
#endif
}
触摸节点的名字是默认的,在lv_drv_conf.h下已经定义过了。如果有多个节点,可以自行改正节点名字。
#if USE_EVDEV || USE_BSD_EVDEV
# define EVDEV_NAME "/dev/input/event0" /*You can use the "evtest" Linux tool to get the list of devices and test them*/
# define EVDEV_SWAP_AXES 0 /*Swap the x and y axes of the touchscreen*/
接着分析evdev_read函数,如下截取了主要的代码,其逻辑是循环读取节点文件的内容赋值给xy坐标。
/**
* Get the current position and state of the evdev
* @param data store the evdev data here
*/
void evdev_read(lv_indev_drv_t * drv, lv_indev_data_t * data)
{
struct input_event in;
while(read(evdev_fd, &in, sizeof(struct input_event)) > 0) {
if(in.type == EV_REL) {
if(in.code == REL_X)
#if EVDEV_SWAP_AXES
evdev_root_y += in.value;
#else
evdev_root_x += in.value;
#endif
else if(in.code == REL_Y)
#if EVDEV_SWAP_AXES
evdev_root_x += in.value;
#else
evdev_root_y += in.value;
#endif
到这,整个触摸功能的逻辑链就捋清了,剩下的就是简单的叠代码以及调试工作了。
4 总结
这几天的整理让我把之前的项目复习一遍,又找到了不少盲点,果然温故知新的古话没骗人呐!
还有,因为时间安排问题,触摸驱动没有时间做彻底适配。后面有空以后会完成适配分享给大家!有啥问题可以留言发邮箱大家一起讨论。