最近在 I.MX6ULL 裸机开发中学习 LCD 屏幕驱动,搞懂了 Framebuffer 显存原理、RGB 色彩格式,并且完成了实操代码:配置 LCD 引脚、时钟,初始化 LCDIF 控制器,最后接入 LM75 温度传感器,在屏幕上实时显示采集到的温度数据。
一、基础概念:像素、分辨率与 RGB888
屏幕画面本质是由大量像素点 构成的矩阵。我们这块 LCD 分辨率为800*480,代表横向 800 个像素,纵向 480 个像素。 分辨率是屏幕横向像素数量 × 纵向像素数量。分辨率相同,屏幕尺寸越小,PPI 越高,画面越细腻;尺寸相同,分辨率越高,图像效果越好。
每个像素点依靠 RGB 三原色混合出不同颜色,常见色彩格式有 RGB888、RGB565、ARGB8888:
- RGB888:R、G、B 三个通道各占 8bit(0~255),一个像素占用 3 字节,可以表达 1670 万种颜色,色彩丰富;
- RGB565:R 占 5bit、G 占 6bit、B 占 5bit,单个像素 2 字节,色彩较少,但占用内存更小;
- ARGB8888:在 RGB 基础上增加 Alpha 透明通道,单个像素 4 字节,也就是我们代码里使用的格式。
我们代码中使用
unsigned int保存颜色,就是 ARGB 格式,A 通道 0,后面 24bit 存放 RGB。
二、Framebuffer(显存)核心原理
Framebuffer,也就是显存,是内存中专门存放图像像素数据的一块空间。 显存起始地址固定,0x89000000,像素坐标(x,y)对应的显存地址计算公式:
*(p + 800 * y + x) = color;
p:显存首地址y:纵向行号,800是每行像素个数x:横向列号
简单理解:CPU 不用直接和 LCD 屏幕打交道,只需要往显存内存地址写入像素颜色值 。硬件 LCDIF 控制器会自动读取显存里的数据,按照时序源源不断把像素送到屏幕上显示。 这就是裸机 LCD 绘图的核心:写内存 = 画屏幕。 我们写的lcd_drawpoint画点函数,底层就是这个公式。
三、LCD 时序信号
LCD 屏幕正常工作,依靠 4 根关键时序信号线:
- PCLK(像素时钟):Pixel Clock。同步每一个像素点发送的时间间隔,每来一个时钟,传输 1 个像素;
- HSYNC(行同步信号):Horizontal Sync。同步每一行图像,一行像素扫描完成,产生行同步脉冲,切换到下一行;
- VSYNC(帧同步信号):Vertical Sync。同步整幅图像,一整屏图像扫描完毕,触发帧同步,刷新下一帧;
- DE(Data Enable):数据使能信号。DE 为高的时候,当前传输的数据是有效的像素图像数据。
在 LCD 初始化代码中,我们配置的 hspw/hfp/hbp、vspw/vfp/vbp 这些参数,就是行场同步的前后肩、同步脉冲宽度,必须和屏幕手册匹配,否则屏幕会花屏、无法点亮。
四、代码实现讲解
1. LCD 硬件初始化
void lcd_pin_init(void)
使用IOMUXC_SetPinMux把 24 根 LCD 数据线、PCLK、HSYNC、VSYNC、DE 等引脚复用为 LCDIF 外设功能;IOMUXC_SetPinConfig配置引脚电气属性,同时配置背光 GPIO 引脚。
void lcd_clock_init(void)
配置 LCD 视频 PLL 时钟,为 LCDIF 控制器提供像素时钟 PCLK,是屏幕正常输出时序的基础。
void lcd_init(void)
LCD 总初始化函数:引脚初始化→时钟初始化→LCDIF 复位→填写屏幕时序参数,指定 framebuffer 显存地址,最后开启 LCDIF,硬件开始输出图像。
2. 绘图基础函数
// 画像素点
void lcd_drawpoint(int x, int y, unsigned int col)
{
unsigned int *p = (unsigned int *)lcd_dev.frame_buffer;
if (x > lcd_dev.width || y > lcd_dev.height)
{
return;
}
*(p + lcd_dev.width * y + x) = col;
}
这个函数就是显存公式的直接实现,给对应坐标的显存地址写入颜色,屏幕对应坐标就会点亮。lcd_clear_screen清屏函数,就是循环遍历全部显存,统一填充同一个颜色。
3. 项目实战:LCD 显示 LM75 实时温度
主循环代码:
while (1)
{
float f = get_lm75_temp();
int k = f * 10;
int m = k / 10;
int n = k % 10;
sprintf(buffer, "%d.%d", m, n);
lcd_show_string(400, 240, 16 * strlen(buffer), 32, 32, buffer);
}
逻辑:I2C 读取 LM75 温度传感器浮点温度,放大十倍拆分整数、小数部分,用 sprintf 拼接字符串,调用lcd_show_string在屏幕坐标(400,240)位置打印温度。
当前代码存在小问题:每次刷新文字没有擦除旧文字,温度变化时会出现文字重叠拖影。解决方案:刷新前,在文字区域填充背景色矩形,覆盖上一次的文字。也可以增加
delay_ms降低刷新频率,减少 CPU 占用。
五、学习小结
这次学习打通了 I.MX6ULL 裸机 LCD 驱动的完整流程:从 RGB 像素基础、显存 Framebuffer 原理,到 LCD 行场时序,再到寄存器配置、代码绘图,最后结合 I2C 温度传感器做了一个完整的小项目。 Framebuffer 模型非常关键,后续 Linux 系统下的 LCD 显示、Qt 界面,底层也沿用这套显存思想,裸机的学习相当于打好了底层基础。