U-Boot 阶段动态加载设备树 Overlay(dtbo)的脚本优化与实践

U-Boot 阶段动态加载设备树 Overlay(dtbo)的完整实现:从屏幕检测到配置加载

在多屏幕产品的自动适配方案中,U-Boot 阶段的屏幕型号检测是前提,设备树 Overlay 动态加载是核心。本文将补充 U-Boot 中屏幕检测的具体代码实现,并结合优化后的 overlay 加载脚本,形成完整的解决方案。

一、U-Boot 屏幕型号检测的核心逻辑

屏幕检测的本质是通过硬件特征(如 I2C ID、GPIO 电平)区分型号,核心步骤为:

  1. 初始化检测所需的硬件接口(I2C 控制器 / GPIO 控制器);
  2. 读取屏幕的硬件特征值(ID 寄存器 / GPIO 电平);
  3. 根据特征值映射到具体屏幕型号;
  4. 将型号保存到 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 初始化)

  1. U-Boot 启动后执行board_init_r
  2. 调用detect_screen_type通过 I2C/GPIO 检测屏幕型号;
  3. 将型号存入环境变量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  # 保存环境变量

三、调试与验证技巧

  1. 验证屏幕检测结果:在 U-Boot 命令行执行printenv screen_type,确认输出与实际屏幕型号一致(如screen_type=1)。

  2. 检查 overlay 加载状态:执行fdt print 0x87000000 /lcd-controller(替换为实际屏幕节点路径),查看节点属性(如hactivevactive)是否与 dtbo 中定义的一致。

  3. 排查 I2C/GPIO 问题

    • I2C 检测失败:用i2c probe 0 0x38(总线 0,地址 0x38)确认设备是否存在;
    • GPIO 检测失败:用gpio input 5gpio get 5手动读取电平,验证硬件连接。

四、总结

本文通过补充 U-Boot 屏幕检测代码(I2C/GPIO 两种方案),结合优化后的 overlay 加载脚本,形成了从 “硬件特征读取” 到 “设备树动态修改” 的完整闭环。该方案可实现多型号屏幕的全自动适配,无需人工干预,特别适合量产场景。实际应用中需根据屏幕硬件手册调整检测逻辑(如 ID 值、GPIO 引脚)和 overlay 配置(时序参数、初始化指令),确保兼容性与稳定性。

在嵌入式Linux下,设备树(device tree)用来描述硬件平台的各种资源,Linux内核在启动过程中,会解析设备树,获取各种硬件资源来初始化硬件。设备树overlay功能是指可以在系统运行期间动态修改设备树。一般情况下,如上图所示,设备树经过DTC编译器编译为二进制的hello.dtb文件,加载到内存,随Linux内核一起启动后,一般就无法更改了。如果我们想修改设备树,需要修改hello.dts文件文件,重新编译成二进制文件:hello.dtb,然后重新启动内核,重新解析。有了设备树overlay功能,省去了设备树的重新编译和内核重启,我们可以直接编写一个设备树插件:overlay.dts,编译成overlay.dtbo后,直接给设备树“打补丁”,在运行期间就可以动态添加节点、修改节点...设备树overlay功能,在很多场合都会用得到,会让我们的开发更加方便:外界插拔设备,无法在设备树中预先描述:耳机树莓派 + FPGA开发板基于I2C的温度传感器管脚的重新配置:PIN multiplexing修改bootcmd、分区...设备树overlay功能,目前还没有加入到内核mainline(linux-5.10.x),但目前有些开发板和配套的BSP已经支持了,支持在系统运行期间动态修改设备树文件。如果你手头的开发板或内核平台还没有支持device tree overlay,可以学习本期课程,学习内核中设备树overlay的实现原理,如何给内核打补丁,使内核支持设备树overlay功能。有了本期课程的学习基础,明白了设备树overlay的实现原理和运行机制,你就可以尝试在自己的开发板平台上实现这个功能了。本期课程的主要内容如下:在开发板上如何实现设备树overlay功能Configfs文件系统的配置挂载Configfs编程接口如何编写设备树 overlay插件设备树 overlay的编译和运行设备树overlay运行机制分析本期课程适合哪些人学习:嵌入式驱动工程师嵌入式BSP工程师嵌入式软件工程师想从事嵌入式开发的同学全网首家讲解设备树overlay的视频教程。   
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值