1. 三个缩写先放回各自语境别急着背定义芯片烧录这件事最容易让人迷糊的不是操作而是名词。ISP、ICP、IAP 这三个缩写摆在一起很多人第一反应是去背全称背完发现还是不会选。我自己刚开始带项目的时候也这样直到有一次产线催着要烧录方案我把三个词的全称念了一遍产线负责人回了一句我就问一句这颗芯片焊在板子上以后还能不能改程序——那一刻我才意识到这三个词的区别根本不在字面而在谁握着 Flash 的写权限。这三个词的全称都以 In-…-Programming 结尾差别全在中间那个介词短语In-System 是在系统里指芯片已经装进整机系统、焊在板上In-Circuit 是在电路里指芯片处在完整的电路环境中In-Application 是在应用里指用户程序正在跑的时候。前两个的语境几乎是重叠的这就是行业里术语混乱的根源。你去看不同厂商的参考手册同一款芯片的烧录章节有人写 ISP有人写 ICP描述的却是同一件事。所以这篇不讲名词考据讲的是怎么在真实项目里做判断。从研发阶段第一次把程序灌进空片到产线批量交付再到设备卖出去三年之后客户要求远程升级这三件事在技术上是三套完全不同的东西选错了轻则效率低下重则现场变砖。不管你是刚接触 MCU 的新手还是已经在做量产方案的老手下面这套判断逻辑都能直接用。1.1 判断标准其实只有一条把三个缩写拆开看真正决定它们差异的只有一个问题执行擦写动作的那段代码跑在哪里如果执行擦写的代码跑在外部工具上——比如一台连着调试探针的电脑、一台脱机烧录器、一个产线治具——那么芯片这时候只是一块被动的存储器工具通过某种接口直接往它的 Flash 里写数据。这条路上芯片内部不需要预先存在任何用户代码空片也能烧。ISP 和 ICP 基本都落在这个范畴里。如果执行擦写的代码跑在芯片内部一段出厂就固化好的引导程序上——这段程序用户改不了、擦不掉上电时根据引脚电平或者空片检测决定要不要进入烧录模式——那它依然属于 ISP 的范畴只不过外部工具从直接操作 Flash变成了跟引导程序对话。如果执行擦写的代码跑在用户自己写的程序里——程序正常运行着收到一段新固件自己调用 Flash 擦写接口把新固件写进自己的存储空间——这就是 IAP。这条路的前提是芯片里必须已经有一个能跑的旧版本程序空片是绝对做不到 IAP 的。记住这条主线空片能烧的是 ISP/ICP非空片自己改自己才叫 IAP。后面所有的细节差异都是从这条主线长出来的。1.2 三者的能力边界对照下面这张表是我自己在做方案评审时常用的版本把关键维度拉平了看选型的时候对着勾就行。维度ISP调试口直写ISP固化引导程序ICP产线整片编程IAP应用内升级谁执行擦写外部调试探针芯片内固化 BootROM外部烧录器或治具用户程序自身典型接口SWD / JTAGUART / CAN / USB调试口或专用烧录口任意可用通信通道空片能否操作能能能不能是否需要预置代码不需要不需要不需要必须有可运行的旧固件能否在整机封闭后操作需要留出调试口需要留出通信口和启动引脚一般需要治具接触点只需业务通信口单颗耗时秒级到十几秒十几秒到分钟级可并行单工位极快取决于固件大小和通道带宽失败后果可重来可重来可重来但影响产线节拍处理不当会变砖主要使用阶段研发、小批量研发、售后救砖量产售后、现场表格里最值得注意的是空片能否操作这一行。很多方案失败的根源就在于把 IAP 当成了万能方案——设计的时候想着反正以后能远程升级结果产线第一次烧录没做规划芯片是空的IAP 根本无从谈起。空片必须有一条 ISP 或 ICP 的路兜底这是硬约束。1.3 术语漂移为什么问十个人有八种答案必须提醒一句这三个缩写在真实文档里的用法并不统一我见过至少三种不同的定义习惯。第一种把 ISP 和 ICP 当作同义词都指芯片焊在板上之后通过调试接口烧写区别仅仅是文档作者的习惯。第二种把 ICP 特指包含选项字节、读保护、全片擦除在内的完整编程流程强调它是产线那种一次性把芯片配置到最终状态的工艺而 ISP 只指纯粹写代码。第三种更离谱某些渠道商把ICP直接当成帮客户代烧录这项服务的代称跟技术本身已经没关系了。我的处理办法是不看缩写看文档里描述的执行主体和接口。翻了十几份不同来源的手册之后我现在看到这三个词第一反应是去找这一步是谁在操作 Flash这句话找到了就一切清楚找不到就往下翻时序图看有没有外部工具发送命令、芯片返回应答的过程。这个习惯帮我避开了很多沟通上的坑——尤其在跟不同供应商对接的时候同一个词在两边的意思可能完全不一样。2. ISP板子焊好之后第一版程序怎么进去ISP 是绝大多数人接触芯片烧录的第一步。你拿到一块新板子芯片是空的什么程序都没有这时候唯一能做的就是 ISP——要么用调试探针直接怼进去要么让芯片进入出厂固化的引导模式通过串口之类的通道灌进去。这一节的实操细节基本上决定了你后面几周开发顺不顺。2.1 两条路径的取舍调试口直写 vs 固化引导程序调试口直写是最直接的方式。外部工具通过 SWD 或者 JTAG 这类调试协议直接获得对芯片内核和存储器的访问权限可以读、可以写、可以设断点、可以单步。它的好处是什么都能干——不光是烧程序还能读回内存、改寄存器、看变量研发阶段几乎是刚需。坏处是需要把调试引脚引出来通常至少四根线电源、地、数据线、时钟线有条件的话还要接复位线。固化引导程序这条路走的是另一套逻辑。芯片出厂的时候厂家在内部固化了一小段引导代码上电时它会检查某些引脚的电平或者检测主程序区是不是空的如果满足条件就进入烧录模式通过串口、CAN、USB 之类的通道等待外部发命令。这条路的好处是只要业务接口能用就行不一定需要额外的调试口坏处是速度慢而且握手过程对时序敏感稍微有点偏差就进不去。选哪条我的经验是研发阶段两条都留着产线看情况选一条。研发阶段留调试口是必须的不然出了问题没法查同时把串口引导的引脚也引出来因为总会有那么一两次芯片被误操作锁住了调试口进不去只能靠引导程序救回来。2.2 调试口直写的完整链路与几个易错点先讲连线。四根基本线里最容易出问题的是供电。目标板自己有电源的时候调试器的供电输出一定不要接否则两路电源打架轻则通信不稳重则烧掉调试器或者板上的稳压器件。我见过不止一次有人图省事把调试器的电源接到目标板上结果调试器里那颗小稳压芯片直接冒烟。判断方法很简单目标板通电之后用万用表量一下调试器电源脚和目标板电源脚之间的电压差如果接近零说明已经共地共电这时候千万不要再接。时钟线的频率也要注意。调试协议走的是同步串行频率太高的时候线长一超过十几厘米就容易出错。我一般的做法是从低速开始试能连上之后再往上调找到稳定工作的上限然后往回退一档。这个退一档的习惯是从一次现场事故里学来的实验室里跑得好好的配置到了客户现场因为环境温度高了几度通信就开始间歇性失败往回退一档频率之后彻底稳定。烧写的完整流程其实是三步整片擦除、按地址写入、逐段校验。擦除这一步很多人会忽略一个细节——擦除的粒度。Flash 不是按字节擦的而是按页或者按扇区擦的一个扇区可能是几 KB。如果你的程序只有 1KB擦除的时候依然会把整个扇区清空这意味着如果你在同一扇区里放了参数或者序列号一擦就全没了。所以分区规划的时候程序区和参数区一定要按擦除粒度对齐不能随便找个地址就放。校验这一步更不能省。写入过程中如果电源有波动、接触有抖动可能写进去的数据是错误的但工具不报错。我现在的习惯是写完必须做一次全量比对有些工具叫Verify有些叫校验名字无所谓功能必须有。产线上更是如此不校验等于赌博。2.3 串口引导模式的握手时序以及进不去的排查顺序串口引导这条路第一次用的时候很容易卡在连不上。它的基本逻辑是芯片复位释放的那一瞬间采样某个或某几个引脚的电平如果符合约定就跳转到内部的引导程序然后通过串口等待外部发同步字符。外部工具先发一个同步字节芯片回一个应答字节握手成功之后进入命令循环后面就是一条条命令读芯片信息、擦除、写入、校验、跳转回用户程序。进不去的时候排查顺序我一般是这样的。先查引脚电平。这里的关键是复位释放瞬间这个时间点很多人用万用表静态测量觉得电平是对的但实际上电过程中引脚电平有过跳变或者复位释放的时刻电平还没稳定。用示波器抓一下复位引脚和启动引脚的上电波形一目了然。再查时钟。有些芯片的引导程序依赖外部晶振如果板上晶振没起振、或者负载电容焊错了引导程序跑不起来自然没法握手。这时候可以试试切到内部时钟源很多芯片的引导程序支持自动回退。然后查串口本身。波特率偏差是隐形杀手尤其是用内部 RC 振荡器做时钟源的时候偏差可能达到百分之几超出了串口的容错范围。这种情况要么降低波特率要么校准时钟。还有一个特别容易被忘的点硬件流控。有些上位机软件默认开着流控但目标板没接流控线结果就是能发不能收看着像连上了实则一直在超时。最后查上电时序。如果目标板上有大电容电源上升沿很缓芯片可能在电压还没到工作区间的时候就开始采样引脚采样结果是错的。这种情况需要在电源设计上做处理或者靠工具端反复重试来兜。2.4 选项字节和读保护ISP 阶段最容易埋的雷这一小节我要单独拎出来讲因为它是最容易在毫无察觉的情况下把芯片锁死的地方。Flash 里除了程序通常还有一块独立的配置区叫选项字节、配置字、熔丝位名字各不相同作用类似控制读保护级别、写保护范围、看门狗是否硬件使能、复位引脚是否复用成普通 IO、启动方式等等。这些配置一旦写进去行为就变了而且有些是不可逆的或者只能整片擦除才能恢复。最典型的坑是读保护。很多方案为了保护代码不被读出来会在量产时打开最高级别的读保护。打开之后调试口就连不上了程序也读不回来——这是设计意图没问题。问题是有些芯片在读保护打开的状态下连整片擦除这个操作都要走特殊流程如果这个流程没写进产线作业指导书产线返修的时候就抓瞎了。我遇到过一块板子返修工程师连着调试器死活连不上以为芯片坏了其实是读保护在起作用最后翻手册才发现要先发一个解锁序列。还有一个更隐蔽的复位引脚复用。有些芯片默认把复位引脚当复位用但可以通过选项字节改成普通 IO。研发阶段为了省一个引脚改了配置结果量产的时候治具靠复位引脚控制芯片进入烧录模式改完就进不去了。这种问题在实验室里永远发现不了只有到了产线才爆发。我的做法是选项字节的配置单独列一份清单跟着固件版本一起管理。每次改配置都要在清单上记录改了什么、为什么改、影响哪些生产环节。这份清单比代码本身更需要版本控制因为它是一锤子买卖写错了没法靠改代码补救。3. ICP产线语境下的整片交付和 ISP 差在哪如果说 ISP 的关键词是能不能烧进去那 ICP 的关键词就是多快、多稳、多一致。同样是把程序写进 Flash研发阶段一个人对着一块板子慢慢调和产线上一天几千片地过完全是两套逻辑。这一节讲的是产线视角下的烧录工程问题很多做研发的人第一次接手量产方案时最容易在这里翻车。3.1 ICP 在产线上到底长什么样一条典型的产线烧录工位包含这么几样东西治具、探针或者弹针、脱机烧录器、以及一套防呆流程。治具的作用是把板子固定住让探针能准确压在测试点上。探针的接触电阻、压入深度、使用寿命都会直接影响烧录成功率。我见过最离谱的一次批量不良最后查出来是探针用了太久表面氧化导致接触电阻从几十毫欧涨到了几欧信号完整性崩了烧录十片错三片。换了新探针之后立刻恢复正常。这类问题在数据上表现为间歇性失败特别容易被误判成芯片质量问题。脱机烧录器的价值在于不依赖电脑。它把固件存在自己的存储里按下按钮就能烧不需要每台工位配一台电脑。这对产线来说很重要——电脑会死机、会中毒、会被人误操作脱机烧录器不会。而且脱机烧录器通常支持多通道并行一个工位同时烧四片、八片节拍直接翻倍。和纯 ISP 相比ICP 强调的是一次装片完成全部工序整片擦除、写入程序、写入选项字节、写入序列号或 MAC 地址、逐片校验、最后标记完成。这一整套流程要在一个节拍内做完节拍通常以秒计。所以 ICP 方案的设计重点不在功能全不全而在流程能不能压缩、能不能并行、能不能防错。3.2 产线真正在意的是节拍和一次通过率研发的人容易陷入一个误区觉得烧录嘛能烧进去就行了快几秒慢几秒有什么关系。等你真的站在产线边上看着传送带一分钟过来一批板子你就明白节拍是硬指标。单颗芯片的烧录时间可以拆成几段探针接触和上电几百毫秒、握手和识别几百毫秒到一秒、整片擦除这是大头从几百毫秒到几秒不等取决于芯片工艺和擦除粒度、写入跟固件大小和接口速率成正比、校验通常和写入时间相当如果做全量比对的话、断开和复位几百毫秒。加起来十几秒很常见。要压缩这个时间有几个方向。一是提高接口速率比如把调试时钟从几兆提到几十兆但受限于线长和信号质量有上限。二是并行多通道同时烧这是最有效的办法。三是减少不必要的数据量比如固件里如果有大段的空白或者固定填充可以在烧录配置里跳过只烧有效区段。一次通过率比节拍更重要因为它直接影响返修成本。影响通过率的因素除了刚才说的探针还有板面清洁度助焊剂残留会导致接触不良、供电稳定性多工位同时烧录时电流冲击、以及固件本身的问题比如某个版本的固件在特定批次芯片上校验不过。我现在的习惯是产线每天开工前先烧三片做首件确认把当天的通过率基线打出来一旦低于基线就停下来查不等攒一堆不良品再处理。3.3 批量一致性靠的是校验策略和数据管理研发阶段烧一片板子错了重烧就行。产线烧一万片错了就是一万片的返工。所以 ICP 方案里数据管理的重要性不亚于烧录本身。第一层是逐片校验。写完必须读回来比对不能只看工具报成功。有些工具的成功判定只是命令发出去了实际写没写进去它不管。我一般要求做 CRC 校验烧录器算出固件的 CRC芯片端读出全片数据算出 CRC两边比对一致才算过。这比逐字节比对快而且能覆盖到全部数据。第二层是序列号管理。产品要写唯一序列号或者网络地址的时候必须保证不重号、不漏号。做法通常是烧录器从一份预分配的号段里顺序取号每烧一片取一个烧完把已用号段回写到数据库。这里最容易出的问题是号段回写失败——烧了一片号没记下来下次重启又从头开始结果就是重号。所以号段分配一定要有事务性保证宁可这一片报废也不能重号。第三层是版本追溯。产线可能同时在跑几个固件版本对应不同的客户或者不同的批次。烧录器上必须能明确显示当前烧的是哪个版本并且把哪一片板子烧的哪个版本记录下来。出了质量问题能快速圈定范围。这一层很多小团队会省掉等到客户投诉的时候才追悔莫及。4. IAP程序自己给自己换血难点全在向量表和掉电IAP 是三个里面唯一一个设备已经在用户手里、还能改程序的方案也是复杂度最高的。它不像 ISP 那样有外部工具兜底一旦设计有漏洞用户手里的设备就真的变砖了。这一节讲清楚两件事程序怎么跳转以及擦写过程中那些能让设备当场死机的细节。4.1 分区规划先把 Flash 的地盘划清楚做 IAP 之前第一件事是在 Flash 上画地图。通常至少需要三块区域引导区、应用区、参数区。引导区放的是 Bootloader也就是那段永远不升级的代码。它负责上电检查、判断要不要进入升级流程、接收新固件、校验、跳转到应用。这块区域必须写保护任何情况下都不能被擦掉否则设备就彻底失去升级能力了。写保护通常通过选项字节配置配置好之后连 Bootloader 自己都改不了自己这是刻意的设计。应用区放用户程序大小要按擦除粒度对齐。比如芯片的擦除粒度是 2KB 一个扇区应用区就不能规划成 30KB得是 32KB 或者 30KB 向上取整到 32KB 这种。不对齐的后果是升级的时候擦除最后一个扇区会连带把相邻区域的数据擦掉如果相邻是参数区参数就没了。参数区放升级标志、固件版本、校验值、以及一些掉电不能丢的配置。这块区域要单独保护升级过程中绝对不能碰。我见过一个方案把升级标志放在了应用区的最后一个字节结果升级一擦除标志位也被擦了设备重启之后不知道该跳哪里直接卡在 Bootloader 里出不来。还有一种更高端的做法是双区也就是准备两份应用区 A 和 B当前跑 A新固件写到 B校验通过之后把启动指针切到 B。这个后面单独讲。4.2 跳转的本质栈指针、复位向量和向量表重定位从 Bootloader 跳到应用很多人以为就是一句函数调用其实完全不是。这中间要做几件事少一件都可能出问题。第一件是设置栈指针。芯片复位之后硬件会自动从存储器的固定位置读出两个值一个是初始栈指针一个是复位向量也就是第一条指令的地址。Bootloader 跳转到应用的时候必须手动把这两个值从应用区的对应位置读出来然后设置好栈指针再跳过去。如果栈指针没设对跳过去之后第一次函数调用就崩了。第二件是关闭中断。跳转之前所有中断必须关掉外设要复位到默认状态。因为应用区有自己的中断处理逻辑如果 Bootloader 留下的中断还开着跳到应用之后一旦触发中断跳转的地址还是 Bootloader 里的而那块代码可能已经被擦掉了直接跑飞。第三件是向量表重定位。这一点最容易被忽略。很多架构的中断向量表默认位于存储器的起始地址Bootloader 就住在那里它的向量表也是从那里开始的。应用区在更靠后的地址它的向量表也放在自己的区域里但硬件默认还是去起始地址找向量表。所以跳转之前必须告诉硬件向量表搬到了新地址这个动作通常是通过写一个专门的寄存器完成的。下面这段代码是我常用的跳转逻辑去掉具体型号相关的东西之后基本结构是这样的typedef void (*app_entry_t)(void); void jump_to_app(uint32_t app_base) { uint32_t stack_top *(volatile uint32_t *)(app_base); uint32_t reset_vec *(volatile uint32_t *)(app_base 4); /* 地址合法性检查防止空片跳转 */ if ((stack_top 0xFFFFFFFF) || (reset_vec 0xFFFFFFFF)) { return; } /* 关全局中断复位外设 */ __disable_irq(); peripheral_deinit_all(); /* 设置向量表偏移具体寄存器名随架构而定 */ set_vector_table_offset(app_base); /* 设置栈指针并跳转 */ set_stack_pointer(stack_top); ((app_entry_t)reset_vec)(); }这段代码里地址合法性检查那两行是保命的。空片的 Flash 读出来全是 0xFF如果不检查直接跳过去栈指针设成 0xFFFFFFFF下一条指令就异常了。产线上偶尔会有漏烧的板子流出去有了这个检查至少不会莫名其妙死机而是老老实实停在 Bootloader 里等待升级。应用侧的代码也要配合在启动的最早期把向量表偏移寄存器设成自己的基地址。这个动作必须在开中断之前完成否则第一个中断就可能跳到错误的地址。4.3 擦写 Flash 期间的致命细节Flash 有个物理特性擦除和写入的时候那块存储器是不能同时读的。这意味着如果代码本身跑在 Flash 里擦写期间 CPU 取指就会停顿。有些芯片会自动用缓冲或者延时来处理有些则要求代码必须搬到 RAM 里执行或者至少保证擦写期间不执行那块 Flash 上的代码。由此引出一连串限制。中断向量表通常也在 Flash 里所以擦写期间中断是不能响应的一旦有中断进来CPU 要去 Flash 里取中断处理函数的地址取不到直接异常。解决办法有两个要么擦写期间全局关中断把中断响应时间往后推要么把中断向量表搬到 RAM 里让中断在擦写期间也能正常响应。前者简单但会丢中断后者复杂但更稳。我一般选前者因为升级过程本身就不需要响应业务中断关掉几秒钟可以接受。但有个前提关中断期间不能有看门狗在跑。如果看门狗是硬件使能的关中断几秒钟它照样会复位芯片升级就中断了。所以升级流程开始之前必须先喂狗或者暂时关闭看门狗升级完成之后再恢复。还有一个坑是电源。擦除一个扇区需要几毫秒到几十毫秒这期间如果电源跌落扇区可能处于半擦除状态读出来的数据是乱的。如果这个扇区恰好是 Bootloader设备就废了。所以做 IAP 必须做掉电检测在擦写之前先确认电压稳定擦写过程中如果检测到电压下降立刻中止并标记升级失败重启之后重新来。更高端的做法是双区即使当前区被写坏了还有另一份完好的可以回滚。4.4 双区升级与回滚代价和收益要算清楚双区方案的核心思路是Flash 里留两份应用区A 和 B。当前运行的是 A新固件下载下来写到 B写完校验校验通过就把启动标志指向 B然后重启。重启之后 Bootloader 看到标志指向 B就跳转到 B 运行。如果 B 校验不过标志不变还是从 A 启动设备照常工作只是升级失败了。这个方案的收益很明确升级失败不会变砖。代价也很明确Flash 容量翻倍。对于容量紧张的芯片这个代价可能承受不起。折中的做法是单区 备份区。备份区只存新固件不存完整的可运行镜像。升级流程变成把新固件下到备份区校验然后擦除应用区把备份区的数据搬到应用区再校验通过就跳转。这个流程的窗口期比双区长——在擦除应用区到搬完之间断电设备还是废的。所以这个方案必须配合掉电检测和重试机制而且备份区不能擦下次上电还能接着搬。还有一种更省空间的做法不存完整备份只存差异。新固件和旧固件的差异通常不大做个差分算法只传差异部分边接收边应用。这个方案对算法和可靠性要求很高一般产品用不上但资源极度受限的场合确实有人这么做。选哪种取决于你对变砖这件事的容忍度。消费类产品用户能自己返厂或者重新刷机单区加校验就够了。工业设备、装在塔上或者井下的设备返修成本极高那就必须上双区Flash 容量再紧张也得挤出来。5. 选型按项目阶段判断而不是按技术优劣讲完三种方式的技术细节回到最实际的问题我的项目该用哪个。这个问题没有标准答案但有标准判断路径。我的经验是不要问哪个更好而要问我现在处在哪个阶段这个阶段最怕什么。5.1 研发阶段几乎没有选择余地研发阶段的答案很明确调试口 ISP 是唯一舒服的选择。因为这个阶段程序天天改改完就要烧烧完就要调。你需要的不只是烧录还有断点、单步、看变量、读寄存器。这些只有调试口能给。串口引导虽然也能烧但烧完就没法调试了改一行代码烧一次效率低到无法接受。这个阶段唯一要注意的是把后续要用的接口预留出来。调试口要留测试点串口引导的引脚要留升级用的通信通道也要在硬件设计上确认可用。我见过太多项目研发阶段图省事没留测试点到了量产阶段发现治具没地方压探针只能改板一改板就是几万块的模具费和几周的周期。5.2 小批量试产ISP 往往够用几十片到几百片这个量级专门做一套 ICP 治具其实不划算。治具的设计费、加工费、调试时间加起来可能比人工烧录的成本还高。这个阶段直接用调试器一片片烧或者做个简单的夹具手工压探针完全够用。这个阶段真正要做的准备是把烧录流程文档化。写清楚用哪个工具、哪个版本、什么配置、烧完怎么校验、怎么记录。文档化的目的是让第二个人也能照着做而不是所有事都压在一个人身上。我见过一个小团队烧录流程只有一个人会那个人休假的时候产线就停摆这种事在小批量阶段就该避免。5.3 量产阶段ICP 打底、ISP 兜底到了千片以上的量级ICP 的经济性就出来了。治具的成本被摊薄并行烧录把节拍压下来脱机烧录器减少了对电脑的依赖防呆流程降低了人为错误。这时候该投入就投入。但要注意ICP 不能是唯一的路。产线上总会有那么几片板子因为各种原因没烧成功或者烧完发现功能异常需要重烧。这时候如果只有 ICP 治具一条路就很被动——治具可能已经拆了探针可能已经磨损了。所以量产方案里必须保留一条 ISP 的救急通道哪怕只是几根飞线加一个调试器。5.4 售后升级IAP 是刚需但要有回滚设备卖出去之后想改程序只有 IAP 一条路。这时候设备可能装在客户现场可能在高处可能在封闭机柜里你不可能派人去拆机烧录。远程升级不是锦上添花是必须能做的事。但 IAP 的设计目标不是能升级而是升级失败也不影响使用。这两者的差别很大。我建议所有做 IAP 的方案第一版就把回滚机制做进去哪怕用最简单的双区方案。因为升级失败的场景太多了网络中断、电源掉电、固件本身有 bug、客户中途断电……每一种都可能发生而且发生的时候你不在现场。5.5 一张决策表收尾把上面的判断浓缩成一张表遇到新项目的时候对着填就行。场景首选方案备选关键准备研发调试调试口 ISP串口引导 ISP预留调试测试点小批量试产500 片调试口 ISP 夹具手工烧录流程文档化中大批量1000 片ICP 治具 脱机烧录ISP 救急通道探针寿命管理、序列号管理现场售后升级IAP 双区回滚返厂 ISP掉电检测、校验、版本管理封闭式设备IAP预留隐藏调试口Bootloader 写保护表里封闭式设备那一行值得多说一句。有些产品的结构设计是完全密封的出厂之后连螺丝都拧不开。这种产品如果 IAP 做砸了就只能整机报废。所以这类产品在设计阶段就要把 Bootloader 的可靠性拉到最高写保护必须开、校验必须做、回滚必须有、升级日志必须存。多花的那点 Flash 和开发时间比起整机报废的损失不值一提。6. 几个真踩过的坑以及当时的排查链路最后这部分不讲理论讲几个我自己栽过的跟头。每个都附上当时的排查过程因为排查思路比结论更有用——你遇到的坑未必和我一样但排查的方法是可以复用的。6.1 升级到一半断电设备彻底不启动这是最早做 IAP 时踩的坑。方案是单区升级收到新固件先擦除应用区再往里写。测试的时候一切正常直到有一次测试人员手贱在升级进度条走到一半的时候拔了电源。再上电设备停在 Bootloader 里怎么都不启动。排查过程先确认 Bootloader 是好的用调试器连上去看Bootloader 区数据完好说明没被擦到。再看应用区前一半是新固件后一半是空的校验值对不上。Bootloader 的逻辑是校验通过才跳转所以它老老实实不跳等在那里。问题在于Bootloader 没有重试机制它只是等等不到就一直等。修复方案分两层。第一层是加掉电检测擦写之前先读电源电压低于阈值就拒绝升级擦写过程中定期采样检测到跌落立刻中止。第二层是加半包恢复把升级过程分成若干块每块写完记录一个进度标志重启之后从上次的进度继续而不是从头再来。这两层加上之后同样的拔电测试做了二十次每次都能恢复。这件事给我的教训是IAP 的测试用例里中途断电必须是必测项而且要测几十次不是测一次。因为断电的时机是随机的测一次通过不代表没问题。6.2 向量表没重定位升级后一进中断就飞这个坑更隐蔽。升级功能做完之后功能测试全部通过跑了几天没出问题。然后某一天设备在客户现场跑着跑着就死机了重启能恢复但过一段时间又死。排查过程相当折磨。先怀疑是内存泄漏查了几天没查出来。后来在死机的时候用调试器抓现场发现程序停在了一个莫名其妙的地方反汇编一看是一段根本不该执行的代码。再看栈栈已经溢出了。继续往下追发现是某个中断进去之后跳到了 Bootloader 的中断处理函数地址而那块代码已经被应用区的数据覆盖了。根因很清楚应用区的启动代码里设置向量表偏移的那一句被放在了开中断之后。大部分时候没问题因为开中断到设置偏移之间只有几条指令中断来不及进来。但偶尔有一次中断恰好在那个窗口进来就跳错了。这种概率性 bug测试阶段根本复现不了只有到了现场、负载高、中断频繁的时候才暴露。修复很简单把设置向量表偏移的代码移到启动文件的最前面在所有外设初始化和开中断之前。修复之后跑了两个月没再复发。但这个坑教会我一件事启动顺序是有严格约束的不能凭感觉排。后来我养成了一个习惯每次新建工程先把启动流程写成注释放在文件头标清楚哪一步必须在哪一步之前改代码的时候对着看。6.3 读保护打开之后调试口再也连不上这个坑发生在量产阶段。为了防抄板量产固件里打开了最高级别的读保护。打开之后调试口失效这是预期行为。问题是返修的时候一块疑似芯片损坏的板子送到维修工位维修人员连着调试器死活连不上判定为芯片损坏直接换芯片。换了三块还是连不上才找到我。我去看了一下芯片没坏是读保护在起作用。这种状态下调试器需要先发一个特殊的解锁序列才能获得擦除权限而擦除会清掉包括读保护在内的所有配置。维修人员不知道这个流程自然连不上。这件事之后我在产线作业指导书里加了一节返修流程专门写清楚读保护状态下的解锁步骤并且把解锁工具和固件放在维修工位的电脑上。同时还做了一件事在每块板子的丝印或者标签上标注固件版本和是否开启读保护返修的时候一眼就能看出来该走哪套流程。6.4 低温环境下串口引导握手失败最后一个坑比较特殊。一批设备在北方客户现场冬天的时候产线反馈说烧录失败率明显升高同一批板子在南方工厂里烧得好好的。排查过程先排除固件问题同一版固件在工厂复现不了。再排除治具问题换了新探针还是一样。最后把环境温度调低到零下十度在恒温箱里复现了失败率大约三成。继续查发现失败发生在握手阶段上位机发同步字节芯片不回应答。用示波器抓波形发现芯片的串口发送引脚在低温下上升沿变缓波特率也有偏移。根因是板上用的晶振负载电容是普通温漂的型号低温下频偏超出了串口的容错范围芯片发出来的数据上位机解不出来。修复方案是把负载电容换成低温特性更好的型号同时在 Bootloader 里加了自适应波特率的功能——先发一个测试字符让上位机测量实际波特率再按实测值通信。这两项改完之后零下二十度也能稳定握手。这个坑的启示是实验室通过不等于现场通过环境因素要在设计阶段就考虑。消费类产品还好工业类、户外类产品温度、湿度、振动、电磁干扰每一样都可能成为烧录失败的原因。做方案的时候多问一句这东西最终装在什么环境里能省掉很多后期的麻烦。6.5 一个小技巧给每次烧录留个指纹最后分享一个成本极低但收益很高的做法。在固件的固定位置留一小块区域烧录的时候写入这次烧录的时间戳、工位号、固件版本号运行的时候可以通过通信接口读出来。这个东西看起来没什么用但排查问题的时候价值巨大。设备出问题第一件事就是确认这块板子是什么时候、在哪条线、烧的哪个版本有了指纹一秒就能查到。没有的话就得靠翻生产记录、对序列号折腾半天还不一定对得上。实现起来也简单烧录器支持在指定地址写入变量就行大部分脱机烧录器都有这个功能。存储空间占用也就几十个字节。我现在做的每个项目都会加这个已经变成标准动作了。