☰
ISP、ICP、IAP怎么选?芯片烧录与固件升级工程实践
2026/10/28 13:29:37 网站建设 项目流程

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 一个小技巧:给每次烧录留个"指纹"

最后分享一个成本极低但收益很高的做法。在固件的固定位置留一小块区域,烧录的时候写入这次烧录的时间戳、工位号、固件版本号,运行的时候可以通过通信接口读出来。

这个东西看起来没什么用,但排查问题的时候价值巨大。设备出问题,第一件事就是确认"这块板子是什么时候、在哪条线、烧的哪个版本",有了指纹,一秒就能查到。没有的话,就得靠翻生产记录、对序列号,折腾半天还不一定对得上。

实现起来也简单:烧录器支持在指定地址写入变量就行,大部分脱机烧录器都有这个功能。存储空间占用也就几十个字节。我现在做的每个项目都会加这个,已经变成标准动作了。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询