3. Bootloader 与启动流程:DSP 上电后到底在忙什么?

DSP 上电之后,到底发生了什么?很多刚入行的朋友以为,只要电源稳定,程序就自动跑起来了。其实没那么简单。DSP 内部有一套固定的启动流程,你得先搞清楚它,才能让代码老老实实地跑起来。

我最早做 C2000 系列移植时,就吃过这个亏。板子焊好,上电,示波器一看,IO 口没反应。查了半天,原来是 Boot 模式选错了,DSP 压根没从 Flash 启动。嗯,从那以后,我每次画板子都会先确认 Boot 引脚电平。

3.1 DSP 上电启动流程

DSP 上电后,大致会经历这么几个阶段:

  1. 上电复位:电源稳定后,POR(上电复位)电路释放复位信号。
  2. 硬件初始化:CPU 内部寄存器复位到默认值,PLL 开始锁定。
  3. Boot ROM 执行:DSP 从片内 Boot ROM 的 0x0000 地址开始执行固化代码。
  4. Boot 模式采样:Boot ROM 读取 GPIO 状态或 OTP 值,决定从哪种介质加载用户程序。
  5. 加载并跳转:将用户代码从外部介质(Flash、SPI EEPROM、I2C EEPROM 等)搬运到 RAM 或 Flash,然后跳转到入口地址。

核心要点:Boot ROM 是出厂固化的,你改不了。你能控制的,只有 Boot 模式的选择和二次 Bootloader 的编写。

我习惯把启动流程画成一张图,方便团队新人理解。你看下面这张:

DSP 上电启动流程图 DSP 上电启动流程 1. 上电复位 (POR) 2. 硬件初始化 3. Boot ROM 执行 4. Boot 模式采样 读取 GPIO/OTP 5. 加载并跳转 注意 Boot ROM 固化在芯片内 用户无法修改 只能控制 Boot 模式 常见 Boot 模式 SPI Boot / I2C Boot Parallel Boot / SCI Boot

3.2 SPI Boot 与 I2C Boot 模式选择

Boot 模式的选择,说白了就是告诉 DSP:「你的程序放在哪,用什么方式读进来」。最常见的两种外部 Boot 模式是 SPI 和 I2C。怎么选?我列个表,你一看就明白。

对比项 SPI Boot I2C Boot
通信速率 高(可达几十 MHz) 低(标准 100/400 kHz)
引脚数量 4 线(CS、CLK、MOSI、MISO) 2 线(SDA、SCL)
从设备寻址 片选 CS 独立选择 7 位/10 位地址
典型存储介质 SPI Flash(如 W25Q64) I2C EEPROM(如 AT24Cxx)
代码容量 大(MB 级) 小(KB 级,通常 ≤ 512KB)
适用场景 程序较大、需要快速启动 引脚受限、程序较小

我的建议:如果项目对启动速度有要求,或者代码超过 64KB,优先选 SPI Boot。I2C Boot 更适合那些引脚紧张、代码量不大的小系统。我在一个传感器节点项目里用过 I2C Boot,省了两个 GPIO,但启动时间多了将近 200ms——你得权衡。

选好模式后,硬件上要注意 Boot 引脚的配置。以 TMS320F28335 为例,Boot 模式由 GPIO84~GPIO87 的电平决定。我见过有人把这几个引脚悬空,结果 DSP 每次上电都随机进入不同模式——你说这调试起来得多崩溃?

避坑指南:Boot 引脚一定要用上拉或下拉电阻固定电平,不要依赖芯片内部弱上拉。内部弱上拉在复位期间可能不稳定,导致采样错误。我曾经在一个量产项目上吃过这个亏,100 块板子有 3 块启动失败,最后发现是 Boot 引脚焊接不良。

3.3 二次 Bootloader 编写要点

Boot ROM 里的加载程序通常只支持固定格式(比如特定的数据头、校验方式)。如果你的程序需要加密、压缩、或者从非标准地址加载,就得自己写二次 Bootloader。

说白了,二次 Bootloader 就是一段小代码,由 Boot ROM 加载到 RAM 里执行,然后它再去加载真正的应用程序。我习惯把它叫做「搬运工」。

3.3.1 二次 Bootloader 的核心任务

  • 初始化外设:配置 SPI/I2C 控制器时钟、引脚复用。
  • 读取应用程序:从外部存储器读取数据,支持校验(CRC 或累加和)。
  • 写入目标地址:将数据写入 Flash 或 RAM,注意擦除和编程时序。
  • 跳转执行:关闭中断、重设堆栈指针,跳转到应用程序入口。

3.3.2 编写时的关键细节

我整理了几个容易踩坑的地方,你写代码时多留个心眼:

  1. 堆栈设置:二次 Bootloader 运行在 RAM 里,堆栈指针必须重新初始化。我见过有人直接沿用 Boot ROM 的堆栈,结果跳转后栈空间不够,程序跑飞。
  2. 中断处理:在加载过程中,务必关闭所有中断。跳转前要清除中断标志,否则应用程序一启动就进中断,而中断向量表还没准备好——死循环。
  3. Flash 擦除时序:如果目标介质是 Flash,擦除操作需要时间。SPI Flash 擦除一个扇区通常要几百毫秒,你得加延时或查询状态寄存器,不能直接写。
  4. 数据校验:我习惯在应用程序末尾附加 4 字节 CRC32。二次 Bootloader 读取完数据后做校验,不通过就报错或重新加载。别问我为什么——有一次产线烧录器接触不良,100 片里有 5 片数据不全,全靠 CRC 拦住了。

核心原则:二次 Bootloader 要尽量精简,因为它占用的是 RAM 空间。加载完成后,它占用的 RAM 区域可以被应用程序覆盖——前提是你设计好链接脚本。

3.3.3 一个简化的二次 Bootloader 流程

下面这段伪代码展示了我常用的二次 Bootloader 逻辑。实际项目中你还需要处理时钟配置、看门狗喂狗等细节。

// 二次 Bootloader 主流程(伪代码)
void main(void) {
    // 1. 关闭看门狗,关闭全局中断
    WDT_Disable();
    DISABLE_INTERRUPTS();

    // 2. 初始化 SPI 外设(假设使用 SPI Boot)
    SPI_Init(SPI_BOOT_BAUDRATE);
    
    // 3. 从 SPI Flash 读取应用程序头部
    //    头部包含:起始地址、长度、CRC32
    BootHeader_t header;
    SPI_Read(APP_START_SECTOR, &header, sizeof(header));
    
    // 4. 校验头部合法性
    if (header.magic != BOOT_MAGIC) {
        ERROR_HANDLER();  // 非法头部,进入错误处理
    }
    
    // 5. 读取应用程序数据到 RAM 缓冲区
    uint8_t buffer[header.length];
    SPI_Read(APP_DATA_SECTOR, buffer, header.length);
    
    // 6. CRC 校验
    uint32_t calc_crc = CRC32_Calculate(buffer, header.length);
    if (calc_crc != header.crc32) {
        ERROR_HANDLER();  // CRC 不匹配,停止加载
    }
    
    // 7. 将数据写入目标 Flash 地址
    Flash_Erase(header.dest_addr, header.length);
    Flash_Write(header.dest_addr, buffer, header.length);
    
    // 8. 跳转前准备:关闭外设、重设堆栈
    SPI_Deinit();
    SET_STACK_POINTER(APP_STACK_ADDR);
    
    // 9. 跳转到应用程序入口
    JUMP_TO(header.entry_addr);
}

特别注意:跳转前一定要把用到的外设关干净。SPI 控制器如果不复位,应用程序初始化时可能读到残留的中断标志。我遇到过 SPI 中断在跳转后立刻触发,而应用程序还没配置中断向量表——直接进异常。

3.3.4 二次 Bootloader 的链接脚本

二次 Bootloader 本身也需要链接脚本。你要确保它被加载到正确的 RAM 地址,并且不占用应用程序的 RAM 空间。我一般这样划分:

区域 起始地址 大小 用途
Bootloader RAM 0x008000 4KB 二次 Bootloader 代码和数据
App 向量表 0x009000 256B 应用程序中断向量表
App 代码区 0x009100 剩余 RAM 应用程序代码和堆栈

你想想看,如果 Bootloader 和应用程序的 RAM 区域重叠了,加载时就会互相覆盖——这 bug 查起来可够你喝一壶的。

个人习惯:我会在二次 Bootloader 的末尾加一个跳转表,方便以后升级。跳转表里放三个函数指针:加载、校验、跳转。这样主程序也可以通过调用 Bootloader 的接口实现在线升级,不用每次都重新烧录。

嗯,关于 Bootloader 和启动流程,核心就是这些。记住:上电流程是硬件定死的,你改变不了;Boot 模式是你选的,别选错;二次 Bootloader 是你写的,要稳、要小、要可靠。把这三点拿捏住,DSP 启动这块就不会出大问题。