U-Boot 阶段动态加载设备树 Overlay(dtbo)的完整实现:从屏幕检测到配置加载
在多屏幕产品的自动适配方案中,U-Boot 阶段的屏幕型号检测是前提,设备树 Overlay 动态加载是核心。本文将补充 U-Boot 中屏幕检测的具体代码实现,并结合优化后的 overlay 加载脚本,形成完整的解决方案。
一、U-Boot 屏幕型号检测的核心逻辑
屏幕检测的本质是通过硬件特征(如 I2C ID、GPIO 电平)区分型号,核心步骤为:
- 初始化检测所需的硬件接口(I2C 控制器 / GPIO 控制器);
- 读取屏幕的硬件特征值(ID 寄存器 / GPIO 电平);
- 根据特征值映射到具体屏幕型号;
- 将型号保存到 U-Boot 环境变量(
screen_type),供后续 overlay 加载脚本使用。
1.1 基于 I2C 接口的屏幕检测(适用于带 ID 寄存器的屏幕)
许多屏幕(如 LCD/OLED 模块)通过 I2C 总线暴露 ID 寄存器(厂商 ID / 产品 ID),可通过读取寄存器值区分型号。
代码实现(以 ARM 架构为例)
在 U-Boot 源码的board/<厂商>/<板型>/board.c中(如board/myboard/myboard.c),添加检测函数:
c
运行
#include <common.h>
#include <i2c.h>
#include <dm/uclass.h>
#include <dm/device.h>
// 屏幕I2C配置(根据硬件手册修改)
#define SCREEN_I2C_BUS 0 // I2C总线号(通常0或1)
#define SCREEN_I2C_ADDR 0x38 // 屏幕I2C设备地址(7位地址)
#define SCREEN_ID_REG 0x00 // ID寄存器地址
/**
* 检测屏幕型号(通过I2C读取ID)
* 返回值:1=型号A,2=型号B,0=未知
*/
static int detect_screen_type(void)
{
int ret;
u8 id[2]; // 存储读取到的ID(2字节,可根据实际调整长度)
// 1. 初始化I2C总线
ret = i2c_init(SCREEN_I2C_BUS, 100000); // 100KHz总线频率
if (ret) {
printf("I2C bus %d init failed (ret=%d)\n", SCREEN_I2C_BUS, ret);
return 0;
}
// 2. 读取屏幕ID寄存器(从SCREEN_ID_REG地址读取2字节)
ret = i2c_read(SCREEN_I2C_BUS, SCREEN_I2C_ADDR, SCREEN_ID_REG, 1, id, 2);
if (ret != 0) {
printf("Failed to read screen ID (ret=%d)\n", ret);
return 0;
}
// 3. 根据ID值判断型号(需与屏幕手册中的ID对应)
printf("Detected screen ID: 0x%02X%02X\n", id[0], id[1]);
if (id[0] == 0xAB && id[1] == 0xCD) {
return 1; // 型号A
} else if (id[0] == 0xEF && id[1] == 0x12) {
return 2; // 型号B
} else {
return 0; // 未知型号
}
}
1.2 基于 GPIO 电平的屏幕检测(适用于硬件引脚区分的场景)
若屏幕硬件设计中通过拨码开关或电阻分压将型号映射到 GPIO 电平(如 2 个 GPIO 可区分 4 种型号),可通过读取 GPIO 状态实现检测。
代码实现
同样在board/<厂商>/<板型>/board.c中添加:
c
运行
#include <common.h>
#include <gpio.h>
// GPIO配置(根据硬件原理图修改)
#define SCREEN_DETECT_GPIO1 5 // 检测引脚1(如GPIO5)
#define SCREEN_DETECT_GPIO2 6 // 检测引脚2(如GPIO6)
/**
* 检测屏幕型号(通过GPIO电平)
* 返回值:1=型号A,2=型号B,3=型号C,0=未知
*/
static int detect_screen_type(void)
{
int val1, val2;
u8 type_code;
// 1. 初始化GPIO为输入模式
gpio_request(SCREEN_DETECT_GPIO1, "screen_detect1");
gpio_direction_input(SCREEN_DETECT_GPIO1);
gpio_request(SCREEN_DETECT_GPIO2, "screen_detect2");
gpio_direction_input(SCREEN_DETECT_GPIO2);
// 2. 读取GPIO电平(0=低电平,1=高电平)
val1 = gpio_get_value(SCREEN_DETECT_GPIO1);
val2 = gpio_get_value(SCREEN_DETECT_GPIO2);
type_code = (val1 << 1) | val2; // 组合为2位二进制值
// 3. 根据电平组合判断型号(需与硬件设计对应)
printf("Screen detect GPIO levels: GPIO1=%d, GPIO2=%d → code=0x%02X\n", val1, val2, type_code);
switch (type_code) {
case 0x00: return 1; // 型号A:GPIO1=0, GPIO2=0
case 0x01: return 2; // 型号B:GPIO1=0, GPIO2=1
case 0x10: return 3; // 型号C:GPIO1=1, GPIO2=0
default: return 0; // 未知型号
}
}
1.3 将检测结果保存到 U-Boot 环境变量
在 U-Boot 的后期初始化函数board_init_r中调用检测函数,并将结果存入环境变量screen_type:
c
运行
int board_init_r(gd_t *gd, ulong dest_addr)
{
int screen_type;
// 其他板级初始化(如内存、网络等)
// ...
// 检测屏幕型号
screen_type = detect_screen_type();
if (screen_type > 0) {
// 将型号存入环境变量(如screen_type=1)
setenv("screen_type", itoa(screen_type));
printf("Screen type detected: %d, saved to env\n", screen_type);
} else {
// 未检测到型号时,设置默认值(可选)
setenv("screen_type", "0");
printf("Unknown screen type, set screen_type=0\n");
}
// 继续执行其他初始化
// ...
return 0;
}
1.4 U-Boot 配置依赖
确保 U-Boot 编译时开启以下配置(通过make menuconfig设置):
- 若使用 I2C 检测:
Device Drivers → I2C Support → Enable I2C及对应控制器驱动(如DesignWare I2C); - 若使用 GPIO 检测:
Device Drivers → GPIO Support → Enable GPIO及对应 GPIO 控制器驱动; - 环境变量存储:
Environment → Environment in MMC(或 SPI Flash 等,确保saveenv生效)。
二、完整启动流程:从检测到 overlay 加载
结合屏幕检测代码与优化后的 overlay 加载脚本,完整流程如下:
2.1 屏幕检测阶段(U-Boot 初始化)
- U-Boot 启动后执行
board_init_r; - 调用
detect_screen_type通过 I2C/GPIO 检测屏幕型号; - 将型号存入环境变量
screen_type(如screen_type=1)。
2.2 overlay 加载与设备树修改
优化后的 U-Boot 启动脚本(结合检测结果动态加载 overlay):
bash
# 加载overlay的脚本(依赖screen_type环境变量)
setenv load_overlay ' \
mmc dev 0; \ # 选中存储dtbo的MMC设备(如SD卡/eMMC)
# 根据屏幕型号加载对应dtbo
if test ${screen_type} -eq 1; then \
fatload mmc 0:1 0x88000000 overlays/screen_a.dtbo; \
elif test ${screen_type} -eq 2; then \
fatload mmc 0:1 0x88000000 overlays/screen_b.dtbo; \
elif test ${screen_type} -eq 3; then \
fatload mmc 0:1 0x88000000 overlays/screen_c.dtbo; \
else \
echo "Unknown screen type (${screen_type}), load default overlay"; \
fatload mmc 0:1 0x88000000 overlays/screen_default.dtbo; \
fi; \
# 应用overlay到基础dtb(地址0x87000000)
if test -n ${fileaddr}; then \
fdt apply 0x88000000 -f 0x87000000; \
fi \
'
# 完整启动命令(顺序:加载dtb → 加载overlay → 加载内核 → 启动)
setenv bootcmd ' \
mmc dev 0 || echo "MMC init failed!"; \
# 1. 加载基础设备树到内存(0x87000000)
fatload mmc 0:1 0x87000000 dtb.img || exit 1; \
# 2. 加载并应用overlay
run load_overlay; \
# 3. 加载内核到内存(0x80008000)
fatload mmc 0:1 0x80008000 zImage || exit 1; \
# 4. 启动内核(使用修改后的dtb)
bootz 0x80008000 - 0x87000000; \
'
saveenv # 保存环境变量
三、调试与验证技巧
-
验证屏幕检测结果:在 U-Boot 命令行执行
printenv screen_type,确认输出与实际屏幕型号一致(如screen_type=1)。 -
检查 overlay 加载状态:执行
fdt print 0x87000000 /lcd-controller(替换为实际屏幕节点路径),查看节点属性(如hactive、vactive)是否与 dtbo 中定义的一致。 -
排查 I2C/GPIO 问题:
- I2C 检测失败:用
i2c probe 0 0x38(总线 0,地址 0x38)确认设备是否存在; - GPIO 检测失败:用
gpio input 5和gpio get 5手动读取电平,验证硬件连接。
- I2C 检测失败:用
四、总结
本文通过补充 U-Boot 屏幕检测代码(I2C/GPIO 两种方案),结合优化后的 overlay 加载脚本,形成了从 “硬件特征读取” 到 “设备树动态修改” 的完整闭环。该方案可实现多型号屏幕的全自动适配,无需人工干预,特别适合量产场景。实际应用中需根据屏幕硬件手册调整检测逻辑(如 ID 值、GPIO 引脚)和 overlay 配置(时序参数、初始化指令),确保兼容性与稳定性。
1117

被折叠的 条评论
为什么被折叠?



