1. 构建最小Zephyr引导程序
任务目标
构建一个最小可用的Zephyr引导程序,实现上电后从固定地址加载并跳转到应用程序。引导程序与应用程序均基于Zephyr RTOS,通过链接脚本控制地址布局,使用sys_reboot或直接函数指针跳转完成切换。
环境依赖
- Zephyr SDK(v3.4+)及对应工具链
- 开发板:以
qemu_cortex_m3为例(无需真实硬件) - West 工作区已初始化
实现原理
引导程序本质是一个独立的Zephyr镜像,链接到Flash起始地址(如0x0),应用程序链接到偏移地址(如0x8000)。引导程序通过函数指针跳转到应用复位向量,并设置MSP(主栈指针)。关键点在于两个镜像的链接脚本需互不重叠。
关键完整代码
// boot.c - 最小引导程序
#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/linker/sections.h>
#include <stdint.h>
#define APP_FLASH_BASE 0x8000 // 应用程序起始地址
// 应用程序向量表结构(与ARM Cortex-M一致)
typedef void (*vector_entry_t)(void);
typedef struct {
uint32_t initial_sp;
vector_entry_t reset;
} app_vector_table_t;
void jump_to_app(void)
{
// 关闭中断,避免跳转过程中被干扰
__disable_irq();
// 获取应用程序向量表
const app_vector_table_t *vt = (const app_vector_table_t *)APP_FLASH_BASE;
// 验证栈指针和复位向量合法性(简单检查)
if (vt->initial_sp == 0xFFFFFFFF || vt->reset == NULL) {
return; // 非法向量表,停留在引导程序
}
// 设置主栈指针并跳转
__set_MSP(vt->initial_sp);
vt->reset();
// 不应返回
while (1) {}
}
void main(void)
{
printk("Bootloader: jumping to app at 0x%x\n", APP_FLASH_BASE);
jump_to_app();
}
链接脚本配置
在 prj.conf 中设置 CONFIG_FLASH_LOAD_OFFSET=0(引导程序从0x0开始)。应用程序工程需设置 CONFIG_FLASH_LOAD_OFFSET=0x8000。两个工程使用不同的 board.overlay 或直接修改 dts.overlay 定义flash分区。
构建与运行
# 构建引导程序
west build -b qemu_cortex_m3 -d build_boot . -p
# 构建应用程序(需单独工程,设置FLASH_LOAD_OFFSET=0x8000)
# 假设应用工程在 ../app 目录
west build -b qemu_cortex_m3 -d build_app ../app -p
# 合并镜像(引导程序 + 应用程序)
west sign -d build_boot --toolpath /path/to/zephyr/sdk -B build_app/zephyr/zephyr.bin -o merged.bin
# 运行合并镜像
qemu-system-arm -machine mps2-an385 -cpu cortex-m3 -nographic -device loader,file=merged.bin
验证方法
- 构建无错误,生成
merged.bin大小不超过Flash容量。 - 运行QEMU后,串口输出
Bootloader: jumping to app at 0x8000,随后应用程序的启动日志出现(如Hello from app)。 - 若跳转失败,程序会卡在引导程序的
while(1)循环,无应用日志输出。
常见问题
- 跳转后HardFault:检查应用程序的
CONFIG_FLASH_LOAD_OFFSET是否与引导程序中的APP_FLASH_BASE一致。 - 向量表未对齐:ARM要求向量表按128字节对齐,确认应用程序链接脚本中
.isr_vector段对齐。 - 中断未关闭:跳转前必须
__disable_irq(),否则跳转过程中可能触发中断导致异常。