3. 实现Bootloader的基本框架
任务目标
本案例实现一个运行于蓝牙芯片(以Nordic nRF52832为例)上的Bootloader最小框架。该框架完成芯片上电后的启动流程:初始化基础硬件、检测是否有有效应用固件、若无有效应用则进入等待下载状态,若有则跳转至应用区执行。Bootloader与应用程序共用同一Flash,通过预设地址分区实现共存。
Flash分区规划
| 区域 | 起始地址 | 大小 | 内容 |
|---|---|---|---|
| Bootloader区 | 0x00000000 | 0xC000 (48KB) | 本案例代码 |
| 应用区 | 0x0000C000 | 0x34000 (208KB) | 用户应用程序 |
| 应用信息区 | 0x0007F000 | 0x1000 (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
}
构建与烧录
- 环境依赖:GNU ARM Embedded Toolchain 9-2020-q2-update及以上版本,nRF5 SDK 17.1.0。
- 编译命令:
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 - 烧录:使用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),且中断向量重映射需额外处理。