4. 显示接口技术详解
屏幕驱动这块,说白了就是让SoC和屏幕面板“对上话”。8295要驱动多块屏,每块屏的接口、时序、背光都得单独伺候。我最早做车载显示时,以为时序配对了就行,结果屏幕死活不亮——后来才发现,初始化序列里少发了一条命令。
嗯,咱们今天就把这三块掰开揉碎了讲。
4.1 LVDS与eDP接口的时序配置
先看接口。LVDS和eDP是目前车载屏的主流选择。LVDS成熟、抗干扰好,但带宽有限;eDP带宽高、线束少,适合高分辨率屏。
| 对比项 | LVDS | eDP |
|---|---|---|
| 传输方式 | 差分对,多对数据线+时钟 | 差分对,内嵌时钟,可多lane |
| 典型带宽 | 单链路~1.2Gbps | HBR2 ~5.4Gbps/lane |
| 线束数量 | 较多(4~8对) | 较少(2~4对) |
| 适用分辨率 | 1920x720@60fps以内 | 4K@60fps及以上 |
| 初始化流程 | 纯硬件时序,无需握手 | 需AUX通道握手+Link Training |
时序配置的核心,是让SoC的显示控制器输出与面板的时序要求完全匹配。我习惯把时序参数拆成三组来看:
- 像素时钟:决定了每秒钟刷多少个像素点。算错了,画面要么撕裂,要么黑屏。
- 同步信号:Hsync和Vsync的极性、脉宽、前后肩。这些参数决定了画面从哪里开始、到哪里结束。
- 数据使能:DE信号的有效区间,必须和像素数据对齐。
举个例子,一块1920x720的LVDS屏,典型时序参数长这样:
// 像素时钟:74.25MHz
// Hsync:脉宽 40,前肩 88,后肩 148
// Vsync:脉宽 5,前肩 2,后肩 15
// 有效分辨率:1920 x 720
struct display_timing {
.pixelclock = 74250000,
.hactive = 1920,
.hfront_porch = 88,
.hback_porch = 148,
.hsync_len = 40,
.vactive = 720,
.vfront_porch = 2,
.vback_porch = 15,
.vsync_len = 5,
.flags = DISPLAY_FLAGS_HSYNC_LOW | DISPLAY_FLAGS_VSYNC_LOW,
};
eDP的时序配置比LVDS多一步——Link Training。说白了,就是SoC和面板之间先“握个手”,确认用多少条lane、多高速率来传数据。8295的eDP控制器支持HBR2(5.4Gbps/lane),但面板不一定支持。我建议在驱动里做降级处理:先尝试HBR2,如果Training失败,自动降到HBR(2.7Gbps)。
4.2 PWM调光策略
背光控制,车载屏的刚需。白天要亮到能看清,晚上要暗到不刺眼。PWM调光是最常用的方案。
PWM调光的核心参数就两个:频率和占空比。
- 频率:决定了人眼是否感知到闪烁。我一般设到200Hz以上,低于100Hz会有明显频闪,开车时容易头晕。
- 占空比:决定了亮度。0%全黑,100%最亮。但实际面板的亮度-占空比曲线不是线性的,需要做Gamma校正。
8295内部有专用的PWM定时器,可以独立配置频率和极性。我常用的配置流程:
- 设置PWM时钟源(通常用系统主时钟分频)
- 配置周期寄存器(决定频率)
- 配置占空比寄存器(决定亮度)
- 使能PWM输出
// 以50kHz PWM为例,主时钟100MHz
// 周期 = 100MHz / 50kHz = 2000
// 占空比50%时,高电平时间 = 1000
pwm_set_clk(100000000); // 设置时钟源
pwm_set_period(2000); // 设置周期
pwm_set_duty(1000); // 设置占空比
pwm_set_polarity(1); // 高电平有效
pwm_enable(1); // 使能输出
还有一个坑:PWM与显示刷新率的同步。如果PWM频率和屏幕刷新率不是整数倍关系,屏幕上会出现滚动暗条(俗称“水波纹”)。我建议PWM频率设为刷新率的整数倍,比如60Hz刷新率配240Hz PWM。
4.3 屏幕初始化序列
屏幕初始化,是驱动开发里最容易出问题的地方。每块屏都有自己的“脾气”,初始化序列必须严格按照面板厂商提供的时序来。
初始化序列一般包含:
- 上电时序:VDD、VGL、VGH等电源的上下电顺序和延时
- 复位时序:RESET引脚的拉低/拉高时序
- 寄存器配置:通过I2C/SPI写入面板控制寄存器
- 显示使能:最后发送Display On命令
我见过最典型的错误,是上电时序没满足。某块屏要求VDD先上电,延时10ms后再上VGL。结果驱动里两个电源同时上,屏幕直接保护性黑屏。
下面是一个典型的初始化序列示例:
// 1. 上电
gpio_set(VDD_EN, 1);
mdelay(10);
gpio_set(VGL_EN, 1);
mdelay(5);
gpio_set(VGH_EN, 1);
mdelay(10);
// 2. 复位
gpio_set(RESET, 0);
mdelay(1);
gpio_set(RESET, 1);
mdelay(120); // 等待内部初始化完成
// 3. 通过I2C配置寄存器
i2c_write(0x3C, 0x11, 0x00); // Sleep Out
mdelay(120);
i2c_write(0x3C, 0x36, 0x70); // MADCTL: 旋转方向
i2c_write(0x3C, 0x3A, 0x55); // 像素格式: 16bit
// 4. 显示使能
i2c_write(0x3C, 0x29, 0x00); // Display On
mdelay(50);
8295的显示控制器支持多路DSI/DPI输出,可以同时初始化多块屏。但要注意:每块屏的初始化序列是独立的。不能因为主屏初始化成功了,就认为副屏也能用同一套参数。我遇到过副屏和主屏型号不同,但用了同一份初始化代码,结果副屏颜色完全不对。
4.4 知识结构总览
下面这张图,把本章的核心逻辑串起来了:
嗯,屏幕驱动这块,说到底就是“时序对、背光亮、初始化完整”。8295的硬件能力很强,但再强的SoC也架不住驱动代码里一个时序参数配错。我建议你在开发时,先把面板规格书里的时序参数做成结构体,然后逐项核对。别偷懒,每块屏单独测一遍。