在这里插入图片描述

一、SYSCFG重映射

背景

  • Bootloader 位于 Flash 起始地址 0x08000000(映射到 0x00000000
  • 应用程序 位于 0x08004000(Bootloader 占用前 16KB)
  • 当应用程序运行时,如果发生中断,CPU 仍然会从 0x00000000(即 Bootloader 的向量表)读取中断处理函数地址
  • 这导致应用程序的中断无法正常工作

在 Cortex-M0 架构的微控制器上实现 Bootloader 时,由于Cortex-M0 不支持 VTOR(向量表偏移寄存器)。这意味着向量表必须固定在 0x00000000 地址,无法通过软件动态更改。

一般有两方式处理应用程序的中断映射问题:

  1. 硬件重映射(芯片带 SYSCFG 或类似 remap 寄存器)
    跳转前先把 APP 向量表(通常 48×4 字节)整个 memcpy 到 SRAM 起始 0x2000 0000
    调用库函数 HAL_SYSCFG_SetRemapMemory(SYSCFG_BOOT_SRAM) 把 SRAM 映射到 0x0000 0000
    再把 MSP、PC 设成 APP 的栈顶与 Reset_Handler,完成跳转
    → 此后任何中断 CPU 都会从 SRAM 里的新表取地址,自然进入 APP 的 ISR
  2. 软件转发(芯片无 remap 功能,或不想占 SRAM)
    Bootloader 的向量表保留在 Flash(0x0800 0000 起),但所有中断向量统一填同一个“转发函数” VectorRedirect()
    进入 APP 前把 APP 向量表基地址(如 0x08005000)记到全局变量 userAppStart
    VectorRedirect() 里用 SCB->ICSR & 0x1FF 拿到当前 IRQ 号,按 (userAppStart + 164 + irq4) 取出 APP 真正的 ISR 地址,再 BX 过去
    → 中断先跑到 Bootloader,但立即跳到 APP 的 ISR,对应用完全透明

本文使用方案1.

工作原理

  1. 向量表复制:将应用程序的向量表从 Flash(0x08004000)复制到 RAM 起始地址(0x20000000
  2. 硬件重映射:通过 SYSCFG 的 MEM_MODE 位设置为 0b11,将 RAM 映射到 0x00000000
  3. 中断处理:当发生中断时,CPU 从 0x00000000(现在映射到 RAM)读取向量表,从而使用应用程序的中断处理函数

实现细节

1. 向量表重映射函数

/**

  • @brief 跳转到主程序

  • @note 跳转前清理所有中断和外设状态,确保app启动时环境干净
    */
    void BootJump_ToApplication(void)
    {
    uint32_t app_stack_ptr = *((volatile uint32_t *)APPLICATION_START_ADDR);
    uint32_t app_reset_vector = *((volatile uint32_t *)(APPLICATION_START_ADDR + 4));

    /* 主程序复位处理函数指针 */
    void (app_reset_handler)(void) = (void ()(void))(app_reset_vector);

    /* 关闭所有中断(必须在清理外设之前) */
    __disable_irq();

    /* ========== 关键:清理中断相关残留状态 ========== /
    /
    按照最佳实践,在跳转前清理所有使用过的外设和中断状态 */

    /* 1. 清理UART1(如果bootloader使用了串口) /
    /
    1.1 直接操作外设寄存器关闭中断使能位 /
    UART1->IER &= ~(UART_IER_RX | UART_IER_TX); /
    禁用UART1接收和发送中断 */

    /* 1.2 清除NVIC的中断挂起位 */
    NVIC_ClearPendingIRQ(UART1_IRQn);

    /* 1.3 禁用NVIC中的对应中断 */
    NVIC_DisableIRQ(UART1_IRQn);

    /* 1.4 复位UART1外设(推荐,确保外设回到默认状态) /
    RCC->APB2RSTR |= RCC_APB2RSTR_UART1;
    volatile uint32_t delay = 100;
    while(delay–); /
    等待复位完成 /
    RCC->APB2RSTR &= ~RCC_APB2RSTR_UART1;
    delay = 100;
    while(delay–); /
    等待复位释放 */

    /* 1.5 关闭UART1外设时钟(彻底清理,app会重新初始化) /
    RCC->APB2ENR &= ~RCC_APB2ENR_UART1; /
    关闭UART1时钟 */

    /* 注意:不复位GPIOB,因为app会重新配置GPIO,复位可能导致时序问题 /
    /
    也不关闭GPIOB时钟,因为app会立即使用,避免时钟开关的时序问题 */

    /* 2. 清理其他可能使用的外设(如定时器、DMA等) /
    /
    如果bootloader使用了其他外设,在这里添加清理代码 /
    /
    例如:

    • TIM2->DIER &= ~TIM_DIER_UIE; // 禁用定时器中断
    • NVIC_DisableIRQ(TIM2_IRQn);
    • NVIC_ClearPendingIRQ(TIM2_IRQn);
    • RCC->APB1RSTR |= RCC_APB1RSTR_TIM2; // 复位定时器

    • */

    /* 2. 清理SysTick(如果bootloader使用了SysTick) /
    /
    禁用SysTick /
    SYSTICK_CTRL = 0;
    /
    清除SysTick计数器和重载值 /
    SYSTICK_LOAD = 0;
    SYSTICK_VAL = 0;
    /
    清除SysTick中断pending状态 */
    SCB_ICSR_ADDR |= SCB_ICSR_PENDSTCLR;

    /* 3. 关键:清除所有NVIC中断pending状态(确保没有残留的中断请求) /
    /
    Cortex-M0的NVIC只有ICER[0]和ICPR[0],支持32个中断 /
    /
    清除所有可能的中断pending状态 /
    NVIC->ICPR[0] = 0xFFFFFFFF; /
    清除前32个中断的pending状态 */

    /* 4. 禁用所有NVIC中断(确保app启动时中断系统干净) /
    NVIC->ICER[0] = 0xFFFFFFFF; /
    禁用前32个中断 */

    /* 5. 注意:MM32SPIN0280是Cortex-M0,不支持VTOR (向量表偏移寄存器)

    • 向量表始终在 0x00000000 (映射到 Flash 0x08000000)
    • 应用程序会在SystemInit()中使用SYSCFG硬件重映射将向量表复制到RAM
    • 并设置MEM_MODE使0x00000000映射到RAM,从而使用应用程序的中断处理函数
    • 因此这里不需要设置VTOR(Cortex-M0不支持VTOR)
      */

    /* 6. 确保内存屏障,保证所有清理操作完成后再跳转 /
    __DSB(); /
    数据同步屏障 /
    __ISB(); /
    指令同步屏障 */

    /* 设置主堆栈指针 */
    __set_MSP(app_stack_ptr);

    /* 跳转到主程序 */
    app_reset_handler();

    /* 不应该执行到这里 */
    while (1) {
    }
    }

2. 在 SystemInit() 中调用
void SystemInit(void)
{
    // 注意:从bootloader跳转过来时,时钟已经配置好了,不需要重置
    // SetSysClockToDefaultHSI();  // 禁用,避免重置bootloader配置的时钟
    // SetSysClock();              // 禁用,使用bootloader配置的时钟
    
    /* 向量表重映射到RAM(用于Bootloader场景)
     * 必须在启用中断之前完成
     */
    AppVectorTable_RemapToRAM();
}
3. 链接脚本配置

需要确保链接器不会在 0x20000000-0x20000100 范围内放置数据,为向量表预留空间:

LR_IROM1 0x08004000 0x0001C000 {    ; Flash区域
  ER_IROM1 0x08004000 0x0001C000 {
   *.o (RESET, +First)
   *(InRoot$$Sections)
   .ANY (+RO)
   .ANY (+XO)
   *.o (CHECKSUM, +Last)
  }

  ; RAM数据从0x20000100开始,预留0x20000000-0x20000100给向量表
  RW_IRAM1 0x20000100 0x00001F00 {   ; 0x2000 0100 – 0x2000 1FFF
   .ANY (+RW +ZI)
  }
  
  ; 注意:0x20000000-0x20000100 (256字节) 预留给向量表
  ; 向量表通过AppVectorTable_RemapToRAM()函数从Flash复制到这里
}
4. MEM_MODE 设置方式

正确写法

SYSCFG->CFGR = (SYSCFG->CFGR & ~SYSCFG_CFGR_MEM_MODE) /* 清除2位 /
| (0x3 << 0); /
写入0b11 */

5. 向量表地址

重要:向量表必须放在 RAM 起始地址(0x20000000),因为 SYSCFG 的 MEM_MODE 设置为 RAM 映射后,0x00000000 映射到 RAM 起始地址,而不是 RAM 的任意地址。

工作流程

  1. Bootloader 跳转:Bootloader 跳转到应用程序的 Reset_Handler
  2. SystemInit() 执行:调用 AppVectorTable_RemapToRAM()
    • 复制向量表到 RAM(0x20000000
    • 设置 SYSCFG MEM_MODE = 0b11
  3. main() 执行:初始化外设,启用中断
  4. 中断发生:CPU 从 0x00000000(映射到 RAM)读取向量表,跳转到应用程序的中断处理函数

二、应用程序串口指令

1. 指令格式

  • 编码:ASCII 字符串
  • 大小写:不敏感(自动转换为小写处理)
  • 结束符\r\n\n
  • 最大长度:64 字节(包括结束符)
  • 空格处理:自动去除首尾空格

2. 响应格式

成功响应
OK=[数据]\r\n
  • OK=:固定前缀
  • [数据]:可选的数据内容(某些命令会返回数据)
  • \r\n:换行符
错误响应
ERR=<错误码>[:错误描述]\r\n
  • ERR=:固定前缀
  • <错误码>:单个数字(0-9)
  • [:错误描述]:可选的错误描述(以冒号分隔)

3. 支持的指令

1. 版本查询指令
指令格式
ver

version
功能说明

查询应用程序的版本号信息。

响应示例
OK=v1.0.0\r\n

版本号格式:v<主版本号>.<次版本号>.<修订号>

实现细节
  • 版本号定义在 app_cmd.h 中:
    #define APP_VERSION_MAJOR    1
    #define APP_VERSION_MINOR    0
    #define APP_VERSION_PATCH    0
    

2. 进入升级模式指令
指令格式
ota
功能说明

进入 OTA(Over-The-Air)升级模式。

注意:当前版本中此指令已定义但功能未完全实现(标记为 TODO)。

响应示例
OK=\r\n
使用场景

用于触发固件升级流程,通常需要配合 Bootloader 使用。


3. 进入 Bootloader 模式指令
指令格式
boot
功能说明

设置 Boot Magic 值并重启系统,使系统在下次启动时进入 Bootloader 模式。

工作流程
  1. 写入 Boot Magic 值(0x5A5A)到备份寄存器(BKP->DR1)
  2. 发送成功响应
  3. 延时确保响应发送完成
  4. 执行系统复位(NVIC_SystemReset()
响应示例
OK=\r\n

(响应发送后系统会立即重启)

错误响应

如果写入 Boot Magic 失败:

ERR6:Write magic failed\r\n
实现细节
  • Boot Magic 存储在备份寄存器 BKP->DR1
  • Magic 值:0x5A5A(表示需要进入 Bootloader)
  • 系统重启后,Bootloader 会检查此值,如果匹配则进入升级模式

4. 退出升级模式指令
指令格式
exit
功能说明

清除 Boot Magic 值并重启系统,使系统在下次启动时正常运行应用程序。

工作流程
  1. 清除 Boot Magic 值(写入 0x0000 到备份寄存器)
  2. 发送成功响应
  3. 延时确保响应发送完成
  4. 执行系统复位(NVIC_SystemReset()
响应示例
OK=\r\n

(响应发送后系统会立即重启)

错误响应

如果清除 Boot Magic 失败:

ERR=7:Clear magic failed\r\n
使用场景
  • 在 Bootloader 升级模式下,完成固件升级后使用此指令退出升级模式
  • 确保系统下次启动时正常运行应用程序

错误码说明

错误码 描述 说明
4 Invalid command 无效的命令
6 Write magic failed 写入 Boot Magic 失败
7 Clear magic failed 清除 Boot Magic 失败

命令处理流程

接收流程

  1. 字符接收:通过 AppUART_ReceiveChar() 从串口接收缓冲区读取字符
  2. 缓冲区管理:使用 64 字节的命令缓冲区存储接收到的字符
  3. 命令识别:检测到换行符(\r\n)时,标记命令接收完成
  4. 溢出处理:如果缓冲区溢出,清空缓冲区并丢弃当前命令

处理流程

  1. 预处理
    • 去除首尾空格(trim_string()
    • 转换为小写(to_lowercase()
  2. 命令匹配:使用 strcmp() 匹配命令字符串
  3. 执行处理:调用对应的处理函数
  4. 发送响应:根据处理结果发送成功或错误响应

代码示例

void AppCMD_Process(void)
{
    char ch;
    
    /* 接收串口数据 */
    while (AppUART_ReceiveChar(&ch)) {
        if (ch == '\n' || ch == '\r') {
            if (cmd_index > 0) {
                cmd_buffer[cmd_index] = '\0';
                cmd_ready = 1;
            }
        }
        else {
            if (cmd_index < (CMD_BUFFER_SIZE - 1)) {
                cmd_buffer[cmd_index++] = ch;
            }
            else {
                /* 缓冲区溢出,清空缓冲区 */
                cmd_index = 0;
                cmd_ready = 0;
            }
        }
    }
    
    /* 处理已接收完成的命令 */
    if (cmd_ready) {
        execute_command(cmd_buffer);
        cmd_index = 0;
        cmd_ready = 0;
        memset(cmd_buffer, 0, sizeof(cmd_buffer));
    }
}

三、Boot Magic 机制

1. BKP寄存器说明:

BKP 寄存器属于备份域(Backup Domain),这是一个独立的电源域,具有以下特性:

  • 独立电源:备份域通常有独立的电源供应(VBAT),即使主电源断电,只要 VBAT 有电,备份域的数据就不会丢失
  • 独立复位:备份域有独立的复位控制,普通系统复位不会影响备份域

2. Magic 值

  • 0x5A5A:进入 Bootloader 模式
  • 0x0000:正常运行应用程序
  • 检查时机:系统复位后,Bootloader 启动时检查

3. 写入代码

u8 BootMagic_Write(u16 value)
{
    /* 确保备份域写保护已解除 */
    if ((PWR->CR & PWR_CR_DBP) == 0) {
        /* 如果写保护未解除,先解除 */
        PWR->CR |= PWR_CR_DBP;
        /* 等待写保护解除 */
        volatile u32 i;
        for (i = 0; i < 10; i++);
    }
    
    /* 直接写入BKP寄存器
     * BKP寄存器在系统复位后会保持值,这正是Boot Magic机制所需要的特性
     */
    BKP->DR1 = (u32)value;
    
    /* 确保写入完成(内存屏障) */
    __DSB();
    __ISB();
    
    /* 等待至少5个APB1总线周期(手册要求)
     * APB1通常运行在较低频率,这里使用循环延时确保时序要求
     */
    {
        volatile u32 i;
        for (i = 0; i < 20; i++);  /* 确保超过5个周期 */
    }
    
    /* 验证写入是否成功 */
    if ((BKP->DR1 & 0xFFFF) != value) {
        return 0;  /* 写入失败 */
    }
    
    return 1;
}

4. bootloader实现magic值判断和跳转等待握手

1. 启动时检查 Magic 值

Bootloader 启动后首先初始化 BKP 模块,读取备份寄存器中的 Magic 值:

/* 初始化boot magic模块 */
BootMagic_Init();

/* 读取并检查magic值 */
if (!BootMagic_CheckBootMode()) {
    /* Magic值不匹配,跳转到应用程序 */
    if (BootJump_IsApplicationValid()) {
        BootJump_ToApplication();
    }
}

2. 未收到握手时清除 BKP并跳转应用

   /* 10秒内未收到握手命令,清除BKP并跳转到应用程序 */
            BSP_LOG_Print("Handshake timeout, clearing BKP and jumping to application...");
            
            /* 清除BKP Magic值 */
            BootMagic_Clear();

运行效果:
在这里插入图片描述

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐