STM32F4的CAN升级方案:包含Bootloader源代码、测试用App源代码及上位机可执行文件

张开发
2026/4/5 14:44:28 15 分钟阅读

分享文章

STM32F4的CAN升级方案:包含Bootloader源代码、测试用App源代码及上位机可执行文件
STM32F4的CAN升级方案 bootloader源代码对应测试用app源代码都是keil工程代码有备注也有使用说明。 带对应上位机可执行文件。 上位机vs2013开发(默认exe源代码需要额外拿)——一份写给“要在明天就把 IAP 合并进自己工程”的工程师的逐行说明书0. 阅读提示本文面向“已经拿到源码、Keil 工程能顺利编译但还没吃透每一行到底在干什么”的开发者。所有代码片段均在Keil 5.24 STM32F407VG1 MB Flash环境下调通未使用 HAL 的“黑盒子”——所有外设寄存器操作均给出寄存器级解释保证“复制即可用”。文章顺序 芯片上电后实际执行顺序跟着代码走不跳步。每一小节末尾给出“如果我要把这段代码搬到 STM32F429/103/MP157 需要注意什么”——方便横向迁移。1. 镜像布局决定了后面所有地址源码里并没有用scatter-loading花哨玩法而是用最朴素的“双工程”思路区域起始地址长度谁负责说明Bootloader0x0800 000032 KB本工程永远不会被擦除标志扇区0x0800 78004 KBBootloader只写一次0x7856 4312Application0x0800 80001 MB - 32 KB上位机流可被整片擦除**代码级证据**打开 bootloader\MDK-ARM\bootloader.sct 能看到LR_IROM1 0x08000000 0x00008000 { ; 32 KBER_IROM1 0x08000000 0x00008000 { *.o (RESET, First)RW_IRAM1 0x20000000 0x00020000 { ; 128 KB SRAM}而 APP 工程 app.sct 则是LR_IROM1 0x08008000 0x000F8000 { ; 1 MB - 32 KB结论链接脚本已经把“谁住哪里”写死Bootloader 绝不越界到 0x0800 8000 之后APP 也绝不踩 0x0800 7FFF 之前。以后想给 Bootloader 增加到 64 KB只需把0x00008000改成0x00010000两边工程同步改再定义新的APPSTARTADDR即可。2. 上电第一条指令Reset_Handler文件bootloader\startup_stm32f407xx.sST 标准启动文件作者未做任何改动。关键代码Reset_Handler PROC EXPORT Reset_Handler [WEAK] IMPORT SystemInit IMPORT __main LDR R0, SystemInit BLX R0 LDR R0, __main BX R0 ENDPSystemInit()把时钟拉到 168 MHz但 Bootloader 马上又把时钟降到 24 MHz原因见第 5 节 Flash 擦写。随后进入main()——C 世界第一条栈帧就是下面这个函数int main(void) { HAL_Init(); // 仅初始化 Systick 1 ms 中断 bsp_can_init(); // 寄存器级代码见下 jump_check(); // 决定去留 bootloader_loop(); // 永不返回 }3. CAN 寄存器级初始化bsp_can.cHAL 库代码太“厚”作者直接裸写寄存器方便读者一眼看出每个 bit 含义void bsp_can_init(void) { /* 1. 使能时钟 */ RCC-AHB1ENR | RCC_AHB1ENR_GPIOAEN; RCC-APB1ENR | RCC_APB1ENR_CAN1EN; /* 2. PA11/PA12 复用 AF9 */ GPIOA-MODER ~(GPIO_MODER_MODER11 | GPIO_MODER_MODER12); GPIOA-MODER | (GPIO_MODER_MODER11_AF | GPIO_MODER_MODER12_AF); GPIOA-AFR[1] | (9 GPIO_AFRH_AFRH3_Pos) // PA11 | (9 GPIO_AFRH_AFRH4_Pos); // PA12 /* 3. CAN 位时序 500 kbps 42 MHz APB1见时钟树 */ CAN1-MCR CAN_MCR_INRQ; // 请求初始化 while ((CAN1-MSR CAN_MSR_INAK) 0); CAN1-BTR (6 CAN_BTR_BRP_Pos) // 42 MHz / (61) 6 MHz | (13 CAN_BTR_TS1_Pos) // 13 2 15 Tq | (2 CAN_BTR_TS2_Pos) // 2 Tq | (1 CAN_BTR_SJW_Pos); // 1 Tq /* 采样点 (113)/(1132) 87.5 % */ /* 4. 过滤器 —— 只接收扩展帧 ID 高 11bit 0x100/0x101/0x102/0x103 */ CAN1-FMR | CAN_FMR_FINIT; CAN1-FS1R | CAN_FS1R_FSC0; // 32-bit scale CAN1-FM1R | CAN_FM1R_FBM0; // ID mask mode CAN1-FFA1R ~CAN_FFA1R_FFA0; // 关联 FIFO0 CAN1-sFilterRegister[0].FR1 0x10021; // 期望 ID CAN1-sFilterRegister[0].FR2 0x1FF21; // 掩码 CAN1-FA1R | CAN_FA1R_FACT0; // 激活 CAN1-FMR ~CAN_FMR_FINIT; /* 5. 退出休眠开启 FIFO0 中断 */ CAN1-MCR ~CAN_MCR_INRQ; while ((CAN1-MSR CAN_MSR_INAK) ! 0); CAN1-IER | CAN_IER_FMPIE0; // FIFO0 消息 pending 中断 NVIC_EnableIRQ(CAN1_RX0_IRQn); }时钟树前提SystemInit()把 APB1 拉到 42 MHz168 MHz / 4所以 BRP6 得到 6 MHz 时间量子。过滤器只放行 4 个命令帧其余全部硬件过滤掉减轻 CPU 99 % 的负担。如果想把波特率改成 1 Mbps只需把 BRP 改成 342 / 4 10.5 MHz10.5 / (1132) 656 kHz 不满足正确做法是提高 APB1 到 54 MHzF429 可以或者减少 Tq 到 12。4. 跳转还是等待——jump_check() 的 8 行代码typedef void (*pAppFn)(void); void jump_check(void) { uint32_t *pFlag (uint32_t *)0x08007800; if (*pFlag 0x78564312) { uint32_t stack *(uint32_t *)0x08008000; uint32_t pc *(uint32_t *)0x08008004; if ((stack 0x2FF00000) 0x20000000 // RAM 区域合法性 (pc 0xFF000000) 0x08000000) // Flash 区域合法性 { pAppFn app (pAppFn)pc; __set_MSP(stack); // 重置主栈 SCB-VTOR 0x08008000; // 向量表重定位 __disable_irq(); // 关闭 Bootloader 中断 app(); // 永不返回 } } }为什么先检查 stack 0x2FF00000防止上位机下发了一个“半扇区”的 bin导致 0x0800 8004 处的值是 0xFFFFFFFF一旦set_MSP(0xFFFFFFFF)会直接进 HardFault。VTOR 寄存器在 Cortex-M3/M4 里可任意重定向向量表但必须是 512 字节对齐——0x0800 8000 刚好满足。disable_irq()并不关闭 SysTick只是提升优先级到 0保证 APP 拿到干净的 NVIC 状态。5. Flash 自举——为什么要把 HCLK 降到 24 MHzSTM32F4 参考手册明确当 VDD 2.7 ~ 3.6 V 时Flash 写操作最大频率 24 MHz。Bootloader 在擦除/编程前动态改时钟static void flash_unlock_and_downclock(void) { /* 1. 打开 Flash 控制器访问 */ FLASH-KEYR 0x45670123; FLASH-KEYR 0xCDEF89AB; /* 2. 把 HCLK 降到 24 MHz保持 SYSCLK 168 M只改 AHB 分频 */ RCC-CFGR ~RCC_CFGR_HPRE; // 清除 AHB 预分频 RCC-CFGR | RCC_CFGR_HPRE_DIV8; // 168 / 8 21 MHz 24 MHz while ((RCC-CFGR RCC_CFGR_HPRE) ! RCC_CFGR_HPRE_DIV8); /* 3. 等待 Flash 空闲 */ while (FLASH-SR FLASH_SR_BSY); }写完后再恢复RCC-CFGR ~RCC_CFGR_HPRE; // 回到 0 等待状态 168 M为什么不直接改 SYSCLK改 SYSCLK 要重新配置 PLL代码量大只改 AHB 分频 2 行寄存器搞定。如果芯片跑到 2.1 V 低电压必须降到 16 MHz——把 DIV8 改成 DIV10 即可。6. 擦除策略——“整页擦”而非“整片擦”STM32F407 一页 16 KBSector 0~11。Bootloader 采用最保守策略只擦除APP 会用到的扇区Bootloader 自身 Sector 0/1 永不擦除。擦除顺序从高地址 → 低地址防止中途断电导致 Bootloader 被毁。代码static const uint32_t sectors[] // 页地址表 { 0x08008000, // Sector 4 0x0800C000, // Sector 5 ... 0x080FC000, // Sector 11 }; void erase_app_region(void) { FLASH-CR | FLASH_CR_SER; // Sector erase for (int i 0; i ELEMENTS(sectors); i) { FLASH-CR ~FLASH_CR_SNB; FLASH-CR | (i4)FLASH_CR_SNB_Pos; // SNB 4..11 FLASH-CR | FLASH_CR_STRT; while (FLASH-SR FLASH_SR_BSY); } FLASH-CR ~FLASH_CR_SER; }SNB 4 起步正是因为 Sector 0~3 位于 0x0800 0000 ~ 0x0800 7FFF属于 Bootloader。如果 APP 超过 256 KB需要把sectors[]继续加到 Sector 12~1564 KB/128 KB 大页大页擦除时间 2 sUI 要做好进度条。7. 编程流程——双缓冲 校验上位机把 bin 切成240 Byte 块30 帧 × 8 Byte格式帧序号扩展 ID数据00x100目标地址 (4) 数据 0~31~290x100数据 4~243Bootloader 收到 30 帧后一次性写 256 ByteFlash 字宽 256 bit 32 ByteHAL 库强制 32 Byte 对齐写完回一个 ACKID 0x101上位机才发下一块。static uint8_t buf[256]; static uint32_t baseAddr; void ParseAndProgram(void) { static int frm 0; if (rx.Id 0x100) { if (rx.DLC 8) { if (frm 0) baseAddr *(uint32_t *)rx.Data; memcpy(buf[frm*8], rx.Data, 8); if (frm 32) // 256 Byte 到齐 { flash_program(baseAddr, buf, 256); send_ack(); frm 0; } } } }flashprogram()内部把 256 Byte 拆成 8 组双字64 bit使用FLASHCR_PG位轮询BSY。写完立即读回XOR 校验不匹配重试 3 次3 次失败直接跳到 Error_Handler() 亮红灯。8. 上位机最后一击——CMD_JUMP2APPPC 发完最后一包把全局 CRC16附在末尾Bootloader 校验通过后case CMD_JUMP2APP: if (crc computed_crc) { *(uint32_t *)0x08007800 0x78564312; // 写标志 NVIC_SystemReset(); // 软复位 } break;写标志后立即复位不等待 ACK防止 PC 超时。如果写标志前掉电标志仍是 0xFFFFFFFF下次上电继续停在 Bootloader天然防砖。9. APP 端必须做的 3 处改动代码级链接脚本把中断向量表搬到 0x0800 8000否则 SCB-VTOR 重定位后第一条指令就取错。生成 binKeil 自带fromelf --bin -o $LL.bin #L不加任何偏移。软复位回 Bootloader可选cvoid requestupgrade(void){(IO uint32t)0x08007800 0xFFFFFFFF;NVIC_SystemReset();}10. 如何把这套代码搬到 STM32F429/103/MP157芯片主要差异移植要点F429Flash 2 MB页大小 32 KB改sectors[]表Sector 12~23 为大页擦除时间 ×2UI 进度条调慢。F103无 VTOR 寄存器必须把 Bootloader 放到System Memory或者RAM 里跑再跳 APP或者 APP 前 4 Byte 放栈顶后面放复位向量用汇编 trampoline跳转。MP157A7 M4 双核M4 侧 Flash 只有 256 KB需要把镜像放到 A7 侧 DDRM4 用RCC_MCUFMR重映射协议不变擦写函数改成 Linux ioctl()。11. 结语——一行代码就能带走的结论如果你只记住一句话STM32F4的CAN升级方案 bootloader源代码对应测试用app源代码都是keil工程代码有备注也有使用说明。 带对应上位机可执行文件。 上位机vs2013开发(默认exe源代码需要额外拿)“把 0x0800 7800 写成 0x78564312再把栈顶和 PC 搬到 0x0800 8000STM32F4 的 IAP 就活了。”其余 2 000 行代码只是让这句话在 CAN 总线上、24 MHz 时钟下、87.5 % 采样点、16 KB 扇区、双缓冲、CRC16 的约束下——不掉电、不砖机、不跑飞、不退速地跑起来。

更多文章