2、初始化序列:上电时序、初始化命令配置、寄存器读写操作
好,咱们接着聊。上一章我们把LCD的接口类型和硬件连接理清了,这一章要进入一个非常关键的环节——初始化序列。
说白了,LCD屏就像一台刚出厂的手机,你拿到手得先开机、设置语言、连Wi-Fi,才能正常用。LCD也一样,上电之后必须走一套严格的初始化流程,否则它要么黑屏,要么花屏,要么显示异常。
我刚开始做LCD驱动那会儿,就吃过这个亏。有一次项目赶进度,我图省事,把上电时序里的一个延时从10ms改成了5ms,结果屏死活点不亮。查了两天,最后发现是电源稳定时间不够。嗯,从那以后,我再也不敢乱改时序参数了。
2.1 上电时序:别小看这几十毫秒
上电时序,就是给LCD屏供电时,各路电源和信号线必须遵循的先后顺序。不同厂商、不同型号的屏,要求可能不一样。但大体上,都遵循一个原则:先供核心电压,再供IO电压,最后给复位信号。
我习惯把上电时序拆成三步来看:
- VCI(模拟电源)上电——这是屏的主电源,一般3.3V或2.8V。先给这个电,让屏内部电路开始工作。
- VDDIO(IO电源)上电——这是接口电平的电源,决定了通信引脚的电平标准。通常和主控的IO电压一致。
- 复位信号释放——等电源稳定后,拉高复位引脚,让屏退出复位状态。
这里有个关键点:电源稳定时间。你给屏供上电,不代表电压立刻就稳了。电源芯片有启动时间,电容有充电时间。我一般会在每步之间加10~20ms的延时,确保电压稳定。
典型上电时序示例(以ILI9341为例):
// 步骤1:VCI上电
LCD_VCI_ENABLE(); // 打开3.3V电源
delay_ms(20); // 等待电源稳定
// 步骤2:VDDIO上电
LCD_VDDIO_ENABLE(); // 打开1.8V/3.3V IO电源
delay_ms(10); // 等待IO电源稳定
// 步骤3:复位信号释放
LCD_RESET_LOW(); // 拉低复位引脚
delay_ms(10); // 保持复位状态至少10ms
LCD_RESET_HIGH(); // 拉高复位引脚,释放复位
delay_ms(120); // 等待屏内部初始化完成
注意:有些屏对时序非常敏感。比如,如果复位信号释放得太早,屏可能还没准备好接收命令。我遇到过一款屏,复位后必须等150ms才能发第一条命令,否则直接死机。所以,一定要看数据手册里的时序图,别凭感觉写。
2.2 初始化命令配置:屏的“出厂设置”
上电完成后,屏还处于“裸机”状态。你需要通过SPI或MCU接口,往它的寄存器里写一堆配置命令,告诉它:你要用什么颜色格式、扫描方向、伽马曲线、背光亮度等等。
这些命令,通常由屏厂提供。你拿到屏的规格书,里面会有一张“初始化命令表”。我见过最长的,有上百条命令。别怕,大部分都是固定值,直接抄就行。
但这里有个坑:命令的顺序不能乱。有些命令必须在特定命令之后才能生效。比如,设置伽马曲线之前,必须先退出睡眠模式。我曾经因为把这两条命令的顺序搞反了,屏幕颜色偏得离谱,调了一下午才发现。
我一般会把初始化命令封装成一个数组,结构如下:
// 初始化命令结构体
typedef struct {
uint8_t cmd; // 命令字节
uint8_t data_len; // 数据长度(0表示无数据)
uint8_t *data; // 数据指针
} lcd_init_cmd_t;
// 示例:ILI9341初始化命令表(部分)
const lcd_init_cmd_t ili9341_init[] = {
{0x01, 0, NULL}, // 软件复位
{0x11, 0, NULL}, // 退出睡眠模式
{0x36, 1, (uint8_t[]){0x48}}, // 设置扫描方向
{0x3A, 1, (uint8_t[]){0x55}}, // 设置像素格式(16位色)
{0x29, 0, NULL}, // 开启显示
// ... 更多命令
};
执行的时候,就一条一条发。每条命令之间,记得加一点延时,给屏内部处理的时间。我习惯在每条命令后加5ms延时,虽然保守,但稳定。
小技巧:如果你手头没有屏厂提供的初始化代码,可以试试“读寄存器”的方法。有些屏支持读取当前寄存器值,你可以从一块正常工作的屏上把配置读出来,然后写到新屏上。我在项目里用过这招,省了不少事。
2.3 寄存器读写操作:和屏“对话”的基本功
初始化命令的本质,就是往屏的寄存器里写数据。所以,你得先学会怎么读写寄存器。
不同的接口,读写方式不一样。但核心逻辑是一样的:先发命令,再发数据。
以SPI接口为例,读写操作通常分两步:
- 写命令:拉低DC引脚(表示命令),通过SPI发送命令字节。
- 写数据:拉高DC引脚(表示数据),通过SPI发送数据字节。
读操作稍微复杂一点。有些屏支持读寄存器,但需要先发一个“读命令”,然后切换SPI方向,从屏里读数据。我一般只在调试时用读操作,正常初始化只用写。
// SPI写命令
void lcd_write_cmd(uint8_t cmd) {
LCD_DC_LOW(); // 命令模式
LCD_CS_LOW(); // 选中片选
spi_transfer(cmd); // 发送命令字节
LCD_CS_HIGH(); // 释放片选
}
// SPI写数据
void lcd_write_data(uint8_t data) {
LCD_DC_HIGH(); // 数据模式
LCD_CS_LOW(); // 选中片选
spi_transfer(data); // 发送数据字节
LCD_CS_HIGH(); // 释放片选
}
// 写多条数据(用于初始化命令中的参数)
void lcd_write_data_buf(uint8_t *buf, uint16_t len) {
LCD_DC_HIGH();
LCD_CS_LOW();
for (uint16_t i = 0; i < len; i++) {
spi_transfer(buf[i]);
}
LCD_CS_HIGH();
}
寄存器读写操作要点总结:
| 操作 | DC引脚状态 | 说明 |
|---|---|---|
| 写命令 | 低电平 | 发送命令字节,告诉屏要做什么 |
| 写数据 | 高电平 | 发送数据字节,配置寄存器参数 |
| 读数据 | 高电平 | 从屏读取寄存器值(需先发读命令) |
你想想看,如果连读写函数都写不对,后面的初始化命令配置就是空中楼阁。所以,我建议你在写初始化代码之前,先写一个简单的测试函数:往屏的某个寄存器写一个值,再读回来,看看对不对。这叫“回读验证”,能帮你快速定位硬件或驱动问题。
嗯,这一章的内容就这些。上电时序、初始化命令配置、寄存器读写操作,这三块是LCD驱动的基石。下一章,我们会聊到更具体的显示优化技巧。到时候见。