2、LVGL输入设备驱动架构:输入设备驱动模型、注册与回调机制、事件处理流程、驱动与UI的交互方式
好,咱们接着聊。上一章我们把LVGL的移植跑通了,屏幕能点亮,但光有显示还不够——你得能跟它交互才行。这一章,我就带你深入LVGL的输入设备驱动架构。说白了,就是搞清楚触摸屏、按键、编码器这些东西,是怎么跟LVGL“对话”的。
2.1 输入设备驱动模型:LVGL眼中的“输入设备”长什么样?
LVGL把输入设备抽象成了一个统一的结构体——lv_indev_drv_t。你想想看,触摸屏、鼠标、键盘、旋转编码器,它们本质上都是“输入”,只是数据格式不同。LVGL用这个结构体,把千奇百怪的硬件统一了起来。
这个结构体里,最重要的几个字段我列一下:
| 字段 | 类型 | 说明 |
|---|---|---|
type |
lv_indev_type_t |
设备类型:触摸屏、按键、编码器、鼠标等 |
read_cb |
函数指针 | 读取数据的回调函数,核心! |
user_data |
void * |
用户自定义数据,比如I2C句柄 |
disp |
lv_disp_t * |
关联的显示器,触摸屏必须绑定 |
我个人习惯,在初始化驱动之前,先把这些字段想清楚。比如你用的是FT6336触摸芯片,那type就是LV_INDEV_TYPE_POINTER,read_cb就是你自己写的读取函数。
2.2 注册与回调机制:把你的驱动“挂”到LVGL上
驱动模型搞清楚了,下一步就是注册。注册的过程,其实就是把lv_indev_drv_t初始化好,然后调用lv_indev_drv_register()。我贴一段典型的代码,你一看就明白:
static lv_indev_t * indev_touch;
void touch_driver_init(void)
{
lv_indev_drv_t indev_drv;
lv_indev_drv_init(&indev_drv); // 初始化驱动结构体
indev_drv.type = LV_INDEV_TYPE_POINTER; // 触摸屏属于指针类型
indev_drv.read_cb = touch_read_cb; // 注册读取回调
indev_drv.user_data = NULL; // 暂时不用
indev_touch = lv_indev_drv_register(&indev_drv); // 注册!
}
这里有个关键点——read_cb回调函数。LVGL会周期性地调用它,频率由LVGL的定时器决定。你的任务就是在回调里,把硬件数据填到lv_indev_data_t结构体中。
核心回调函数模板:
static void touch_read_cb(lv_indev_drv_t * drv, lv_indev_data_t * data)
{
// 1. 读取触摸芯片寄存器
uint8_t touch_points = 0;
uint16_t x = 0, y = 0;
// 假设这是你的I2C读取函数
read_touch_point(&touch_points, &x, &y);
// 2. 判断是否有触摸
if(touch_points > 0) {
data->state = LV_INDEV_STATE_PR;
data->point.x = x;
data->point.y = y;
} else {
data->state = LV_INDEV_STATE_REL;
}
// 3. 不需要返回,LVGL会自动处理
}
嗯,这里要注意一点:data->state只有两个值——LV_INDEV_STATE_PR(按下)和LV_INDEV_STATE_REL(释放)。别搞复杂了。
小技巧: 我在项目中遇到过触摸坐标反了的情况。后来发现是触摸芯片的X/Y轴和屏幕的坐标系不一致。解决办法很简单,在回调里做个映射:data->point.x = screen_width - raw_x; 就行了。
2.3 事件处理流程:从手指按下到UI响应,中间发生了什么?
你按下屏幕,UI上的按钮变色了。这中间经历了什么?我把它拆成三步:
- 硬件读取阶段: LVGL的定时器触发,调用你的
read_cb,拿到原始坐标和状态。 - 软件处理阶段: LVGL内部判断这个坐标落在了哪个对象上,然后生成事件。
- UI响应阶段: 你注册的事件回调被调用,执行对应的逻辑。
说白了,LVGL帮你做了“坐标命中检测”这件事。你只需要告诉它“手指按在了(100, 200)”,它就知道这个点是不是在按钮A上。
避坑指南: 我曾经在回调里做了延时操作,比如用lv_task_delay()。结果整个UI都卡住了。为什么?因为read_cb是在LVGL的主循环里被调用的,你在这里阻塞,整个事件处理流程就停了。记住:回调里不要做耗时操作!
2.4 驱动与UI的交互方式:你的代码怎么“通知”UI?
驱动和UI的交互,其实就两种方式:
- 轮询方式: LVGL定时调用
read_cb,这是默认方式,简单可靠。 - 中断方式: 触摸芯片产生中断,你通过
lv_indev_read_cb()手动触发读取。适合低功耗场景。
我个人更推荐轮询方式,除非你对功耗有严格要求。因为中断方式容易引入竞态问题,调试起来比较头疼。
驱动和UI的交互,最终体现在事件上。比如你写了一个按钮的回调:
static void btn_event_cb(lv_event_t * e)
{
lv_event_code_t code = lv_event_get_code(e);
if(code == LV_EVENT_CLICKED) {
LV_LOG_USER("按钮被点击了!");
// 执行你的业务逻辑
}
}
这个回调什么时候被调用?就是LVGL在事件处理流程的第三步触发的。驱动层不需要关心这个,它只负责把原始数据喂给LVGL。
总结一下驱动与UI的交互链路:
硬件中断/轮询 → read_cb → lv_indev_data_t → LVGL内部处理 → 事件回调 → UI响应
你想想看,这个链路其实很清晰。驱动层只做一件事:把硬件数据翻译成LVGL能理解的格式。剩下的命中检测、事件分发,LVGL全包了。这就是框架的好处——你不用重复造轮子。
好,这一章就到这里。下一章我会带你实战一个具体的触摸驱动——FT6336,从I2C初始化到坐标校准,一步步写出来。到时候你就知道,理论跟实践结合起来,其实没那么难。