3、驱动层优化:触摸驱动初始化优化、数据读取优化、去抖与滤波算法
驱动层,说白了就是硬件和 LVGL 之间的翻译官。这个翻译官要是效率低,上层再怎么优化也白搭。我见过太多项目,触摸响应慢,最后查来查去,问题就出在驱动初始化时多睡了 200 毫秒,或者读取数据时做了太多无用功。
这一节,咱们就专门收拾驱动层的三个关键环节:初始化、数据读取、还有去抖滤波。把这些搞定,你的触摸响应至少能快 30%。
3.1 触摸驱动初始化优化
很多工程师写触摸初始化,习惯上来就调用一个 touch_init(),里面塞一堆延时。嗯,这里要注意,延时不是不能用,但得讲究方法。
核心原则:初始化流程要「快进快出」,把耗时的硬件自校准放到后台,或者干脆提前完成。
我习惯把初始化拆成两个阶段:
- 快速阶段:配置 GPIO、I2C/SPI 时钟、复位引脚。这部分必须快,毫秒级完成。
- 慢速阶段:发送初始化命令序列、等待芯片内部自校准。这部分可以放到一个状态机里,或者用
lv_timer分步执行。
举个例子,以前我用 FT5x06 系列触摸芯片,官方驱动里初始化后直接来了个 300ms 的 delay_ms(300)。为什么?因为芯片上电后需要时间稳定。但 300ms 啊,用户都能感觉到界面卡顿了。
我的做法是:
// 传统写法:阻塞 300ms
void touch_init(void) {
// ... 配置 I2C ...
ft5x06_write_reg(0x00, 0x01); // 软复位
delay_ms(300); // 等芯片稳定
// ... 后续配置 ...
}
// 优化写法:分步执行
static uint8_t init_step = 0;
void touch_init_fast(void) {
// 第一步:只配硬件接口
gpio_init(TOUCH_IRQ_PIN);
i2c_init(TOUCH_I2C, 400000);
ft5x06_write_reg(0x00, 0x01); // 触发复位,不等
init_step = 1;
}
void touch_init_poll(void) {
// 第二步:由 lv_timer 周期性调用
if (init_step == 1) {
if (ft5x06_read_reg(0x02) == 0x55) { // 检查芯片是否就绪
// 芯片好了,继续配置
ft5x06_write_reg(0x01, 0x02);
init_step = 2;
}
// 没就绪?下次再来,不阻塞
}
}
这样,界面在初始化期间照样能渲染,用户根本感觉不到触摸驱动在「磨蹭」。
小技巧:如果芯片必须等待,可以用 lv_tick_get() 做超时判断,代替硬延时。比如:while((lv_tick_get() - start) < 300) { /* 轮询就绪标志 */ },这样至少能让 LVGL 的任务调度器有空隙执行。
3.2 数据读取优化
触摸数据读取,是驱动层最频繁的操作。每次触摸事件,LVGL 都会调用你的 touch_read() 函数。这个函数要是写得拖泥带水,整个 UI 的帧率都会被拉低。
我曾经接手过一个项目,触摸读取用了 I2C 的阻塞模式,每次读取 10 个字节要花 5ms。你想想看,如果用户手指在屏幕上滑动,每秒产生 100 个点,光读取就占了 500ms 的 CPU 时间,这还怎么玩?
优化方向有三个:
| 优化方向 | 具体做法 | 效果 |
|---|---|---|
| 减少读取次数 | 只在触摸中断触发时才读取,不要轮询 | CPU 占用降低 80% |
| 减少读取数据量 | 只读必要的坐标和触摸状态,不读厂商信息 | 单次读取时间减半 |
| 使用 DMA 或中断 | I2C/SPI 读取用 DMA 后台传输,CPU 干别的 | 读取时间几乎为 0(对 CPU 而言) |
具体到代码,我建议这样写:
// 优化前:每次轮询都读一堆数据
bool touch_read(lv_indev_drv_t *drv, lv_indev_data_t *data) {
uint8_t buf[16];
i2c_read(TOUCH_ADDR, 0x00, buf, 16); // 读 16 字节
data->point.x = (buf[3] << 8) | buf[4];
data->point.y = (buf[5] << 8) | buf[6];
data->state = (buf[2] & 0x80) ? LV_INDEV_STATE_PR : LV_INDEV_STATE_REL;
return false;
}
// 优化后:中断触发 + 按需读取
static volatile bool touch_irq_pending = false;
static lv_point_t touch_point;
static lv_indev_state_t touch_state;
void touch_irq_handler(void) {
touch_irq_pending = true; // 标记有触摸事件
}
bool touch_read(lv_indev_drv_t *drv, lv_indev_data_t *data) {
if (!touch_irq_pending) {
// 没有新事件,直接返回上次数据
data->point = touch_point;
data->state = touch_state;
return false;
}
touch_irq_pending = false;
// 只读 6 个关键字节
uint8_t buf[6];
i2c_read(TOUCH_ADDR, 0x02, buf, 6); // 从状态寄存器开始读
touch_point.x = (buf[1] << 8) | buf[2];
touch_point.y = (buf[3] << 8) | buf[4];
touch_state = (buf[0] & 0x80) ? LV_INDEV_STATE_PR : LV_INDEV_STATE_REL;
data->point = touch_point;
data->state = touch_state;
return false;
}
注意:中断服务函数里不要做 I2C 读取!中断里只设标志位,真正的读取放在 touch_read() 里。否则中断嵌套会导致系统崩溃,我吃过这个亏。
3.3 去抖与滤波算法
触摸芯片返回的原始数据,往往带着噪声。尤其是电容触摸屏,手指轻微抖动、环境电磁干扰,都会让坐标跳来跳去。如果不做处理,LVGL 的按钮会像「抽风」一样乱闪。
去抖和滤波,是两个不同的事:
- 去抖:消除触摸按下/释放瞬间的毛刺信号。
- 滤波:平滑连续触摸时的坐标轨迹。
3.3.1 去抖算法
最简单的去抖,就是「延时确认」。检测到触摸状态变化后,不立即上报,而是等几个毫秒再确认一次。如果状态没变,才认为是有效事件。
我常用的去抖代码:
#define DEBOUNCE_MS 10
static uint32_t last_change_tick = 0;
static lv_indev_state_t debounced_state = LV_INDEV_STATE_REL;
lv_indev_state_t touch_debounce(lv_indev_state_t raw_state) {
uint32_t now = lv_tick_get();
if (raw_state != debounced_state) {
// 状态变化了,记录时间
if (now - last_change_tick > DEBOUNCE_MS) {
// 超过 10ms 还是这个状态,确认变化
debounced_state = raw_state;
}
last_change_tick = now;
}
return debounced_state;
}
这个算法,说白了就是「不信第一下,只信持续了 10ms 的状态」。对付机械抖动和电气毛刺,效果很好。
3.3.2 滤波算法
坐标滤波,我推荐两种:
| 算法 | 原理 | 适用场景 | 计算量 |
|---|---|---|---|
| 滑动平均 | 取最近 N 个点的平均值 | 手指慢速滑动 | 低 |
| 一阶低通滤波 | 新值 = 旧值 × α + 原始值 × (1-α) | 快速滑动、绘图应用 | 极低 |
我个人习惯用一阶低通滤波,因为它只需要一个乘法和一个加法,在 MCU 上跑起来飞快。而且 α 值可以调节灵敏度:α 越大,轨迹越平滑,但延迟也越大。
#define FILTER_ALPHA 0.7f // 平滑系数,范围 0~1
static lv_point_t filtered_point = {0, 0};
void touch_filter(lv_point_t *raw, lv_point_t *out) {
// 一阶低通滤波
filtered_point.x = (int16_t)(FILTER_ALPHA * filtered_point.x +
(1 - FILTER_ALPHA) * raw->x);
filtered_point.y = (int16_t)(FILTER_ALPHA * filtered_point.y +
(1 - FILTER_ALPHA) * raw->y);
*out = filtered_point;
}
经验值:对于大多数电容触摸屏,α 取 0.6~0.8 比较合适。α 太小(比如 0.3),滤波效果不明显;α 太大(比如 0.95),手指划过去半秒坐标才跟上,用户会骂娘的。
3.4 整体流程:一张图看懂
说了这么多,咱们把驱动层优化的整体流程串起来。下面这张图,是我做项目时必画的架构图,每次评审都拿它说事。
你看,整个流程从硬件层开始,经过初始化、数据读取、去抖滤波,最后输出给 LVGL 的坐标数据。每一层都有明确的优化目标,互不干扰。
总结一下:驱动层优化的核心就三句话——初始化别阻塞、读取别浪费、数据别抖动。把这三点做到位,你的触摸体验就已经超越了 80% 的嵌入式产品。