3. 实现Bootloader的基本框架

实现Bootloader的基本框架 学习卡片

任务目标

本案例实现一个运行于蓝牙芯片(以Nordic nRF52832为例)上的Bootloader最小框架。该框架完成芯片上电后的启动流程:初始化基础硬件、检测是否有有效应用固件、若无有效应用则进入等待下载状态,若有则跳转至应用区执行。Bootloader与应用程序共用同一Flash,通过预设地址分区实现共存。

Flash分区规划

区域起始地址大小内容
Bootloader区0x000000000xC000 (48KB)本案例代码
应用区0x0000C0000x34000 (208KB)用户应用程序
应用信息区0x0007F0000x1000 (4KB)应用有效性标志(Magic Number)

核心实现

以下代码基于nRF5 SDK 17.1.0,使用S132 SoftDevice(占用0x00000000起始的Flash,实际Bootloader起始地址需后移至0x0001F000,此处为清晰展示框架逻辑,简化了SoftDevice占用情况,实际工程中需根据链接脚本调整起始地址)。

// bootloader_main.c - Bootloader最小框架
#include <stdint.h>
#include <stdbool.h>
#include "nrf.h"
#include "nrf_gpio.h"
#include "nrf_delay.h"

// Flash分区地址定义
#define APP_CODE_START_ADDR     0x0000C000u
#define APP_INFO_ADDR           0x0007F000u
#define APP_MAGIC_NUMBER        0xA5A5A5A5u

// 应用信息结构(存储于Flash末尾)
typedef struct {
    uint32_t magic;
    uint32_t app_size;
    uint32_t checksum;
} app_info_t;

// 跳转至应用区
static void bootloader_jump_to_app(uint32_t app_addr) {
    // 关闭全局中断
    __disable_irq();
    
    // 设置主栈指针为应用向量表第一个字
    uint32_t app_stack_ptr = *(volatile uint32_t *)app_addr;
    
    // 获取应用复位向量
    uint32_t app_reset_handler = *(volatile uint32_t *)(app_addr + 4);
    
    // 验证栈指针在RAM范围内(nRF52832 RAM: 0x20000000-0x20010000)
    if ((app_stack_ptr < 0x20000000u) || (app_stack_ptr > 0x20010000u)) {
        return; // 栈指针非法,不跳转
    }
    
    // 设置新的栈指针
    __set_MSP(app_stack_ptr);
    
    // 跳转至应用复位向量
    ((void (*)(void))app_reset_handler)();
}

// 检查应用是否有效
static bool app_is_valid(void) {
    volatile app_info_t *info = (volatile app_info_t *)APP_INFO_ADDR;
    
    // 检查Magic Number
    if (info->magic != APP_MAGIC_NUMBER) {
        return false;
    }
    
    // 检查应用大小是否合理(不超过应用区大小)
    if (info->app_size > 0x34000u) {
        return false;
    }
    
    // 简单校验和验证(对应用区逐字累加)
    uint32_t sum = 0;
    uint32_t *app_ptr = (uint32_t *)APP_CODE_START_ADDR;
    uint32_t word_count = info->app_size / 4;
    
    for (uint32_t i = 0; i < word_count; i++) {
        sum += app_ptr[i];
    }
    
    return (sum == info->checksum);
}

// 等待下载模式(此处仅做LED指示,实际工程中可在此处初始化UART/DFU服务)
static void enter_wait_download_mode(void) {
    // 配置LED引脚(假设P0.18连接LED)
    nrf_gpio_cfg_output(18);
    
    while (1) {
        nrf_gpio_pin_toggle(18);
        nrf_delay_ms(200);  // 500ms周期闪烁,指示等待下载
    }
}

int main(void) {
    // 基础时钟配置(使用内部RC,实际工程建议启用外部晶振)
    NRF_CLOCK->TASKS_HFCLKSTART = 1;
    
    // 检查应用有效性
    if (app_is_valid()) {
        // 应用有效,跳转执行
        bootloader_jump_to_app(APP_CODE_START_ADDR);
    }
    
    // 应用无效,进入等待下载模式
    enter_wait_download_mode();
    
    // 不应到达此处
    while (1) { }
}

链接脚本配置(bootloader.ld)

/* 最小Bootloader链接脚本 - 仅保留关键段 */
MEMORY
{
  FLASH (rx) : ORIGIN = 0x00000000, LENGTH = 0xC000
  RAM (rwx)  : ORIGIN = 0x20000000, LENGTH = 0x4000
}

SECTIONS
{
  .text :
  {
    KEEP(*(.isr_vector))
    *(.text*)
    *(.rodata*)
  } > FLASH

  .data :
  {
    _sdata = .;
    *(.data*)
    _edata = .;
  } > RAM AT> FLASH

  .bss :
  {
    _sbss = .;
    *(.bss*)
    *(COMMON)
    _ebss = .;
  } > RAM
}

构建与烧录

  1. 环境依赖:GNU ARM Embedded Toolchain 9-2020-q2-update及以上版本,nRF5 SDK 17.1.0。
  2. 编译命令:
    arm-none-eabi-gcc -mcpu=cortex-m4 -mthumb -mabi=aapcs \
      -DNRF52832_XXAA -DNRF_SD_BLE_API_VERSION=6 \
      -I"path/to/sdk/modules/nrfx" -I"path/to/sdk/components/toolchain" \
      -Tbootloader.ld -nostdlib -ffunction-sections -fdata-sections \
      bootloader_main.c -o bootloader.elf
    arm-none-eabi-objcopy -O ihex bootloader.elf bootloader.hex
  3. 烧录:使用nrfjprog或J-Flash将bootloader.hex烧录至芯片起始地址。

验证方法

通过条件:烧录后复位芯片,若应用区无有效应用(Flash全0xFF或Magic Number不匹配),LED以500ms周期闪烁,表明Bootloader正确进入等待下载模式。若预先在0x0000C000处烧录一个有效应用(含正确向量表和Magic Number),复位后应观察到应用开始运行(如LED常亮或应用自定义行为),表明跳转成功。

测试建议:使用nrfjprog --memrd 0x0007F000 查看应用信息区内容,确认Magic Number写入正确。可通过nrfjprog --memwr 0x0007F000 0xA5A5A5A5 手动写入Magic Number来模拟有效应用(需同时保证校验和匹配)。

常见问题

  • 跳转后HardFault:检查应用向量表首字是否为合法RAM地址,确认应用编译时链接地址为0x0000C000。
  • 校验和不匹配:确认应用烧录后实际大小与app_info中记录一致,注意Flash写入需按字对齐。
  • SoftDevice冲突:若使用SoftDevice,Bootloader起始地址需后移至SoftDevice结束处(如0x0001F000),且中断向量重映射需额外处理。