4、核心参数详解(上):启动源选择、时钟频率配置、调试串口配置

各位好,欢迎来到第四章。这一章我们开始啃硬骨头——核心参数配置。

说实话,BootROM 的参数配置,是整个系统启动的基石。你想想看,芯片上电后第一件事就是读这些参数。要是配错了,板子可能连灯都不闪一下。我这些年调试过的板子,少说也有几十款,至少有一半的“点不亮”问题,最后都追到了这几个参数头上。

今天我们先聊三个最关键的:启动源选择、时钟频率配置、调试串口配置。它们决定了你的芯片从哪里启动、跑多快、以及怎么跟你“说话”。

4.1 启动源选择(Boot Source)

启动源,说白了就是告诉 BootROM:“嘿,你去哪儿把程序给我搬进来?”

常见的启动源有这么几种:

  • NOR Flash —— 支持 XIP(片上执行),启动最快,但容量小、贵
  • NAND Flash —— 容量大、便宜,但需要搬移到 RAM 里跑
  • eMMC / SD 卡 —— 消费电子标配,协议复杂一点
  • SPI NOR / SPI NAND —— 引脚少,适合小封装芯片
  • USB / UART —— 工厂烧录或调试用,平时不选这个

我个人习惯,在设计阶段就把启动源的优先级想清楚。比如产品量产用 eMMC,但开发阶段可能先用 SPI NOR 调试。这时候 BootROM 的启动源选择逻辑就很重要了。

核心要点:启动源选择通常由硬件 Pin 脚或 OTP(一次性可编程)区域决定。BootROM 会按照预设的优先级依次尝试,直到某个启动源返回有效的启动代码。

举个例子,某款 SoC 的启动源配置寄存器长这样:

// 伪代码示例:启动源选择寄存器
#define BOOT_SOURCE_REG  0x1E000004

#define BOOT_SOURCE_NOR     0x00
#define BOOT_SOURCE_NAND    0x01
#define BOOT_SOURCE_EMMC    0x02
#define BOOT_SOURCE_SPI     0x03
#define BOOT_SOURCE_UART    0x04

// 读取当前启动源
uint32_t boot_src = readl(BOOT_SOURCE_REG) & 0x07;

switch (boot_src) {
    case BOOT_SOURCE_NOR:
        // 直接从 NOR Flash 执行
        break;
    case BOOT_SOURCE_NAND:
        // 加载 NAND 启动块到 SRAM
        break;
    // ... 其他情况
}

避坑指南:我曾经遇到过一块板子,明明焊了 eMMC,但死活起不来。查了两天才发现,是启动源选择 Pin 脚的上拉电阻虚焊了,导致 BootROM 一直去读 SPI Flash。嗯,硬件上的小问题,软件排查起来真要命。

4.2 时钟频率配置(Clock Divider)

时钟频率配置,说白了就是告诉芯片:“你跑多快?”

BootROM 阶段的时钟配置,通常只涉及几个关键时钟:

  • CPU 时钟 —— 核心频率,BootROM 阶段一般跑低频,省电且稳定
  • 总线时钟(AHB / APB) —— 外设通信的基础
  • 存储接口时钟 —— 比如 SPI 时钟、NAND 时钟、DDR 时钟
  • 调试串口时钟 —— 决定了串口波特率是否准确

你想想看,如果时钟配错了,串口打印出来的全是乱码,你怎么调试?我刚开始做 UEFI 时就被这个问题坑过——串口能收到数据,但全是“ÿÿÿÿ”这种乱码,折腾了半天才发现是时钟分频算错了。

我的经验:BootROM 阶段的时钟,建议先跑一个保守的低频值。等主程序加载起来后,再通过 PLL 动态调频。这样既保证了启动可靠性,又不影响后续性能。

来看一个实际的时钟分频配置代码:

// 时钟分频配置示例
#define CLK_DIV_CTRL    0x1E001000
#define CLK_DIV_CPU     0x00
#define CLK_DIV_AHB     0x04
#define CLK_DIV_APB     0x08

void clock_init(void) {
    uint32_t reg_val;
    
    // 设置 CPU 时钟分频:输入 24MHz,分频后 12MHz
    reg_val = readl(CLK_DIV_CTRL + CLK_DIV_CPU);
    reg_val &= ~0xFF;
    reg_val |= 0x01;  // 2 分频
    writel(reg_val, CLK_DIV_CTRL + CLK_DIV_CPU);
    
    // 设置 AHB 时钟分频:12MHz / 2 = 6MHz
    reg_val = readl(CLK_DIV_CTRL + CLK_DIV_AHB);
    reg_val &= ~0xFF;
    reg_val |= 0x01;  // 2 分频
    writel(reg_val, CLK_DIV_CTRL + CLK_DIV_AHB);
    
    // 设置 APB 时钟分频:6MHz / 2 = 3MHz
    reg_val = readl(CLK_DIV_CTRL + CLK_DIV_APB);
    reg_val &= ~0xFF;
    reg_val |= 0x01;  // 2 分频
    writel(reg_val, CLK_DIV_CTRL + CLK_DIV_APB);
}

警告:时钟配置时,一定要确认各模块的时钟依赖关系。比如 DDR 控制器初始化前,必须先配好 DDR 的参考时钟。我曾经见过有人先初始化 DDR 再配时钟,结果 DDR 训练直接失败——顺序搞反了。

4.3 调试串口配置(Debug UART)

调试串口,是嵌入式工程师的“眼睛”。没有它,你就像在黑屋子里修手表——全靠摸。

BootROM 阶段的串口配置,通常包括:

  • 波特率设置 —— 常见 115200 或 921600
  • 数据位 / 停止位 / 校验位 —— 8N1 是标配
  • 引脚复用(Pin Mux) —— 确认 UART 引脚没有被其他外设占用
  • 时钟源选择 —— 串口时钟来自哪个 PLL 或晶振

我个人习惯,在 BootROM 里把串口初始化做成一个独立函数。这样不管后续加载哪个阶段的代码,都能直接调用,保证调试信息不断。

来看一个典型的串口初始化代码:

// 调试串口初始化
#define UART_BASE       0x1C000000
#define UART_BAUD_REG   (UART_BASE + 0x00)
#define UART_CTRL_REG   (UART_BASE + 0x04)
#define UART_DATA_REG   (UART_BASE + 0x08)

void debug_uart_init(uint32_t baudrate) {
    uint32_t div;
    
    // 假设串口时钟为 24MHz
    div = 24000000 / (16 * baudrate);
    
    // 设置波特率
    writel(div, UART_BAUD_REG);
    
    // 配置 8N1:8 数据位,无校验,1 停止位
    writel(0x03, UART_CTRL_REG);  // 使能发送和接收
    
    // 输出一条测试信息
    debug_uart_putc('U');
    debug_uart_putc('A');
    debug_uart_putc('R');
    debug_uart_putc('T');
    debug_uart_putc(' ');
    debug_uart_putc('O');
    debug_uart_putc('K');
    debug_uart_putc('\n');
}

void debug_uart_putc(char c) {
    // 等待发送缓冲区空
    while (!(readl(UART_CTRL_REG) & 0x20));
    // 发送字符
    writel(c, UART_DATA_REG);
}

关键点:串口初始化成功后,一定要打印一条可见的标识信息。我习惯打印“UART OK”或者“Debug UART Ready”。这样一眼就能确认串口是否工作正常,不用去猜。

避坑指南:我曾经遇到过一个问题——串口能打印,但打印几行后就卡死了。查了半天,发现是串口中断没关,BootROM 阶段中断向量表还没初始化,一有中断进来就飞了。所以,BootROM 阶段的串口,建议用轮询模式,别开中断。

知识体系总览

这三个参数之间的关系,我用一张图来总结:

BootROM 核心参数配置流程 BootROM 启动 启动源选择 NOR / NAND / eMMC / SPI 时钟频率配置 CPU / AHB / APB / 外设 调试串口配置 波特率 / 引脚 / 时钟 硬件 Pin 脚采样 OTP 区域读取 优先级轮询 PLL 初始化 分频系数计算 时钟门控使能 引脚复用配置 波特率发生器 轮询模式输出 加载下一阶段启动代码

这张图展示了 BootROM 启动时的核心流程。先选启动源,再配时钟,最后开串口。顺序不能乱,依赖关系要理清。

好了,这一章就到这里。这三个参数是 BootROM 配置的基石,理解了它们,后续的 DDR 初始化、安全启动等内容就好办了。记住我上面说的那些坑,能帮你省下不少调试时间。

可可精品资料库