一、基础概念
LCD(液晶显示器)是纯输出设备,只负责显示,不含触摸功能。触摸由独立的触摸控制器实现。本项目使用 ATK4384,分辨率 800×480,RGB 接口。
分辨率
| 规格 | 像素点数量 |
|---|---|
| 1080P | 1920×1080 |
| 2K | 2560×1440 |
| 4K | 3840×2160 |
| 本项目 | 800×480 |
尺寸不变 → 分辨率越高越清晰;分辨率不变 → 尺寸越小越清晰(手机比显示器细腻的原因)
像素格式(本项目使用 ARGB8888)
| 格式 | 位数 | 字节数 | 说明 |
|---|---|---|---|
| RGB565 | 16bit | 2字节 | R5G6B5 |
| RGB888 | 24bit | 3字节 | 无透明通道 |
| ARGB8888 | 32bit | 4字节 | A透明+RGB,本项目使用 |
ARGB8888 内存布局:
bit31~24: Alpha(透明度)
bit23~16: RED
bit15~8: GREEN
bit7~0: BLUE
常用颜色值:
c
红色 0x00FF0000
绿色 0x0000FF00
蓝色 0x000000FF
黄色 0x00FFFF00
白色 0x00FFFFFF
黑色 0x00000000
紫色 0x00FF00FF // lcd_fill() 中使用的颜色
显存大小
显存大小 = 800 × 480 × 4字节 = 1,536,000 字节 ≈ 1.5MB
从 DDR3 中划出,首地址写入 LCDIF_CUR_BUF,修改显存内容即可改变屏幕显示。
二、RGB LCD 硬件接口
I.MX6U-Mini 支持 RGB 接口 LCD,信号线如下:
| 信号线 | 数量 | 说明 |
|---|---|---|
| R[7:0] | 8根 | 红色数据线 |
| G[7:0] | 8根 | 绿色数据线 |
| B[7:0] | 8根 | 蓝色数据线 |
| DE | 1根 | 数据使能线(Data Enable) |
| VSYNC | 1根 | 帧同步信号(垂直同步) |
| HSYNC | 1根 | 行同步信号(水平同步) |
| PCLK | 1根 | 像素时钟 |
驱动模式:
- DE 模式 :需要 DE 信号,可以不接 HSYNC 也能正常工作(本项目使用)
- HV 模式:不需要 DE 信号,依赖 HSYNC/VSYNC
三、LCD 时序参数(最核心知识点)
显示一帧图像就像用一支"笔"从左到右、从上到下扫描每个像素点,扫描完最后一个点就是一帧。
3.1 行时序参数(水平方向)
|<--HSPW-->|<---HBP--->|<------HOZVAL(有效像素)----->|<--HFP-->|
| 参数 | 全称 | 说明 | 本项目值 |
|---|---|---|---|
| HSPW | Horizontal Sync Pulse Width | HSYNC 脉冲宽度,单位:CLK | 48 |
| HBP | Horizontal Back Porch | 行同步后肩(等待时间) | 88 |
| HOZVAL | Horizontal Valid | 有效像素宽度(屏幕宽度) | 800 |
| HFP | Horizontal Front Porch | 行同步前肩 | 40 |
一行时间 = HSPW + HBP + HOZVAL + HFP = 48 + 88 + 800 + 40 = 976 个像素时钟
3.2 帧时序参数(垂直方向)
|<-VSPW->|<--VBP-->|<--------LINE(有效行)-------->|<-VFP->|
| 参数 | 全称 | 说明 | 本项目值 |
|---|---|---|---|
| VSPW | Vertical Sync Pulse Width | VSYNC 脉冲宽度,单位:行 | 3 |
| VBP | Vertical Back Porch | 帧同步后肩 | 32 |
| LINE | Vertical Valid | 有效行数(屏幕高度) | 480 |
| VFP | Vertical Front Porch | 帧同步前肩 | 13 |
一帧行数 = VSPW + VBP + LINE + VFP = 3 + 32 + 480 + 13 = 528 行
3.3 像素时钟计算
一帧时钟数 = 528 × 976 = 515,328
60帧/秒 = 515,328 × 60 = 30,919,680 ≈ 31 MHz
→ 实际配置 PLL5 输出 31.5 MHz
HBP/HFP/VBP/VFP 存在的原因: 源自 CRT 电子枪的历史设计。在 LCD 中是给屏幕内部 IC 的反应时间,让其识别换行/换帧信号,锁定有效像素数据的开始位置。不同屏幕参数不同,必须查屏幕手册,不能随意填写。
四、像素时钟配置
时钟路径
24MHz 晶振
↓
PLL5(VIDEO_PLL)×42 → 1008 MHz
↓ LCDIF1_PRE_CLK_SEL 选 PLL5
↓ LCDIF1_PRED = 3 → ÷4 → 252 MHz
↓ LCDIF1_PODF = 7 → ÷8
↓
LCDIF1_CLK_ROOT = 1008 ÷ 4 ÷ 8 = 31.5 MHz
涉及的寄存器
| 寄存器 | 位 | 说明 | 设置值 |
|---|---|---|---|
| CCM_ANALOG_PLL_VIDEO | bit6:0 DIV_SELECT | PLL5 倍频 | 42(×42=1008MHz) |
| CCM_ANALOG_PLL_VIDEO | bit20:19 POST_DIV_SELECT | 后分频 | 0 → 1分频 |
| CCM_ANALOG_PLL_VIDEO | bit13 ENABLE | PLL5 使能 | 1 |
| CCM_ANALOG_MISC2 | bit31:30 VIDEO_DIV | 视频分频 | 0 → 1分频 |
| CCM_CSCDR2 | bit17:15 LCDIF1_PRE_CLK_SEL | 时钟源选择 | 选 PLL5 |
| CCM_CSCDR2 | bit14:12 LCDIF1_PRED | 预分频 | 3 → ÷4 |
| CCM_CBCMR | bit25:23 LCDIF1_PODF | 后分频 | 7 → ÷8 |
| CCM_CSCDR2 | bit11:9 LCDIF1_CLK_SEL | 最终时钟选择 | pre-muxed |
五、eLCDIF 控制器寄存器详解
I.MX6U 的 LCD 控制器全称 Enhanced LCD Interface(eLCDIF),使用 DOTCLK 模式驱动 RGB LCD。
LCDIF_CTRL --- 主控制寄存器
| 位 | 名称 | 说明 | 本项目设置 |
|---|---|---|---|
| bit31 | SFTRST | 软复位,为1强制复位 | 初始化时先复位再清零 |
| bit30 | CLKGATE | 时钟门控,正常运行必须为0 | 0 |
| bit19 | BYPASS_COUNT | DOTCLK模式必须为1 | 1 |
| bit17 | DOTCLK_MODE | 为1 → DOTCLK模式 | 1 |
| bit11:10 | LCD_DATABUS_WIDTH | 数据总线宽度,3=24位 | 3 |
| bit9:8 | WORD_LENGTH | 像素数据宽度,3=24位/像素 | 3 |
| bit5 | MASTER | 主模式使能 | 1 |
| bit0 | RUN | eLCDIF 使能,最后置1 | 最后置1 |
c
// 代码中的写法
LCDIF->CTRL = (1 << 19) | (1 << 18) | (0x3 << 10) | (0x3 << 8) | (1 << 5);
// 最后使能
LCDIF->CTRL |= (1 << 0);
LCDIF_CTRL1
只用到 BYTE_PACKING_FORMAT(bit19:16):
0xF= 32位像素数据全有效(默认值)0x7= 仅低24位有效(A通道不传输,本项目使用)
c
tmp = LCDIF->CTRL1;
tmp &= ~(0xf << 16);
tmp |= (0x7 << 16);
LCDIF->CTRL1 = tmp;
LCDIF_TRANSFER_COUNT --- 分辨率寄存器
bit31:16 V_COUNT = 屏幕高度(480)
bit15:0 H_COUNT = 屏幕宽度(800)
c
LCDIF->TRANSFER_COUNT = (480 << 16) | 800;
LCDIF_VDCTRL0 --- VSYNC 控制
| 位 | 名称 | 说明 |
|---|---|---|
| bit28 | ENABLE_PRESENT | DE 信号使能:1=使能 |
| bit24 | ENABLE_POL | DE 极性:1=高有效 |
| bit21 | VSYNC_PERIOD_UNIT | DOTCLK 模式必须为1(单位=行) |
| bit20 | VSYNC_PULSE_WIDTH_UNIT | DOTCLK 模式必须为1 |
| bit17:0 | VSYNC_PULSE_WIDTH | VSPW 参数值 = 3 |
c
LCDIF->VDCTRL0 = (1 << 28) | (1 << 24) | (1 << 21) | (1 << 20) | 3;
LCDIF_VDCTRL1 --- VSYNC 总周期
值 = 高度 + VSPW + VBP + VFP = 480 + 3 + 32 + 13 = 528
LCDIF_VDCTRL2 --- HSYNC 控制
bit31:18 HSYNC_PULSE_WIDTH = HSPW = 48
bit17:0 HSYNC_PERIOD = 宽度 + HSPW + HBP + HFP = 800 + 48 + 88 + 40 = 976
c
LCDIF->VDCTRL2 = (48 << 18) | 976;
LCDIF_VDCTRL3 --- 等待计数
bit27:16 HORIZONTAL_WAIT_CNT = HSPW + HBP = 48 + 88 = 136
bit15:0 VERTICAL_WAIT_CNT = VSPW + VBP = 3 + 32 = 35
c
LCDIF->VDCTRL3 = (136 << 16) | 35;
LCDIF_VDCTRL4
bit18 SYNC_SIGNALS_ON = 1(使能 VSYNC/HSYNC/DOTCLK)
bit15:0 DOTCLK_H_VALID_DATA_CNT = 屏幕宽度 = 800
c
LCDIF->VDCTRL4 = (1 << 18) | 800;
显存寄存器
| 寄存器 | 说明 |
|---|---|
| LCDIF_CUR_BUF | 当前帧显存地址,eLCDIF 自动读取并显示 |
| LCDIF_NEXT_BUF | 下一帧显存地址(双缓冲用,也可与 CUR_BUF 相同) |
c
#define LCD_CUR_BUF 0x88000000
LCDIF->CUR_BUF = LCD_CUR_BUF;
LCDIF->NEXT_BUF = LCD_CUR_BUF;
六、lcd_init() 完整初始化流程
1. lcd_io_init() 初始化 IO 引脚
↓ 24根数据线 DATA00~DATA23,CLK/HSYNC/VSYNC/ENABLE
↓ 引脚复用:IOMUXC 配置为 LCDIF,电气特性 0x10f9
↓ 背光控制:GPIO1_IO08 → 输出高电平点亮
2. lcd_clk_init() 初始化像素时钟
↓ PLL5 × 42 = 1008MHz
↓ PRED ÷ 4 = 252MHz
↓ PODF ÷ 8 = 31.5MHz
3. lcd_reset() 复位 eLCDIF
↓ SFTRST=1 → delay_ms(20) → SFTRST=0, CLKGATE=0
4. 配置 LCDIF_CTRL DOTCLK模式,24位总线,主模式(RUN 暂不置1)
5. 配置 LCDIF_CTRL1 BYTE_PACKING_FORMAT = 0x7
6. 配置 LCDIF_TRANSFER_COUNT (480<<16)|800
7. 配置 LCDIF_VDCTRL0 信号极性,VSPW=3
8. 配置 LCDIF_VDCTRL1 VSYNC总周期=528
9. 配置 LCDIF_VDCTRL2 HSPW=48,HSYNC总周期=976
10. 配置 LCDIF_VDCTRL3 水平等待=136,垂直等待=35
11. 配置 LCDIF_VDCTRL4 使能同步信号,有效宽度=800
12. 设置显存地址 CUR_BUF = NEXT_BUF = 0x88000000
13. LCDIF_CTRL |= (1<<0) RUN=1,开始输出像素时钟
14. GPIO1_IO08 = 高电平 开启背光,屏幕点亮 ✓
七、显存机制与 lcd_fill()
LCD 内部没有显存 。需要从 DDR3 中划出一块内存(约 1.5MB)作为显存,把首地址写入 LCDIF_CUR_BUF,eLCDIF 控制器自动循环读取这块内存,按像素格式输出到屏幕。要显示什么,直接修改显存内容即可。
c
int lcd_fill(void)
{
uint32_t * p = (uint32_t *)LCD_CUR_BUF; // 指向显存起始地址
int i = 0;
for(i = 0; i < 800 * 300; i++) // 填充前 300 行
{
*(p + i) = 0xff00ff; // 紫色
}
return 0;
}
八、图形库移植接口
移植 graphics.c / graphics.h / font.h 需要先实现三个底层接口:
c
// 1. 在指定坐标画一个像素点
void lcd_draw_point(int x, int y, uint32_t color);
// 2. 读取指定坐标的像素值
uint32_t lcd_read_point(int x, int y);
// 3. 画一个矩形(左上角x0,y0,右下角x1,y1)
void lcd_draw_rect(int x0, int y0, int x1, int y1, uint32_t color);
图形库提供的常用函数:
c
// 在指定位置显示字符串(最常用)
lcd_show_string(int x, int y, char *str, uint32_t color);
// 画线、画圆等
lcd_draw_line(...);
lcd_draw_circle(...);
九、LCD 设备结构体设计
将 LCD 参数封装进结构体,方便图形库调用:
c
typedef struct {
uint32_t width; // 屏幕宽度(像素)
uint32_t height; // 屏幕高度(像素)
uint32_t hspw; // HSYNC 脉冲宽度
uint32_t hbp; // 行后肩
uint32_t hfp; // 行前肩
uint32_t vspw; // VSYNC 脉冲宽度
uint32_t vbp; // 帧后肩
uint32_t vfp; // 帧前肩
uint32_t *framebuffer; // 显存首地址
} Lcd_type;
extern Lcd_type dev; // 全局变量,供图形库调用
十、关键寄存器速查表
| 寄存器 | 关键位 | 功能 | 本项目值 |
|---|---|---|---|
| LCDIF_CTRL | bit19 BYPASS_COUNT | DOTCLK 模式必须=1 | 1 |
| LCDIF_CTRL | bit17 DOTCLK_MODE | DOTCLK 模式使能 | 1 |
| LCDIF_CTRL | bit11:10 LCD_DATABUS_WIDTH | 总线宽度(3=24位) | 3 |
| LCDIF_CTRL | bit9:8 WORD_LENGTH | 像素位数(3=24位) | 3 |
| LCDIF_CTRL | bit5 MASTER | 主模式 | 1 |
| LCDIF_CTRL | bit0 RUN | eLCDIF 使能 | 最后置1 |
| LCDIF_CTRL1 | bit19:16 BYTE_PACKING_FORMAT | 有效字节掩码(0x7=24位) | 0x7 |
| LCDIF_TRANSFER_COUNT | bit31:16 V_COUNT | 垂直分辨率 | 480 |
| LCDIF_TRANSFER_COUNT | bit15:0 H_COUNT | 水平分辨率 | 800 |
| LCDIF_VDCTRL0 | bit28 ENABLE_PRESENT | DE 信号使能 | 1 |
| LCDIF_VDCTRL0 | bit17:0 VSYNC_PULSE_WIDTH | VSPW 值 | 3 |
| LCDIF_VDCTRL1 | 全部 | VSYNC总周期 | 528 |
| LCDIF_VDCTRL2 | bit31:18 | HSPW 值 | 48 |
| LCDIF_VDCTRL2 | bit17:0 | HSYNC总周期 | 976 |
| LCDIF_VDCTRL3 | bit27:16 | HSPW+HBP(水平等待) | 136 |
| LCDIF_VDCTRL3 | bit15:0 | VSPW+VBP(垂直等待) | 35 |
| LCDIF_VDCTRL4 | bit18 SYNC_SIGNALS_ON | 同步信号使能 | 1 |
| LCDIF_VDCTRL4 | bit15:0 | 有效宽度(水平像素数) | 800 |
| LCDIF_CUR_BUF | 全部 | 当前帧显存首地址 | 0x88000000 |
| LCDIF_NEXT_BUF | 全部 | 下一帧显存首地址 | 0x88000000 |
十一、面试常问 Q&A
Q1:LCD 的 HBP/HFP/VBP/VFP 是什么,为什么存在?
源自 CRT 电子枪的历史设计。在 LCD 中,这段时间是给屏幕内部 IC 的反应时间,让其识别换行/换帧信号,锁定有效像素数据的开始位置。不同屏幕的这四个参数不同,必须查屏幕手册获取,不能随意填写。
Q2:显存在哪里,如何显示图像?
LCD 内部没有显存,需要从 DDR3 中划出一块内存(约 1.5MB)作为显存,把首地址写入 LCDIF_CUR_BUF,eLCDIF 控制器自动把这块内存按像素格式输出到屏幕。要显示什么,直接修改显存内容即可。
Q3:像素时钟 31 MHz 是怎么来的?
由屏幕分辨率和刷新率决定:
(VSPW+VBP+HEIGHT+VFP) × (HSPW+HBP+WIDTH+HFP) × fps
= (3+32+480+13) × (48+88+800+40) × 60
= 528 × 976 × 60
≈ 31 MHz
实际通过 PLL5 配置为 31.5MHz。
Q4:为什么用 DOTCLK 模式而不是 VSYNC 或 MPU 模式?
DOTCLK 模式就是 RGB 接口,适合直接驱动带 RGB 信号线的 LCD 屏幕(24根数据线 + 控制信号)。本项目搭载的 ATK4384 就是 RGB 接口屏幕。与 MPU 模式相比,DOTCLK 模式由控制器自动刷新,CPU 无需干预。
Q5:BYTE_PACKING_FORMAT 为什么设置为 0x7?
像素格式虽然是 ARGB8888(每像素 4字节),但 A(透明度)通道不需要输出到屏幕。BYTE_PACKING=0x7 表示每个 32bit 数据中,bit[23:0](RGB 三通道)有效,bit[31:24](A通道)无效,控制器只传输 24 位有效数据。