资讯动态

STM32F103 CAN Bootloader固件升级全解析:从协议设计到量产实践

发布时间:2026/10/4 10:13:40 来源:尧图企业网站定制
1. Bootloader项目到底解决什么问题1.1 为什么要做CAN Bootloader先交代一下背景。我这边做的产品是分布式的车载电控单元主控用的STM32F103系列节点之间走CAN总线。早期固件升级都是人工拿着ST-Link挨个开盖刷写一条产线几十个节点一遍刷下来少说一个多小时而且后装市场返修更头疼——设备已经封在机壳里有些还打了胶压根没法拆。这时候远程升级就成了刚需而CAN总线恰恰是现成的通道。STM32F103在国产化替代和低成本项目里用得非常多芯片本身自带CAN外设支持标准帧和扩展帧波特率可配硬件上只要加一个CAN收发器比如TJA1050、SN65HVD230就能跑起来。所以我就把Bootloader方案定成了“CAN Bootloader”上电先跑一段IAP程序通过CAN总线接收升级包写入内部Flash完成后跳转到APP执行。整个过程不需要额外的下载器只需要一个USBCAN卡或者现场总线上的上位机节点就能完成。做这件事之前我踩过一个认知误区以为Bootloader就是写一段“把接收到的数据往Flash里写”的代码结果第一版出来问题一大堆——跳转过去就死机、写到一半写不进去、升级中断后设备变砖。后来才明白Bootloader看起来简单实际涉及Flash分区规划、中断向量表迁移、CAN协议设计、异常恢复机制、量产防呆设计一堆事。这篇文章把我在这个项目里的完整实践拆开讲从协议、源码、调试到量产落地一条线捋清楚。1.2 方案选型为什么是CAN而不是UART很多教程讲Bootloader都喜欢用UART因为简单一个串口加一个USB转TTL就能演示。但在整车和工业现场场景里UART有几个致命问题第一很多设备没有拉出串口引脚或者串口被其他功能占用。第二RS232/TTL抗干扰能力弱线缆一长误码率就上来而工业现场动辄几米甚至几十米线束。第三整车上已经是CAN网络了单独为升级再拉一条调试串口产线和售后都要多一套硬件。CAN的优势是物理层本身就是差分信号抗干扰强通信距离远而且天然支持多节点组网。同一根总线上的设备可以通过ID过滤只响应升级指令其他节点正常工作不受影响。对于量产部署来说CAN还有一个好处产线测试本来就要通过CAN做EOLEnd of Line检测升级功能可以直接复用同一套工装和线束。另外从成本角度看F103的CAN外设几乎是“白送”的不需要额外加芯片。唯一要注意的是CAN收发器选型我建议用带隔离的模块比如CTM1050产线上经常有设备接地不一致的问题隔离模块能直接把共模干扰拦在物理层外面少很多莫名其妙的通信故障。1.3 整体架构与功能拆解这个项目最终落地的系统架构分成三层第一层是上位机/产线工装。我这边用的是周立功USBCAN-II上位机软件自己用C#写了一个简单的升级工具通过CAN发送升级包。如果现场总线里已经有主节点在做调度也可以让主节点兼任升级发起方。第二层是Bootloader程序放在STM32F103内部Flash的起始区域。它负责上电初始化CAN、等待升级指令、接收固件包、擦写Flash、校验、跳转。第三层是APP应用程序放在了Flash的高地址区。APP里面也要配合做一件事把中断向量表重映射到自己的起始地址否则中断一进来就跑到Bootloader里去了。整个升级流程大概是这样设备上电先跑Bootloader如果是冷启动并且没有收到升级指令就延时几百毫秒后直接跳转到APP如果收到了升级帧或者APP区无效就留在Bootloader等待完整升级包。升级指令体一般包括握手、开始传输、数据帧、结束帧、校验结果回读这几类。2. 核心原理Flash分区与程序跳转机制2.1 STM32F103内部Flash布局与分区STM32F103的内部Flash从0x08000000开始大小因型号而异比如F103C8T6是64KBF103RCT6是256KBF103ZET6是512KB。Flash的擦除以页为单位注意不同容量的页大小不一样小容量和中容量64KB以下是1KB一页大容量128KB以上是2KB一页。我在这个项目里用了F103CBT6Flash一共128KB页大小1KB。分区很关键规划好了后面所有逻辑都顺。我的分区如下区间地址范围大小用途Bootloader0x08000000 ~ 0x08001FFF8KBIAP升级程序参数存储区0x08002000 ~ 0x08002FFF4KB升级标志、版本号、校验和APP程序区0x08003000 ~ 0x0801BFFF102KB应用程序预留0x0801C000 ~ 0x0801FFFF16KB备份区或日志区Bootloader给8KB是非常宽裕的实际上我编译完只有4KB多。APP起始地址选0x08003000注意这里有个对齐问题虽然F103的页是1KB但向量表要求按地址对齐到0x08000000或0x08010000这样的大地址边界吗不需要向量表只要对齐到自己所在地址即可关键是偏移量按字对齐。0x08003000可以被4整除满足要求。2.2 向量表偏移与APP起始地址设置APP程序编译时必须把ROM起始地址改成0x08003000。在Keil MDK里就是打开Target选项卡把IROM1的Start改成0x08003000Size改成0x1B000102KB。很多人改了ROM地址后程序还是跑飞其实就是忘了初始化向量表偏移。从F103启动流程来说芯片上电后固定从0x08000000取栈指针和复位向量。如果APP放在0x08003000那么APP的向量表也在0x08003000但CPU并不会自动知道它默认还是去0x08000000找向量表。所以Bootloader跳转前必须告诉CPU“你的向量表搬家了以后中断从这里查”。标准做法是在APP的main函数最开始调用#define APP_VECTOR_TABLE_ADDR 0x08003000 SCB-VTOR APP_VECTOR_TABLE_ADDR;对F103来说SCB-VTOR这个寄存器是0xE000ED08。注意第一次赋值时要先确保当前代码本身不依赖中断因为一旦改了VTOR后面来的所有中断都会去新向量表取地址。所以这句要放在main最前面最好在系统初始化之前。2.3 跳转函数实现与临界区处理Bootloader跳转APP的方法有讲究。不能简单地用函数指针因为直接调用时栈指针和复位向量没重新初始化很可能跳过去后第一句就进HardFault。我用的跳转代码是业内标准的做法typedef void (*pFunction)(void); void JumpToApp(uint32_t app_addr) { uint32_t stack_addr *(volatile uint32_t *)app_addr; uint32_t reset_addr *(volatile uint32_t *)(app_addr 4); if ((stack_addr 0xFFF00000) ! 0x20000000) { return; // 栈指针异常不能跳转 } // 关闭全局中断 __disable_irq(); // 恢复默认时钟配置避免APP初始化时钟时冲突 RCC_DeInit(); SysTick-CTRL 0; SysTick-LOAD 0; SysTick-VAL 0; // 关闭AHB/APB外设时钟可选但建议做 RCC-AHBENR 0; RCC-APB1ENR 0; RCC-APB2ENR 0; // 重新设置栈指针然后跳转 __set_MSP(stack_addr); ((pFunction)reset_addr)(); }有几个容易忽略的点跳转前要把SysTick关掉否则APP里初始化SysTick时会开在Bootloader残留的中断上。其次是中断标志位CAN外设如果还有挂起中断跳过去可能马上触发但APP的CAN中断向量已经重映射了没问题主要是EXTI、USART这些最好也清一下挂起位。另一个是硬件看门狗如果Bootloader里开了IWDG跳转前先关掉否则APP如果初始化耗时长看门狗先复位了。为什么跳转前检查栈指针因为如果APP区是空的或者数据损坏第一个字不落在0x20000000开头的SRAM范围内说明这个“栈指针”根本不是合法的直接跳过去必然死机。实测中最常见的就是APP没烧录就跳转加了这道检查能省很多返修。3. CAN通信协议设计3.1 协议分层与帧格式定义CAN通信和UART的升级协议有个本质区别UART可以一字节一字节流式处理CAN一次最多传8字节有效载荷而且帧和帧之间没有天然的“流”的概念。所以协议必须自己定义帧类型、帧序号、数据长度、校验方式实现“CAN上的文件传输”。我设计的协议比较简单两层就够了底层是CAN数据帧上层是升级指令区。全部使用标准帧11位ID波特率500kbps。ID分配如下ID方向含义0x100上位机 - 设备升级控制命令0x101上位机 - 设备数据帧0x102上位机 - 设备请求重传0x110设备 - 上位机应答帧0x111设备 - 上位机状态上报注意一个细节如果总线上还有其他业务节点ID分配要避开业务ID的范围防止升级帧被当成普通业务帧解析。我们这边业务帧ID都在0x200以上所以升级帧用0x1xx完全隔离。控制命令帧的Data[0]是命令码Data[1]是参数。常用命令包括握手0x10、开始升级0x11、结束升级0x12、查询状态0x13、复位执行0x14。数据帧里Data[0]是帧序号Data[1]~Data[7]是固件数据一次最多传7字节。3.2 分帧传输与多帧重组F103的Flash页最小1KB而CAN一帧最多7字节数据所以一页数据要拆成147帧左右。整个固件如果有90KB大概需要13000多帧。听起来很多但500kbps的CAN总线上每帧大约0.26ms一秒钟能传3000多帧实际上传90KB的固件大概10秒内能完成这个速度在量产产线里完全可接受。分帧传输的核心问题是丢帧和乱序。CAN本身有硬件仲裁和CRC校验正常通信丢帧率非常低但也不是万无一失——总线繁忙、错误帧、节点掉线都会导致丢帧。我采用的办法是“滑动确认”上位机每发送64帧后等待设备应答一个“接收进度”消息设备告诉上位机“我已经正确收到了第N帧”上位机从N1继续发。这种方式比逐帧握手快得多又比全包发完再校验保险得多。设备端收到数据帧后先把数据暂存在一个RAM缓冲区里。因为F103CBT6的SRAM是20KB装不下整个固件所以缓冲区只放一页1KB的数据攒满一页后立刻写入Flash然后清空缓冲区等下一页。这样RAM占用不到2KB非常节省。3.3 高可靠性的确认与重传机制升级过程最怕的不是“传输慢”而是“悄悄丢帧最后校验失败”。所以协议里在结束阶段做了一个双保险CRC32校验和回读校验。发送端上位机在发送固件前先计算整个固件文件的CRC32放在结束升级命令帧里一起发过去。设备端收完所有数据后也把收到的固件数据按同样的算法算一遍CRC32和上位机发的值比对。如果一致应答“校验通过”如果不一致应答“校验失败”上位机可以重新发送整个固件或者逐帧重传。只做CRC还不够因为设备写Flash后可能因为供电不稳、Flash疲劳等原因出现个别字节写入错误。我在校验CRC之前还有一个“回读”步骤所有数据写完后把Flash里的内容和RAM缓存里的原始数据逐字节比较一遍确认硬件写入没有异常。这个回读比较看起来慢90KB数据逐个字节读但实际上Flash读速度很快耗时不到一秒钟这笔账花得很值。3.4 和DBC文件的对接问题做整车项目的朋友会关心CAN协议能不能用DBC文件管理。我的建议是升级专用的私有协议不建议进DBC因为DBC里的信号是周期或事件触发的“业务信号”而升级协议里的数据帧是“连续字节流”用DBC来表达反而别扭。如果产线工具非要通过DBC来对接可以把控制命令和状态应答做成DBC里的两个消息数据帧通过DBC的原始字节信号透传。但实际产线我都是让上位机直接走原始CAN帧。4. 工程实现与源码解析4.1 工程结构搭建工程我基于STM32CubeMX生成底层再在Keil MDK里加Bootloader逻辑。CubeMX里开了CAN1、UART1用来打日志调试期用、一个定时器TIM3作为升级超时计时GPIO里留了一个LED做状态指示。之所以用TIM3做超时计时而不是简单用延时是因为Bootloader的接收循环中经常要判断“等一个握手应答等了多久”“等下一帧等了多久”用定时器做基准可以避免阻塞式延时把CAN接收卡死。CubeMX配置要点CAN波特率500k采样点设置在75%左右。F103的CAN外设时钟来自APB1通常是36MHz500k波特率对应的分频是// APB1 36MHz // BRP 4, 则时基 4 * (1/36MHz) 111ns // tq数 (1/500k) / 111ns ≈ 18 // 采样点 (1 13) / 18 ≈ 77.8%具体在CubeMX里就是配置CAN的Prescaler4Time Quanta18重新同步跳跃宽度选2。采样点对老练的车载CAN网络很重要总线线缆长、节点多时采样点稍微不对就会出现“对方发得出来、自己收不到”的灵异问题。4.2 Flash擦写底层实现Flash驱动是本项目的硬骨头之一F103的Flash写入有几个硬性规则写Flash前必须先擦除整个扇区页擦除后数据全变成0xFF。擦除和编程操作需要在FLASH_CR寄存器配置操作完成后要检查BSY位。编程必须按16位半字进行操作不能按字节写。操作期间不能有中断嵌套访问Flash否则会报错。我封装了一个比较稳的写Flash函数基本原理是“先擦除目标页然后按半字写入”uint8_t FLASH_WritePage(uint32_t page_addr, uint8_t *buf, uint16_t len) { uint16_t i 0; uint16_t *p (uint16_t *)buf; uint16_t num_halfwords (len 1) / 2; // 补到半字对齐 FLASH_Unlock(); FLASH_ClearFlag(FLASH_FLAG_EOP | FLASH_FLAG_PGERR | FLASH_FLAG_WRPRTERR); if (FLASH_ErasePage(page_addr) ! FLASH_COMPLETE) { FLASH_Lock(); return 1; } for (i 0; i num_halfwords; i) { if (FLASH_ProgramHalfWord(page_addr i * 2, p[i]) ! FLASH_COMPLETE) { FLASH_Lock(); return 2; } } FLASH_Lock(); return 0; }注意这个函数里的FLASH_ProgramHalfWord来自标准外设库操作时要确保全局中断是关闭的或者至少保证没有其他更高优先级的中断在Flash编程期间进入。我在Bootloader的升级主循环里用临界区保护了整个擦写过程代价是CAN接收中断在写Flash期间大约1KB数据擦写要几十ms会丢几帧。这个问题的解决方案是上位机发数据时设计了“每64帧等待应答”的机制预留了足够的时间余量所以丢帧不影响最终结果。4.3 主循环状态机Bootloader不能写成“顺序执行”的流水账代码因为现场可能出现在任意阶段掉线、复位、中断等异常情况。我把它设计成一个有限状态机这样每个状态对应明确的行为和超时处理。状态有状态说明超时动作IDLE初始等待无WAIT_HANDSHAKE等待上位机握手超时跳APPRECEIVING接收固件数据超时报错返回IDLEWRITING_FLASH写入Flash写失败返回IDLEWAIT_CRC_CMD等待结束/校验命令超时报错返回IDLECRC_CHECK校验固件校验失败进入ERRORJUMP_APP跳转执行APP跳转失败进入ERROR主循环伪代码如下while (1) { switch (state) { case IDLE: if (frame_received cmd CMD_HANDSHAKE) { send_response(RESP_ACK); state WAIT_HANDSHAKE; timer_start(TIMER_UPGRADE_TIMEOUT); } else if (app_valid()) { delay_ms(500); // 冷启动短暂等待给上位机一个握手窗口 JumpToApp(APP_START_ADDR); } break; case RECEIVING: page_buf_receive(frame); if (page_buf_full()) { FLASH_WritePage(...); send_ack(progress); } break; case CRC_CHECK: if (crc32_matched()) { send_response(RESP_CRC_OK); state JUMP_APP; } else { send_response(RESP_CRC_ERR); state ERROR; } break; case JUMP_APP: JumpToApp(APP_START_ADDR); break; case ERROR: // 停留等待重新握手或超时后自复位 break; } }状态机的好处是逻辑可控出问题能快速定位到具体环节。调试的时候我在每个状态切换点打一条串口日志配合上位机抓帧哪里断的一目了然。4.4 APP端配合改法很多朋友只盯着Bootloader写忘了APP也要配合。前面说了要设SCB-VTOR还有几件事也别忽略一是中断服务函数的地址不能写死。比如老代码里有时会直接给某个外设中断回调赋函数指针从旧地址跳到新地址后这个指针如果不重新初始化就会飞掉。二是APP的启动文件里堆栈大小要重新评估。Bootloader跳转到APP时用的是APP向量表第一个字作为栈指针所以APP启动文件开头的Stack_Size和Heap_Size要按APP的实际需求配别沿用Bootloader的小栈配置。三是APP里要预留一个“固件更新标志”。我是在参数存储区里放了4个字节的魔数比如0xA5A5A5A5表示“下次复位进入Bootloader升级模式”APP收到升级指令后先在参数区写标志然后软复位。这样就不需要每次上电都要等待Bootloader的超时窗口用户体验好得多。具体做法void EnterBootloader(void) { *(volatile uint32_t *)PARAM_AREA_ADDR UPGRADE_REQUEST_FLAG; NVIC_SystemReset(); }Bootloader启动后先检查参数区的标志如果是升级请求就直接留在Bootloader否则延时跳APP。升级流程完成后清除标志。4.5 计时与超时处理升级过程中如果上位机掉线设备不能永远死在“接收状态”要有一个兜底超时。我用TIM3做毫秒级计时基准每次收到有效帧就清零计数值如果超过5秒没收到任何帧就判定通信中断自动复位到Bootloader起始状态等待下一次升级指令。这样即使上位机中途崩溃设备也不会卡死。超时时间的选择我说一下不能太短因为CAN总线在某些瞬间可能连续几个错误帧导致总线阻塞几帧收不到很正常也不能太长否则产线上发现升级失败后要干等半天。5秒是实践下来比较合适的值。5. 量产部署的坑与对策5.1 产线烧录流程设计实验室里怎么玩都可以但量产部署讲究的是流程标准化和防呆。我这边最终确定的产线流程是这样工装治具夹好设备给设备上电设备进入Bootloader等待握手。上位机扫描总线上所有设备ID确认数量与工位产品一致。上位机发送握手帧设备端回读版本号、芯片ID、上次升级状态。上位机比对目标固件和当前固件版本决定是否升级。升级完成后上位机发送“查询固件CRC”命令把设备端Flash中固件的CRC32读回来和上位机源文件比对作为出厂验收项之一。记录升级时间、设备序列号、固件版本、CRC结果到MES系统。这个流程在产线上跑了两个多月了最大的经验是上位机不要每台设备都重新配置参数最好是读一个固定的配置文件。固件版本号、存放路径、目标节点ID、是否强制升级这些统统从配置文件读避免操作员手动点错。5.2 防止刷成砖的机制任何Bootloader最怕的就是升级失败后设备变砖尤其量产阶段一旦砖了就要拆壳烧写损失很大。我的防砖策略有三个层次第一是双保险启动逻辑设备上电后先检查APP区首字栈指针和复位向量是否合法不合法就留在Bootloader。这样即使APP刷得稀烂只要Bootloader还在就能重新刷回来。第二是升级确认机制写完固件后先校验CRC校验通过才设置“APP可用”标志。这个标志单独放在参数存储区是最后一步才写入的。如果固件还没校验就断电标志不被置位下次启动还是会进Bootloader。第三是Bootloader本身的自保护在擦除APP区之前先校验Bootloader自己的CRC如果Bootloader区损坏了才说明真没救了但这几乎不会发生。F103内部Flash的寿命和稳定性在正常供电下是有保障的真正会搞坏Bootloader的是在擦写过程中断电。另外我强烈建议给产线工装加一个检测程序升级完成后设备自动发一条“版本CRC”消息工装显示绿色通过才允许装箱。虽然看起来多了一步但能拦截掉几乎所有潜在问题。5.3 升级认证与防回滚如果产品对安全有要求光有CRC还不够。我这边在协议里加了简单的序列号认证设备端在握手时把96位唯一ID芯片的Unique ID回传给上位机上位机把升级包绑定到这个ID上防止A设备的固件被复制刷到B设备里去。这个在知识产权保护和产品管理上有实际价值。防回滚机制也做了比如当前版本是V1.2上位机不允许下发V1.1。怎么实现在结束升级命令帧里带上目标版本号和最低允许版本号设备端判定版本号是否符合要求不符合直接拒绝。这样能避免产线调试时误刷旧固件导致后期兼容性问题。5.4 量产测试项与验收量产验证踩过坑之后我整理了一份验收清单推荐给同样要做量产部署的朋友测试项操作通过标准升级功能正常下发固件CRC一致APP可正常运行断电恢复传输中随机断电上电后进入Bootloader可重新升级非法固件发送损坏固件设备拒绝执行APP不变升级后用业务测试跑一遍正常业务无异常复位、无总线错误长时间老化升级后连续运行24小时无复位、无死机6. 调试经验与常见问题6.1 CAN通信突然连不上这个坑我遇到太多次了现象是“之前明明好好的突然一发数据就发不出去或者对方收不到”。排查顺序我总结如下先看波特率。CAN没有“自动识别波特率”的功能双方波特率不一致时表现为发送方自己能看到总线报文吗可以。但对方收不到或者收到一帧后后续全是错误帧。用CAN分析仪USBCAN、PCAN都可以抓总线数据如果看到一堆Error Frame几乎可以断定波特率或采样点不匹配。再看终端电阻。CAN总线的两端必须各接一个120欧电阻。我的产线工装里经常出现只在一端接了电阻的情况短距离实验发现不了问题但线缆长度超过几米后信号反射会把波形搞崩表现为升级过程中偶发性丢帧。还要检查总线占用率。如果现有CAN网络里业务报文非常密集升级帧的优先级低可能一直无法获得仲裁。这种情况可以调高升级帧的ID优先级或者暂时让业务节点静默。我调这类问题一般用USBCAN的“总线检测”模式抓一段波形看电平是否正常再用“报文统计”看错误帧计数基本能定位。6.2 下载中途断开与Flash写入失败升级过程中最恶心的事情是“传输到80%后设备断了”。原因五花八门但我排查下来最多的是供电问题。CAN升级时设备通常要连续工作一段时间如果供电用的是劣质电源适配器电流一波动Flash擦写瞬间电流大直接把供电拉低到复位阈值设备就重启了。处理方式给设备供电用稳压电源电流余量留足。另外在Bootloader里加“掉电保护”一旦检测到供电异常比如用ADC采样VDD立即停止Flash操作等待供电稳定后再继续。Flash写入失败还有一种隐蔽原因写某个地址区间时该地址对应的Flash扇区正在被代码本身占用。比如把缓冲区定义在Flash映射的地址空间虽然这不可能因为SRAM和Flash地址分开的或者中断向量表所在的扇区被误擦除。真遇到了可以用CMSIS里的FLASH_GetStatus函数把错误状态打出来常见的PGERR或WRPRTERR对应的问题不一样。6.3 跳转失败死机的定位跳转失败的现象通常是“Bootloader屏幕还活着LED闪但APP不启动”或者是“直接进HardFault”。定位思路第一步检查APP区是否真的有内容。用调试器读0x08003000处的前8个字节正常应该是一个SRAM地址比如0x20001234和一个Flash地址比如0x08003341。如果读出来是0xFFFFFFFF说明压根没刷进去。第二步检查APP的VTOR是否设置。在APP的main函数里设SCB-VTOR后再查看该寄存器值是否等于0x08003000。第三步检查跳转时中断是否还挂着。可以把所有挂起中断清一遍再跳转NVIC-ICER[0] 0xFFFFFFFF; NVIC-ICPR[0] 0xFFFFFFFF;另外还有一个容易忽视的点如果APP用了FreeRTOS跳转时APP的任务栈和系统时钟初始化都不能依赖于“从0x08000000开始的启动流程”FreeRTOS的启动文件通常会调用SystemInit这个函数里有时会写PLL和Flash等待周期但它是用寄存器配置硬编码的不会因为地址变了就出错。只要确保时钟和Flash等待状态在跳转前被妥善处理即可。6.4 工具与资料推荐关于工具和资料我建议手头常备这么几样周立功USBCAN-II或者PCAN用来抓CAN报文、模拟上位机ST-Link V2用来烧录Bootloader和调试APPSTM32F103中文参考手册RM0008注意要看“Flash编程”和“CAN控制器”这两章PDF记得下原版或者靠谱翻译版Keil MDK工程用CubeMX生成底层后千万别手动画代码能省掉大量无意义错误一个示波器或者逻辑分析仪排查CAN物理层问题时必备调试期如果嫌串口日志麻烦我后来是直接把调试信息编码成CAN私有帧发出来上位机一个窗口实时显示Bootloader内部状态。这样最贴近实际工作环境也省得在板子上额外留串口。7. 写在后面的几句体会这个CAN Bootloader项目前前后后改了五六版真正稳定是在加了状态机、超时处理和断电保护之后。回头看我踩过最大的坑反而是最基础的两件事一是Flash分区和向量表偏移二是CAN总线的物理层参数。这两个问题如果提前吃透后面很多折腾根本不会发生。另外一个深刻的体会是Bootloader这东西不是“写完就行”它是出厂后你唯一能远程抓住设备的手质量要求要比普通业务代码更高多花几天时间把边缘情况想全比后面出差返修划算得多。最后再提醒一句量产部署前一定要用坏固件、半截固件、断电场景充分折磨你的Bootloader它扛得住产线才能睡得着觉。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑