3. Qt 嵌入式开发环境搭建
设备树,说白了就是Linux内核的“硬件说明书”。你想想看,内核要驱动一块LCD屏,总得知道它怎么接的、时序对不对、背光怎么控吧?这些信息,全写在设备树里。我刚开始接触嵌入式显示时,最头疼的就是调设备树——改一个参数,屏幕可能就花了、闪了,甚至直接黑掉。
这一节,我们就来拆解显示节点的三个核心部分:timing参数、pinctrl设置、backlight绑定。搞懂了它们,你就能自己给一块新屏写设备树了。
3.1 显示节点的基础骨架
先看一个完整的显示节点长什么样。以i.MX6ULL为例,我习惯这样组织:
&lcdif {
pinctrl-names = "default";
pinctrl-0 = &pinctrl_lcdif_dat
&pinctrl_lcdif_ctrl;
display = &display0;
status = "okay";
display0: display {
bits-per-pixel = <32>;
bus-width = <24>;
display-timings {
native-mode = <&timing0>;
timing0: timing0 {
clock-frequency = <33000000>;
hactive = <800>;
vactive = <480>;
hfront-porch = <40>;
hback-porch = <40>;
hsync-len = <48>;
vfront-porch = <13>;
vback-porch = <29>;
vsync-len = <3>;
hsync-active = <0>;
vsync-active = <0>;
de-active = <1>;
pixelclk-active = <0>;
};
};
};
};
嗯,这里要注意:lcdif是LCD控制器节点,display0是子节点,display-timings才是真正的时序配置。层级关系不能搞错,否则内核解析不到。
3.2 timing参数:屏幕的“心跳”
timing参数决定了屏幕刷新的节奏。每个参数都有它的物理意义,我列个表方便你对照:
| 参数 | 含义 | 典型值(800x480) | 单位 |
|---|---|---|---|
clock-frequency |
像素时钟频率 | 33 MHz | Hz |
hactive |
水平有效像素 | 800 | 像素 |
vactive |
垂直有效像素 | 480 | 像素 |
hfront-porch |
行前肩 | 40 | 像素时钟 |
hback-porch |
行后肩 | 40 | 像素时钟 |
hsync-len |
行同步脉宽 | 48 | 像素时钟 |
vfront-porch |
帧前肩 | 13 | 行 |
vback-porch |
帧后肩 | 29 | 行 |
vsync-len |
帧同步脉宽 | 3 | 行 |
hsync-active |
行同步极性(0低有效) | 0 | - |
vsync-active |
帧同步极性(0低有效) | 0 | - |
de-active |
数据使能极性 | 1 | - |
pixelclk-active |
像素时钟边沿(0下降沿) | 0 | - |
为什么会这样?因为每个LCD面板的驱动IC不一样,对时序的容忍度也不同。我曾经调一块7寸屏,按datasheet填了参数,结果屏幕一直闪。后来把hfront-porch从40改成45,立马稳定了。嗯,有时候就差这么几个像素时钟。
3.3 pinctrl设置:管脚怎么接
pinctrl就是告诉内核:这些GPIO引脚要复用成LCD功能。我习惯把数据引脚和控制引脚分开定义:
&iomuxc {
pinctrl_lcdif_dat: lcdifdatgrp {
fsl,pins = <
MX6UL_PAD_LCD_DATA00__LCDIF_DATA00 0x79
MX6UL_PAD_LCD_DATA01__LCDIF_DATA01 0x79
/* ... 省略中间引脚 ... */
MX6UL_PAD_LCD_DATA23__LCDIF_DATA23 0x79
>;
};
pinctrl_lcdif_ctrl: lcdifctrlgrp {
fsl,pins = <
MX6UL_PAD_LCD_CLK__LCDIF_CLK 0x79
MX6UL_PAD_LCD_ENABLE__LCDIF_ENABLE 0x79
MX6UL_PAD_LCD_HSYNC__LCDIF_HSYNC 0x79
MX6UL_PAD_LCD_VSYNC__LCDIF_VSYNC 0x79
>;
};
};
这里0x79是引脚配置值,我解释一下:
- bit[7:4]:驱动能力,0x7对应R0=1、R1=1,即高驱动
- bit[3]:速度,1表示100MHz
- bit[2:0]:上下拉,001表示100K上拉
0x79写成了0x70,导致LCD_CLK信号太弱,屏幕出现横条纹。查了两天才发现是驱动能力不够。所以,LCD引脚建议都用高驱动+上拉。
3.4 backlight绑定:亮起来
背光控制,通常用PWM。设备树里要绑定一个pwm-backlight节点:
&pwm1 {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_pwm1>;
status = "okay";
};
&backlight {
compatible = "pwm-backlight";
pwms = <&pwm1 0 5000000>;
brightness-levels = <0 4 8 16 32 64 128 255>;
default-brightness-level = <6>;
status = "okay";
};
关键参数说明:
pwms:第三个参数5000000是PWM周期,单位ns,对应200Hzbrightness-levels:亮度等级数组,我习惯用非线性的,低亮度时步长小,人眼更舒服default-brightness-level:默认亮度索引,6对应128,大概50%亮度
然后在显示节点里加上背光引用:
&lcdif {
backlight = &backlight;
/* ... 其他配置 ... */
};
这样,内核启动时就会自动初始化背光PWM,Qt应用也能通过sysfs调节亮度。
3.5 整体流程梳理
我把整个显示节点配置的流程画成一张图,方便你理解:
3.6 调试技巧与避坑
配置写好了,怎么验证?我分享几个实战经验:
- 检查设备树编译:
make dtbs没有报错,再用dtc -I dtb -O dts反编译确认节点存在 - 查看内核日志:
dmesg | grep -i lcd或dmesg | grep -i backlight,看有没有probe成功 - 检查Framebuffer设备:
ls -l /dev/fb*,如果有/dev/fb0,说明注册成功 - 测试背光:
echo 100 > /sys/class/backlight/backlight/brightness,看屏幕亮度变化
我曾经在一个项目里,因为pixelclk-active写反了,导致屏幕颜色偏绿。查了两天,最后用示波器量时钟边沿才发现——数据在上升沿采样,我配置成了下降沿。嗯,这种坑踩过一次就记住了。