保姆级教程:手把手教你修改RK3588的U-Boot,提前点亮LED或控制风扇(GPIO配置篇)
在嵌入式开发中,能够掌握U-Boot阶段的硬件控制能力,意味着你可以在系统启动的最早期就对硬件进行干预。想象一下,当你的RK3588开发板刚上电,U-Boot还在加载时,就能通过LED灯的状态来指示启动进度,或者提前启动散热风扇为即将运行的高负载任务做好准备——这种"掌控全局"的感觉,正是嵌入式开发的魅力所在。
本文将带你从零开始,完成在RK3588开发板上通过U-Boot控制GPIO的完整流程。不同于简单的代码片段展示,我们会用详细的步骤截图、命令行操作和实际效果演示,确保即使是没有Rockchip平台开发经验的新手也能轻松跟上。我们将重点解决三个核心问题:如何理解RK3588特殊的GPIO编号规则、如何定位并修改U-Boot的关键代码、以及如何验证修改是否生效。
1. 理解RK3588的GPIO编号体系
在开始修改代码前,我们必须先搞清楚RK3588的GPIO编号规则。与常见的简单数字编号不同,Rockchip芯片采用了一种分层编号系统,这让很多刚接触该平台的开发者感到困惑。
RK3588的GPIO控制器分为5个bank(GPIO0-GPIO4),每个bank包含4个group(A-D),每个group又有8个pin(0-7)。这种结构可以用GPIO<bank>_<group><pin>的形式表示,例如GPIO4_D5表示:
- Bank编号:4(GPIO4)
- Group编号:3(D对应3,A=0,B=1,C=2,D=3)
- Pin编号:5(D5中的5)
要将这种表示法转换为U-Boot中使用的绝对编号,需要经过两步计算:
- 计算group内的相对编号:
number = group * 8 + pin - 计算全局绝对编号:
global_number = bank * 32 + number
以GPIO4_D5为例:
number = 3 * 8 + 5 = 29 global_number = 4 * 32 + 29 = 157为了方便查阅,以下是RK3588常用GPIO的编号对照表:
| GPIO名称 | Bank | Group | Pin | 绝对编号 |
|---|---|---|---|---|
| GPIO0_A0 | 0 | 0 | 0 | 0 |
| GPIO0_A7 | 0 | 0 | 7 | 7 |
| GPIO0_B3 | 0 | 1 | 3 | 11 |
| GPIO4_D5 | 4 | 3 | 5 | 157 |
| GPIO4_C6 | 4 | 2 | 6 | 150 |
提示:开发板原理图上标注的GPIO名称通常采用
GPIO<bank>_<group><pin>格式,务必先确认你要控制的GPIO在原理图上的准确名称。
2. 准备开发环境与源码
在修改U-Boot前,我们需要准备好开发环境和RK3588的Linux SDK源码。以下是详细步骤:
安装交叉编译工具链:
sudo apt-get install gcc-aarch64-linux-gnu获取Linux SDK源码:
git clone https://github.com/rockchip-linux/rkbin.git git clone https://github.com/rockchip-linux/u-boot.git配置环境变量:
export CROSS_COMPILE=aarch64-linux-gnu- export ARCH=arm64确认开发板配置: 进入u-boot目录,查看RK3588的默认配置文件:
cd u-boot ls configs/rk3588_*defconfig通常会看到类似
rk3588_defconfig或rk3588_evb_defconfig的文件,这取决于你的具体开发板型号。
3. 定位并修改U-Boot的关键代码
RK3588的GPIO初始化代码位于U-Boot的board.c文件中。按照以下步骤进行修改:
找到目标文件:
u-boot/arch/arm/mach-rockchip/board.c定位
rk_board_init函数: 这个函数是U-Boot初始化阶段会调用的一个弱符号函数,正是我们添加GPIO控制代码的理想位置。添加GPIO控制代码: 以下是控制GPIO4_D5(绝对编号157)的完整示例:
__weak int rk_board_init(void) { printf("Initializing custom GPIOs...\n"); // 控制GPIO4_D5(绝对编号157) if (gpio_request(157, "sys_led") == 0) { gpio_direction_output(157, 1); // 设置为输出模式,初始高电平 gpio_set_value(157, 0); // 输出低电平,点亮LED } else { printf("Failed to request GPIO 157\n"); } return 0; }代码解析:
gpio_request:申请GPIO使用权,防止冲突gpio_direction_output:设置GPIO为输出模式gpio_set_value:设置输出电平(0=低,1=高)
添加头文件(如果IDE提示未定义): 在文件顶部添加:
#include <asm/gpio.h> #include <common.h>
4. 编译与烧写U-Boot
修改完成后,需要重新编译U-Boot并烧写到开发板:
配置编译选项:
make rk3588_defconfig启动编译:
make -j$(nproc)编译完成后,会在u-boot目录下生成
u-boot.bin文件。烧写到开发板: 使用Rockchip提供的
upgrade_tool工具进行烧写:sudo upgrade_tool ul u-boot.bin或者通过TF卡启动方式更新:
sudo dd if=u-boot.bin of=/dev/sdX seek=64(将
/dev/sdX替换为你的TF卡设备名)
5. 验证GPIO控制效果
烧写完成后,给开发板上电,可以通过以下方式验证GPIO控制是否生效:
观察LED状态: 如果你控制的是LED连接的GPIO,上电后应该能立即看到LED状态变化。
使用万用表测量: 将万用表调到电压档,测量GPIO引脚对地电压:
- 高电平:约3.3V
- 低电平:约0V
U-Boot命令行验证: 如果开发板支持U-Boot命令行,可以尝试:
gpio status -a这个命令会列出所有GPIO的状态,找到你控制的GPIO编号确认其状态。
6. 进阶技巧与故障排除
在实际项目中,你可能会遇到各种特殊情况。以下是几个常见问题的解决方案:
GPIO无法控制:
- 检查原理图确认GPIO没有被其他功能复用(如I2C、SPI等)
- 使用
gpio_request的返回值判断是否申请成功 - 确认开发板的硬件版本和GPIO连接方式
需要控制多个GPIO:
// 控制LED和风扇的示例 #define SYS_LED_GPIO 157 #define FAN_CTRL_GPIO 158 __weak int rk_board_init(void) { // 初始化系统LED if (gpio_request(SYS_LED_GPIO, "sys_led") == 0) { gpio_direction_output(SYS_LED_GPIO, 0); } // 初始化风扇控制 if (gpio_request(FAN_CTRL_GPIO, "fan_ctrl") == 0) { gpio_direction_output(FAN_CTRL_GPIO, 1); // 默认关闭风扇 } return 0; }动态控制GPIO: 如果需要根据条件动态控制GPIO,可以在U-Boot命令行中添加自定义命令:
static int do_led_ctrl(cmd_tbl_t *cmdtp, int flag, int argc, char *const argv[]) { if (argc != 2) return CMD_RET_USAGE; int value = simple_strtol(argv[1], NULL, 10); gpio_set_value(SYS_LED_GPIO, value); return CMD_RET_SUCCESS; } U_BOOT_CMD( led, 2, 1, do_led_ctrl, "Control system LED", "<0|1> - Set LED off(0) or on(1)" );编译烧写后,在U-Boot命令行中可以输入:
led 1 # 点亮LED led 0 # 关闭LED
7. 实际应用场景示例
让我们看一个实际项目中非常有用的应用场景:通过LED指示启动阶段。
__weak int rk_board_init(void) { // 初始化阶段LED快速闪烁 for (int i = 0; i < 5; i++) { gpio_request(SYS_LED_GPIO, "sys_led"); gpio_direction_output(SYS_LED_GPIO, 1); gpio_set_value(SYS_LED_GPIO, 0); mdelay(100); gpio_set_value(SYS_LED_GPIO, 1); mdelay(100); gpio_free(SYS_LED_GPIO); } return 0; } __weak int last_stage_init(void) { // 系统即将启动内核时,LED常亮 gpio_request(SYS_LED_GPIO, "sys_led"); gpio_direction_output(SYS_LED_GPIO, 1); gpio_set_value(SYS_LED_GPIO, 0); return 0; }这段代码实现了:
- 在U-Boot早期初始化阶段,LED快速闪烁5次
- 在即将启动内核前,LED变为常亮
- 这样通过LED的行为就能直观判断系统启动到了哪个阶段
注意:
mdelay函数需要包含#include <linux/delay.h>头文件,且在某些U-Boot版本中可能需要使用udelay代替。