4、显示驱动适配(下):编写显示初始化函数、实现flush_callback回调、配置像素格式与颜色深度、实测验证与性能调优
好,咱们接着上一章往下聊。上回我们把显示驱动的骨架搭好了,也把硬件接口打通了。这一章,我们要让屏幕真正亮起来,显示出东西来。
说白了,就是要把LVGL的绘图指令,变成屏幕上的像素点。这件事做成了,你的嵌入式设备才算有了“脸面”。
4.1 编写显示初始化函数
初始化函数,是屏幕的“开机仪式”。每个屏幕都有自己的脾气,你得按它的规矩来。
我个人习惯把初始化拆成三步:
- 硬件复位:拉低RST引脚,等个10ms,再拉高。这一步是让屏幕控制器回到已知状态。
- 配置参数:通过SPI或I2C发送初始化指令序列。比如设置扫描方向、像素格式、伽马曲线等。
- 唤醒显示:发送SLPOUT(睡眠退出)和DISPON(显示开启)指令。
这里我贴一段典型的初始化代码,以ILI9341为例:
static void lcd_init(void)
{
// 1. 硬件复位
lcd_reset_gpio_write(0);
lcd_delay_ms(10);
lcd_reset_gpio_write(1);
lcd_delay_ms(120);
// 2. 发送初始化序列
lcd_send_cmd(0x01); // 软件复位
lcd_delay_ms(120);
lcd_send_cmd(0x11); // 退出睡眠
lcd_delay_ms(120);
lcd_send_cmd(0x36); // 内存访问控制
lcd_send_data(0x48); // MX, MV位设置
lcd_send_cmd(0x3A); // 像素格式
lcd_send_data(0x55); // 16位色(RGB565)
lcd_send_cmd(0x29); // 开启显示
lcd_delay_ms(10);
}
嗯,这里要注意:不同厂商的屏幕,初始化序列差别很大。我遇到过一块便宜的国产屏,官方手册写的序列死活点不亮,后来发现是复位时序太短。把延时从5ms改成20ms,立马就好了。
4.2 实现flush_callback回调
这是LVGL和硬件之间的“桥梁”。LVGL画好一帧画面后,会调用这个回调函数,把数据交给你,你再把它刷到屏幕上。
回调函数的原型长这样:
static void my_flush_cb(lv_disp_drv_t * disp_drv,
const lv_area_t * area,
lv_color_t * color_p)
{
// area->x1, area->y1: 左上角坐标
// area->x2, area->y2: 右下角坐标
// color_p: 像素数据缓冲区
// 1. 设置屏幕的写入窗口
lcd_set_window(area->x1, area->y1, area->x2, area->y2);
// 2. 通过SPI/DMA发送像素数据
lcd_send_pixels(color_p,
(area->x2 - area->x1 + 1) *
(area->y2 - area->y1 + 1));
// 3. 通知LVGL刷新完成
lv_disp_flush_ready(disp_drv);
}
你想想看,这个回调会被频繁调用。如果每次都用CPU逐像素发送,性能会很难看。我建议用DMA传输,CPU可以腾出手来做别的事。
4.3 配置像素格式与颜色深度
LVGL支持多种颜色深度:1位、8位、16位、32位。最常用的是16位RGB565,因为它在颜色表现和内存占用之间取得了平衡。
配置方式很简单,在lv_conf.h中设置:
/* 颜色深度设置 */
#define LV_COLOR_DEPTH 16
/* 颜色格式 */
#define LV_COLOR_16_SWAP 0 // 字节序是否交换
这里有个坑:有些屏幕的RGB565字节序是反的。比如你发送0x1F, 0xE0(红色),屏幕却显示蓝色。这就是字节序问题。
我遇到过这种情况,当时排查了很久。后来用了一个简单的方法验证:画一个纯红色的矩形,然后用逻辑分析仪抓SPI数据,对比手册里的像素格式说明,才发现是字节序反了。
解决办法有两个:
- 设置
LV_COLOR_16_SWAP = 1,让LVGL帮你交换字节序 - 或者在发送数据时手动交换高低字节
| 颜色深度 | 每像素字节数 | 适用场景 | 内存占用(320x240) |
|---|---|---|---|
| 1位 | 1/8 | 单色屏、低功耗 | 9.6 KB |
| 8位 | 1 | 低端彩屏 | 76.8 KB |
| 16位 | 2 | 主流彩屏 | 153.6 KB |
| 32位 | 4 | 高画质需求 | 307.2 KB |
4.4 实测验证与性能调优
代码写完了,怎么知道它跑得好不好?我一般做三个测试:
4.4.1 基础功能测试
先跑LVGL自带的benchmark demo。它能显示各种图形、文字、动画。如果画面有撕裂、花屏、颜色不对,说明驱动还有问题。
我习惯先画一个纯色全屏,比如红色0xF800。如果整个屏幕都是均匀的红色,说明数据传输基本正常。然后再画渐变条,检查颜色过渡是否平滑。
4.4.2 帧率测试
帧率是衡量显示性能的关键指标。LVGL提供了lv_disp_get_inactive_time()函数,可以估算帧率。
更直接的方法:在flush_callback里翻转一个GPIO,用示波器看波形。高电平持续时间就是刷一帧的时间。
static void my_flush_cb(...)
{
gpio_set_level(DEBUG_PIN, 1); // 开始刷屏
// ... 传输数据 ...
gpio_set_level(DEBUG_PIN, 0); // 刷屏结束
lv_disp_flush_ready(disp_drv);
}
320x240的屏幕,16位色,SPI时钟40MHz,理论帧率应该在30fps以上。如果低于15fps,就要优化了。
4.4.3 性能调优三板斧
如果帧率不理想,我一般按这个顺序排查:
- 开启DMA:这是最有效的优化。CPU不用等SPI传输完成,可以回去继续渲染。
- 调整缓冲区大小:LVGL默认用全屏缓冲区,内存不够的话可以改成行缓冲区。但行缓冲区太小会降低效率,我一般用屏幕高度的1/4。
- 减少无效刷新:LVGL默认只刷新变化区域。如果你的应用场景变化不大,可以开启
LV_DISP_DEF_REFR_PERIOD,降低刷新频率。
4.5 小结
这一章我们走完了显示驱动适配的全流程:从初始化函数,到flush回调,再到颜色配置和性能调优。说白了,就是让LVGL和你的屏幕“对上话”。
下一章,我们会聊输入设备的适配。触摸屏、按键、编码器,这些东西怎么和LVGL配合,让用户能和你的设备交互。到时候见。