2、Bootloader移植(U-Boot):U-Boot源码获取与目录结构分析、智能音箱板级配置创建、DDR初始化与时钟配置实战、网络启动与烧录验证

好,咱们进入第二章。Bootloader移植,说白了就是给智能音箱的CPU“接生”。没有它,内核就起不来。我这些年做过的项目,十有八九的启动问题都出在这一步。所以这一章,咱们把U-Boot从源码到跑起来,整个流程捋一遍。

2.1 U-Boot源码获取与目录结构分析

先说说源码怎么拿。U-Boot官方维护在Denx的Git仓库里。我个人习惯用稳定版本,比如当前智能音箱主流的v2023.04或v2024.01。别追最新版,容易踩坑。

git clone https://source.denx.de/u-boot/u-boot.git
cd u-boot
git checkout v2024.01

拿到代码后,别急着编译。先看看目录结构。我刚开始学的时候,面对一堆文件夹也懵。其实核心就这几个:

目录/文件 作用
arch/ CPU架构相关代码。智能音箱用ARM,所以重点看arch/arm/
board/ 板级初始化代码。每个板子一个文件夹,咱们要新建的就在这里
configs/ 板级默认配置。执行make xxx_defconfig时读取
drivers/ 各种外设驱动。串口、网卡、Flash都在这里
include/configs/ 板级头文件。定义DDR大小、时钟频率等宏
dts/ 设备树源文件。新版本U-Boot用设备树描述硬件
我的小技巧: 拿到一个新板子,先找一块参考板。比如你的智能音箱用全志H3芯片,那就先看board/sunxi/下的代码。复制粘贴再修改,比从头写快得多。

2.2 智能音箱板级配置创建

好,现在咱们来创建自己的板级配置。假设芯片是全志H3,板子叫“smart_speaker_v1”。

第一步:创建板级目录

mkdir board/sunxi/smart_speaker_v1

把参考板的board.c、Kconfig、Makefile复制过来。然后修改board.c里的板级初始化函数。嗯,这里要注意:智能音箱一般没有显示屏,所以LCD初始化可以删掉。但I2C和音频相关的GPIO一定要配好。

第二步:创建设备树

cp arch/arm/dts/sun8i-h3-orangepi-pc.dts arch/arm/dts/sun8i-h3-smart-speaker.dts

修改设备树,把网卡PHY地址、DDR大小、SPI Flash型号改成你的硬件参数。我曾经遇到过一个问题:设备树里DDR容量写错了,结果U-Boot死活只认一半内存。查了两天才发现是dts里少了个0。

第三步:创建默认配置

cp configs/orangepi_pc_defconfig configs/smart_speaker_v1_defconfig

修改这个文件,把CONFIG_TARGET_xxx改成你的板子名。还要加上智能音箱需要的功能:比如CONFIG_CMD_SOUND(音频测试命令)、CONFIG_USB_HOST(USB升级功能)。

2.3 DDR初始化与时钟配置实战

这是整个移植过程中最头疼的部分。DDR初始化一旦出错,板子直接“变砖”。我刚开始做的时候,就因为时序参数配错,连续烧了3块板子……后来学乖了,先用厂家提供的DDR训练工具。

DDR初始化流程:

  1. 配置DDR控制器时钟(PLL)
  2. 设置DDR时序参数(tRCD、tRP、tCL等)
  3. 执行ZQ校准
  4. 初始化DDR控制器寄存器
  5. 做内存读写测试验证

以全志H3为例,DDR初始化代码在board/sunxi/dram_sun8i_h3.c里。关键参数在dram_para结构体中:

static struct dram_para dram_para = {
    .clock = 408,          // DDR时钟频率,单位MHz
    .type = 3,             // DDR3
    .rank_num = 1,         // 单Rank
    .density = 2048,       // 每颗颗粒2Gb
    .io_width = 16,        // 16位总线
    .bus_width = 32,       // 32位总线(2颗颗粒)
    .cas_latency = 9,      // CL=9
    .tpr0 = 0x30926692,    // 时序参数
    .tpr1 = 0x1090,        // 时序参数
    .tpr2 = 0x1a0c8,       // 时序参数
};
避坑指南: 我曾经遇到过DDR初始化偶尔失败的问题。后来发现是电源上电时序不对。DDR的VDD和VDDQ必须严格按芯片手册的时序来,差1ms都不行。建议用示波器量一下。

时钟配置: 智能音箱的音频处理对时钟精度要求高。我建议PLL音频时钟用24.576MHz的整数倍,这样I2S的采样率才准。别问我怎么知道的——产品量产了才发现播放音乐变调,那叫一个酸爽。

2.4 网络启动与烧录验证

板子能跑起来了,接下来就是验证。网络启动是开发阶段最常用的方式,省得反复烧写Flash。

网络启动配置:

# 在U-Boot命令行设置
setenv ipaddr 192.168.1.100
setenv serverip 192.168.1.10
setenv netmask 255.255.255.0
setenv gatewayip 192.168.1.1
setenv bootcmd 'tftp 0x42000000 zImage; tftp 0x43000000 sun8i-h3-smart-speaker.dtb; bootz 0x42000000 - 0x43000000'
saveenv

然后PC端启动TFTP服务器,把内核和设备树放到tftp目录。上电后U-Boot会自动从网络加载内核。我第一次看到串口打印出“Starting kernel ...”时,激动得差点跳起来。

烧录验证: 网络启动没问题后,就要把U-Boot烧到SPI Flash里。智能音箱一般用SPI NOR Flash,容量16MB左右。

# 在U-Boot命令行
sf probe          # 检测SPI Flash
sf erase 0 0x100000  # 擦除前1MB
tftp 0x40000000 u-boot-sunxi-with-spl.bin
sf write 0x40000000 0 0x100000  # 写入U-Boot

烧完后断电重启。如果串口能正常打印U-Boot信息,说明移植成功。如果没反应……别慌,检查一下SPI Flash的CS引脚有没有接对。我有个同事就是CS脚虚焊,折腾了一整天。

验证清单:
  • 串口能正常输出U-Boot启动信息
  • DDR容量识别正确(用md命令读几个地址验证)
  • 网卡能ping通PC
  • SPI Flash读写正常
  • 能通过网络加载并启动内核

好了,这一章的内容就这些。U-Boot移植说白了就是三板斧:配DDR、调时钟、搞网络。把这几个搞定了,后面的内核移植就顺风顺水。下一章咱们聊聊Linux内核的移植,到时候见。