☰
ISP、ICP、IAP三种芯片烧录方式详解:从原理到实战
2026/9/29 3:31:52 网站建设 项目流程

1. 从一片空白芯片到程序跑起来,中间发生了什么

收到一片刚从出厂流水线上拿下来的MCU芯片,它和一块石头差不多——里面没有bootloader,没有应用程序,甚至Flash里全是0xFF。你要让它按照你的设计干活,第一步就是把编译好的hex或bin文件写进去。这个动作,行业里就叫芯片烧录(Programming),但真正干过这行的人都知道,烧录远不止“把文件拖进去”这么简单。

烧录方案选不对,后果从轻到重都有:轻则调试时天天插拔下载器,重则产线批量烧录效率低下,或者产品出货后没法远程升级只能召回。所以搞清楚ISP、ICP、IAP这三种烧录方式到底是什么、各自有什么脾气,是每一个玩嵌入式的新手都绕不过去的坎。

我在这个行业混了十几年,从早期的51单片机、AVR,到后来的STM32、GD32、HC32、新唐,再到FPGA的配置芯片,可以说每一种烧录方式我都实打实踩过坑。很多新手容易陷入一个误区:以为ISP和ICP只是叫法不同,实际上它们不只是名字不同,背后的硬件通道、适用场景、烧录速度、能否调试,差别都很大。而IAP更是直接决定产品能不能远程升级的命根子。

这篇文章我尽量用大白话,把ISP / ICP / IAP三兄弟扒个透彻。你不需要有很深的功底,只要知道单片机是什么、Flash是什么,基本就能读懂。我会结合STM32、GD32这些市面上最常见的芯片来举例,把原理、接线、操作步骤、选型思路和踩坑经验一次讲明白。

2. ISP烧录:串口里走出来的“出厂引导模式”

2.1 ISP的本质:芯片出厂自带的一段神秘小程序

ISP的全称是In-System Programming,中文叫在系统编程。意思是芯片已经焊在电路板上了,你不用把它拆下来,就能通过某个通信接口往里写程序。

它背后的原理其实很简单:芯片厂商在出厂时,往芯片内部一段特殊的存储区域(比如STM32叫System Memory系统存储器)里,预先烧死了一段bootloader引导程序。这段程序在芯片上电时可以被激活,激活后它会通过某个固定的通信接口(最常见的是UART串口)接收上位机发来的数据,然后擦除用户Flash、写入新程序。

打个比方:ISP就像你买了一台带“恢复模式”的手机。手机本身有个出厂自带的恢复系统,你不需要拆开后盖,只需要按住特定按键组合进入恢复模式,然后用数据线连接电脑,就能刷机。这里的“恢复模式”就是芯片的System Memory,数据线就是UART,电脑上的刷机工具就是ISP上位机软件。

2.2 STM32进入ISP模式的具体操作

以最常见的STM32F103为例,进入ISP模式的条件是:

  1. 把BOOT0引脚拉高(接到3.3V),BOOT1引脚拉低(接地)。
  2. 保持这个状态,给芯片上电复位(或按复位键)。
  3. 芯片启动时检测到BOOT0为高电平,就会从System Memory启动,而不是从用户Flash启动。
  4. 这时候你用USB转TTL模块连接芯片的USART1(PA9为TX、PA10为RX),打开上位机软件(STM32CubeProgrammer或FlyMCU),选择对应串口号,就能识别到芯片。
  5. 加载hex或bin文件,点击下载,程序就写进去了。

烧完之后别忘了把BOOT0跳线接回低电平,再复位一次,芯片才会从你刚烧写的Flash启动运行。

2.3 一个容易被忽略的关键点:ISP也分“出厂固定”和“用户自写”

STM32这种芯片,ISP用的bootloader是ST在出厂时就固化在System Memory里的,用户改不了、也擦不掉。它只支持固定那几个串口(比如F103的USART1),固定波特率协商逻辑,固定通信协议。你想让它从串口3接收数据?做不到,协议是死的。

而像某些国产芯片,或者一些老派MCU(比如STC),ISP的bootloader也是出厂自带的,但工作机制不同。玩过STC单片机的人应该都知道,STC是通过串口下载,而且它的ISP下载工具普遍有一个烦人的弹窗提示——这就是网上经常搜到“stc isp去弹窗”的原因。STC的ISP下载逻辑是:点击下载按钮后,给目标板上电,芯片内部的出厂引导程序在上电瞬间监听串口数据,收到合法的下载帧就进入编程模式。所以STC下载的经典操作顺序是:先点“下载”,再给板子上电,如果反了就永远下不进去。

这两种ISP机制都叫ISP,但交互时序完全不同。你如果从一个平台切到另一个平台,最容易犯的错就是把“先上电再点下载”的习惯带过去,或者反过来,然后对着串口数据一脸茫然。

2.4 ISP的优缺点与适用场景

ISP最大的优势是:只需要占用一个串口,不需要专门的下载器。产线上只要有USB转TTL模块和电脑,就能批量烧录。对成本敏感的小批量产品来说,ISP几乎是首选。

但它的缺点也很明显:

  • 速度慢。相比SWD的几MB/s,UART的ISP典型速度在115200bps到1.5Mbps左右,一个几百KB的固件要好几分钟。
  • 依赖启动引脚状态。需要BOOT0/BOOT1引脚的外部接线配合,产品量产后如果板子上没引出这两个引脚,ISP就基本废了。
  • 不能在线调试。ISP只负责把程序写进去,写完之后你想单步调试、打断点?抱歉,没有这功能。
  • 引导程序是固定的。你没法扩展ISP的功能,比如加个加密传输、加个校验和算法,因为出厂那段代码是死的。

所以ISP适合的场景是:开发调试初期、小批量产线烧录、现场维护时重新烧录(只要有串口接口)。

3. ICP烧录:用调试口直接“硬写Flash”

3.1 ICP的实质:通过SWD/JTAG访问芯片内部

ICP的全称是In-Circuit Programming,中文叫在线编程。名字里也有一个“在”,但它和ISP完全是两条路。ICP是通过芯片的调试接口(Debug Port)——最常见的两种是ARM内核的SWD(两线:SWDIO和SWCLK)和JTAG(四线或五线)——直接访问芯片内部的调试访问组件(DAP),然后通过DAP操作Flash控制器,完成擦除和写入。

翻译成人话:ICP不是靠芯片里出厂自带的bootloader,而是靠芯片硅片上硬件实现的调试端口。你用一根几块钱的ST-Link或者几十块钱的J-Link,在IDE(比如Keil、IAR)里点一下Download,几秒钟搞定,这背后走的就是ICP通道。

还是用手机来类比:ISP是进恢复模式刷机,ICP则是直接用工程线连上手机内部的硬件调试口(相当于拆开后盖对主板操作)。前者是软件层级的引导,后者是硬件层级的直接访问。所以ICP不需要芯片支持什么特定bootloader——只要芯片有SWD/JTAG接口,哪怕芯片里的Flash被锁死或者程序跑飞了,你都能通过ICP把它救回来,这就是为什么SWD口被称为“嵌入式工程师的救命稻草”。

3.2 ICP与ISP的差异,用一张表格彻底分清

很多人在这里概念打架,我干脆用表格把差异列清楚,你直接收藏就行:

对比项ISPICP
全称In-System ProgrammingIn-Circuit Programming
物理通道UART串口(或其他串行口)SWD/JTAG调试口
依赖出厂bootloader依赖,且不可改不依赖,直接硬件操作
启动引脚要求需要BOOT0/BOOT1配合不需要,连接调试口即可
下载速度慢(几十KB/s量级)快(可达MB/s量级)
在线调试能力无有,可打断点、单步
芯片资源占用烧录期间占用串口烧录期间占用调试口
典型工具STM32CubeProgrammer(串口模式)、FlyMCUST-Link、J-Link、DAP-Link
量产效率一般高,且支持脱机烧录器

重点说一下“脱机烧录”这个事。在真正的量产产线上,ICP方式的效率优势极其明显。一台脱机烧录器(比如树莓派Pico自带的SWD接口、或者专门的量产编程器)可以对着一批芯片连续烧录,不需要电脑,按个键就烧一台。如果固件加密做得好,产线上甚至不需要把源码或hex文件发给代工厂,只需要把加密后的烧录镜像放到烧录器里就行。这是ISP做不到的。

3.3 为什么说ICP是调试阶段的“默认选项”

我个人的习惯是:任何基于ARM Cortex-M内核的项目,开发调试阶段一律首选JTAG/SWD(也就是ICP方式)。原因有三个:

第一,编写代码时你会频繁修改、频繁下载。ICP一下载完,马上就能按F5进入调试,打断点看变量。ISP模式下载完还得重新复位、重新进入Debug,来回切换,烦都烦死。

第二,ICP不挑芯片状态。哪怕你程序里把时钟配置错了、把引脚全部置成推挽输出然后强行短接,芯片跑飞了、死机了,只要SWD口没被禁用,ST-Link都能在连接时把芯片“按住”,擦掉坏程序重新来。ISP虽然在恢复模式下也能擦,但由于它依赖启动引脚的硬件接线,如果板子设计初期没把BOOT引脚留出来,那就真叫天天不应了。

第三,ICP可以读写芯片的选项字节(Option Bytes),也就是配置读保护、写保护、硬件看门狗等。ISP模式下,很多MCU的选项字节访问能力受限,或者根本不允许改。做产品量产时,你总会需要打开读保护(RDP)的,这时候ICP几乎是必经之路。

顺便提一个容易踩的坑:如果打开了读保护级别1(RDP Level 1),再用ISP串口模式去连接,很多MCU会拒绝连接或者只能做全片擦除。ST官方的说法是,RDP Level 1开启后,系统存储器bootloader仍然可以工作,但只能执行整片擦除(mass erase),不能读取Flash内容。如果你产线流程里是先烧录再开保护,那无所谓;但如果你是先开了保护再想用ISP补烧一个什么串口bootloader,那就这个不行、那个不行,最后只能老老实实拉JTAG/SWD出来全擦。

4. IAP升级:让产品长出“自我更新”的本事

4.1 把bootloader写进用户Flash——这就是IAP的关键

IAP的全称是In-Application Programming,中文翻译成在应用编程。注意这里跟ISP有个微妙的不同:ISP是“系统内编程”,由出厂固化的引导程序主导;IAP是“应用内编程”,主导者是你自己写的、烧在用户Flash里的应用程序。

你可以在自己的应用程序里,通过串口、USB、以太网、CAN总线、甚至是蓝牙Wi-Fi,接收一份新的固件镜像,然后把自己Flash里的程序区擦掉、写入新数据、跳转运行。这个过程就是IAP。

核心点在于:IAP的bootloader不是芯片出厂自带的,是你自己写的,放在Flash的固定区域(比如STM32F103的0x08000000起始处),烧录时通过ISP或ICP方式把它和用户App一起烧进去。之后每次升级,就不再需要ISP/ICP下载器了,只需要通过通信接口传输新固件,IAP bootloader负责接收并写入App区。

所以IAP和ISP/IAP的区别一句话就能说清:

  • ISP和ICP是“外部工具动手”,离线烧录。
  • IAP是“芯片内部自己的程序动手”,自己烧自己。

用手机类比:ISP和ICP相当于你用电脑数据线刷机;IAP相当于手机系统里的“在线OTA升级”——你手机上运行的系统里有一个升级程序,它通过网络下载新系统包,然后自己把自己升级掉。这个升级程序就是你写的那段bootloader。

4.2 芯片复位后的启动流程:先跑boot,还是先跑app?

要搞懂IAP,必须搞懂一个概念:程序从复位到运行的启动顺序。

STM32/GD32这类芯片复位后,CPU从0x08000000地址开始取指令。Flash里第4个字节(偏移0x04)存放的是复位中断向量地址,CPU从这里拿到复位向量,跳到对应地址执行代码。如果你把bootloader放在0x08000000,把App放在0x08008000(比如偏移32KB),启动顺序就是:

  1. 芯片复位,从0x08000000执行bootloader。
  2. bootloader初始化外设(比如串口),检查某个标志位(比如按键是否按下、串口是否有升级命令、Flash某区域是否存有“升级请求”标记)。
  3. 如果没有升级请求,bootloader直接跳转到App区起始地址(0x08008000),开始执行你的应用程序。
  4. 如果有升级请求,bootloader就进入升级模式,通过通信接口接收新固件,写入App区,写完校验通过后,再跳到App或复位让App启动。

这个“跳转”不是简单的函数调用,你要做几件关键事:

  • 在跳转前关闭全局中断(__disable_irq()),否则中断向量还在bootloader的向量表里,一旦中断进来就乱了。
  • 把App区的起始地址写入栈指针寄存器(MSP),保证App的栈顶正确。
  • 把App区复位向量地址写入程序计数器(PC),触发App的复位中断。

具体代码一般长这样(以Cortex-M为例):

/* 跳转到App */ typedef void (*pFunction)(void); uint32_t app_entry = *(volatile uint32_t*)APP_ADDR_OFFSET; // 取App地址偏移4字节的复位向量 pFunction jump_to_app = (pFunction)app_entry; __disable_irq(); /* 设置主栈指针 */ __set_MSP(*(volatile uint32_t*)APP_ADDR); /* 跳转 */ jump_to_app();

很多新手在写这段跳转时遇到坑,最典型的是:App里明明能跑,但只要bootloader一跳转过去就死机。大部分情况都是因为跳转前没有更新中断向量表偏移(SCB->VTOR),App里面的中断处理函数找不到正确的入口,一进中断就跑飞。在Cortex-M3/M4上,App初始化最开始就要设置:

SCB->VTOR = APP_ADDR; // 告诉内核中断向量表基地址在App区

4.3 热搜词里那些闪光的真实问题:boot变量复位后去哪了

网上经常搜到“iap boot里面定义的变量复位后会怎样”,这个问题问得非常实在,我展开讲讲。

在IAP体系里,bootloader和App是两个独立的程序,它们编译时各自的链接脚本(LD文件)会把内存区划分开来。bootloader运行时定义在RAM里的变量,在App跳转启动后会怎样?

答案是:变量的值大概率还在,但你不能指望它还在。芯片复位后,启动代码(startup文件里的SystemInit和__main序列)会对全局变量做初始化:未初始化的清零,有初始值的从Flash拷贝到RAM。App程序一启动,它自己的启动代码就会把这些变量重新初始化一遍。

也就是说,bootloader里运行期间设置的标志位、缓存的数据,在App重新初始化之后全部无效了。如果你想让bootloader给App传一些信息(比如“是从升级模式跳过来的”还是“正常上电启动”),不能简单地靠普通全局变量传递。

正确的做法有几种:

  1. 用带有noinit属性的变量段:在链接脚本里额外划出一块RAM区域,标记为不初始化(NO_INIT),这种变量在上电/复位后不会自动清零,bootloader写入的值可以在App启动后读出来。比如在Keil里用__attribute__((section(".noinit")))或者__no_init关键字。

  2. 放在备份寄存器里:Cortex-M配合的RTC备份寄存器(比如STM32的BKR寄存器)在系统复位后不会丢数据,可以用来传标志。

  3. 写入Flash的特定扇区:最稳妥但开销最大,bootloader升级前把一个标识写入Flash某个固定地址,App启动时读取这个标识,然后按需处理。但Flash有擦写寿命限制,不能频繁写。

  4. 使用系统复位还是软件跳转的区别:如果你选择用NVIC_SystemReset()复位而不是直接跳转,那么bootloader阶段的所有RAM内容都会被重初始化,唯一能保留的是RTC备份寄存器或noinit段,因为启动代码不会清它们(至少你的启动代码通常不会清它们)。

所以很多成熟的IAP设计,其实是bootloader不直接跳转,而是设置一个标志,然后执行软复位,让App从最开始干干净净地启动。这样App里的外设初始化时序最可靠,不会因为某个外设寄存器在bootloader里被动过而在App里出现奇怪的问题。

4.4 实打实的IAP工程架构搭建:以GD32F103为例

GD32F103是目前国产替代STM32F103最常见的选手,二者引脚兼容、大部分寄存器兼容,但Flash映射和启动细节有细微差别。前段时间我给客户做GD32F103的IAP升级方案,这里把架构分享一下。

首先规划Flash分区。假设GD32F103C8T6,Flash共64KB,扇区大小为1KB(共64个扇区)。一个常见方案是:

区域地址范围大小内容
Bootloader0x08000000 ~ 0x08003FFF16KBIAP引导程序,负责接收和写入
App区域0x08004000 ~ 0x0800EFFF44KB用户应用程序
配置参数区0x0800F000 ~ 0x0800FFFF4KB保存升级标志、板卡序列号等

同时要修改App工程的链接脚本,让App的起始地址变成0x08004000,Flash长度变成44K。在Keil里就是修改Target选项卡里的IROM1 Start和Size,在GCC里则改ld文件的ORIGIN和LENGTH。

App工程里,还要在system_gd32f103.c,模拟STM32的写法设置VECT_TAB_OFFSET:

/* 如果App启地址不是0x08000000,需要设置中断向量偏移 */ #define VECT_TAB_OFFSET 0x4000 SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET;

GD32和STM32的差异点在于:GD32的Flash擦除是按扇区来,对比STM32F103某些型号的按大小页擦除,擦除粒度不同;另外GD32的Flash写入需要先解锁、等待BSY标志位清零,时序要求和ST略有差异。如果你用ST的HAL库跑GD32,某些Flash操作在边界条件下会失败,所以IAP的Flash驱动我用的是GD32标准库原生的驱动,没再继续用ST的HAL。

升级应用层协议建议简单点:上位机发一个固定帧头(比如0xAA55)+固件包序号(2字节)+单包数据(256字节)+CRC32校验(4字节)。bootloader每收到一帧就写一次Flash,边收边写,最后统一校验。我见过不少人在这个环节偷懒,收到全部数据再一次性写Flash,那样Flash占用大不说,万一中断了都不知道写到哪了,恢复巨麻烦。边收边写配合“页擦除再写”的流程,写坏了也最多坏一个页,下次重传即可。

5. ISP、ICP、IAP在实际项目中怎么选型搭配

5.1 三者的关系不是互斥,而是协作

先说个结论:一个成熟的产品,通常三样都用得上。

在开发阶段,你100%用ICP(SWD/JTAG),因为要调试。等固件功能稳定了,量产烧录富昌直接用脱机烧录器走SWD批量写,快且稳定;如果产线没有电脑但有几块钱的USB转TTL,走ISP也能凑合。产品交付给客户后,为了让现场人员或用户自己能更新功能,你必须在出厂固件里带上IAP升级能力,通过UART/CAN/4G等通道远程或本地升级。

我做过一个实际项目:一个工业控制器,用的是HC32L136,Cortex-M0+内核,64KB Flash。量产阶段是我自己写了个PC端烧录工具,通过SWD协议直接下载,一台设备1分钟左右烧完。交付后客户需要现场升级,现场没有电脑怎么办?我给设备加了一个SD卡槽,IAP逻辑做在App里:检测SD卡根目录有没有update.bin文件,有就搬进内置Flash的新App区,搬完做CRC校验,然后跳转。客户只需要把升级文件拷进SD卡、插上去、断电重启,就完成升级了。这就是ISP/ICP和IAP配合的典型形态。

5.2 不同MCU的IAP支持情况差异很大

很多新手以为“既然叫IAP,那是不是所有芯片都支持?”——完全不是。IAP的实现完全取决于芯片Flash是否支持自身读写(self-programming)。早期一些芯片的Flash控制器不允许在执行代码的同时写入Flash的不同分区,需要特定指令序列或者需要把代码拷贝到RAM里执行。现在ARM Cortex-M内核芯片普遍支持Flash原位写入,但细节差异很大:

  • STM32F1系列:能自编程,但Flash读写时CPU要等待总线访问完成,速度较慢,且必须按半字(16位)对齐写入。
  • GD32F1系列:Flash写入也支持自编程,但和ST有一些微妙差异,我之前提过。
  • HC32L136:支持IAP,但有它的特殊之处——它的Flash写入命令需要进入特定模式,它的向量表偏移支持程度也有限,App中断处理要特别小心。
  • NXP LPC系列:很多型号内部有独立的IAP固件API入口(通过地址调用),同ISP的引导程序是合在一起的,你甚至可以在App里调用它的IAP API写Flash,但要注意API地址在不同系列器件间不通用。
  • STM32H750VBT6:这个芯片特别有意思,它虽然叫H7,但内置Flash只有128KB,经常有人拿它外挂QSPI Flash跑程序,它的IAP设计不仅要处理内部Flash,还要考虑外部存储器映射。热搜词里能看到“stm32h750vbt6 iap”,说明不少人在玩这个方案。

遇到这些差异,唯一靠得住的方法就是:先查参考手册的Flash控制器章节,或者直接到芯片原厂网站上找“Application Note”和“Bootloader设计示例”,照着最接近官方例程的框架改。千万别想当然地用别家代码硬套,那纯粹是给自己找坑。

5.3 选型时还必须想清楚电源、引脚和升级安全性

除了芯片本身,IAP方案的设计还会牵扯到几个“非编程本身”的问题。

第一是升级过程中断电怎么办。IAP写入Flash期间,如果突然断电,Flash里可能写了一半,App区被破坏了,设备就变砖了。常见的解法是双区(A/B分区)设计:始终保留一个能启动的App区,新固件写入另一个区,全部写完后切换启动标志。这个方案的代价是Flash翻倍——如果你的芯片只有64KB Flash、实际App需要50KB,那双区就塞不下了。退而求其次,可以在App区的头部放一个“固件有效标志”,bootloader每次启动时先检查这个标志,发现不对就停在bootloader模式等待重新升级,不跳App。这样至少不会变砖,只是需要现场重新升级。

第二是升级过程的功耗和供电。很多无线产品的IAP是通过蓝牙或433MHz链路传固件的。无线模块在传输大文件时时功耗比较高,如果你的系统是电池供电,要评估传输过程中把整机功耗压在什么水平,必要时可以进入低功耗模式配合。曾经有个客户做NB-IoT远程升级1MB固件,传了20多分钟,电池电压掉得离谱,最后优化成差异包升级(只传变更的部分),传输量从1MB降到80KB,问题就解决了。

第三是还原保护引脚。有些设计为了省事,把BOOT0/BOOT1引脚直接悬空或接了固定电平。如果产品在售后阶段需要“现场救砖”,你发现根本没引出这些引脚,那就只能焊线。所以PCB上哪怕不接跳线,我也习惯把BOOT0、NRST、GND、SWDIO、SWCLK这几个引脚用测试点方式引出来,一毛钱成本,关键时刻救命。

6. 避坑实录:三种烧录方式实操中最容易翻车的几个细节

6.1 ISP烧录时常见的“连不上设备”场景与逐一排查

连不上MCU是ISP新手遇到的第一座大山,常见原因排序如下:

  • 启动引脚状态不对。最基础但也是犯错率最高的。STM32F103必须BOOT0=1才能进入ISP,不要以为BOOT1也需要拉高,很多芯片BOOT1只需低电平即可。GD32的BOOT配置和ST基本上同套路。老练的工程师哪怕程序里注释写了,也会真拿万用表测一下BOOT0引脚电压,而不是靠眼睛看跳线帽有没有插对。
  • 串口TX/RX交叉接反。这是USART通信第一铁律:上位机的TXD必须接芯片的RXD(PA10),上位机的RXD接芯片的TXD(PA9)。新手经常一头怼到USB转TTL盲区就直接下指令,结果收不到。有些人图省事直接飞线到引脚不核对丝印就开干,最后烧不进程序还怀疑芯片坏了。
  • 目标板电压与USB转TTL不一致。很多USB转TTL模块是3.3V供电/电平,有些是5V的。如果MCU供电3.3V,你拿一个5V电平的TTL直连MCU串口引脚,大概率会损伤甚至烧毁引脚和芯片。稳妥做法是选带电平转换的模块,或者干脆用SP3232之类的RS232方案。
  • 没有正确复位进入ISP模式。飞梭STC那类“点下载再上电”的流程大家听得多,但STM32这类是“上电时检测BOOT脚状态,决定启动源”。你需要先设置好BOOT脚,再按复位键进入ISP模式,不是连上串口就万事大吉。
  • 上位机软件设置错端口。STM32CubeProgrammer新版本支持很多接口,如果不小心把“UART”选成“ST-LINK”模式,那自然连不上。连接前要确保串口被正确枚举、波特率设置不超出芯片支持范围。

6.2 ICP下载时最闹心的“无法连接目标设备”问题

SWD连不上,比ISP连不上更让老手头疼。通常排查顺序是:

  • 先查供电:用万用表量VDD是否为标称电压,量NRST是否为高电平。很多芯片供电不足或电压纹波大时,SWD引脚是拉不上来的。
  • 查SWDIO/SWCLK是否被复用:如果你的程序把SWDIO复用成了GPIO输出,或者设置了读保护RDP Level 2,ST-Link就废了。这时只能用ISP把全片擦掉再抢救。所以我很早就强调:不要轻易开RDP Level 2,开之前确保量产和维修流程完全定稿。
  • 查接线和连接器:杜邦线过长、手接触不良、调试器接触电阻大,这些平时看着不起眼的细节在高速SWD时钟下直接坏死。我一般把SWD时钟降到1MHz以下排查,连上了再把速度提上去。
  • 查目标芯片是否已经被锁死:以STM32的Flash Level 0(无保护)为例,你可以随便刷;Level 1(读保护)下,SWD可以连接但只能整体擦除后重写;Level 2(禁止调试)下,物理上就无法连接了,除了改成ISP方式或换芯片基本无解。

6.3 IAP升级后App死机:三个高频根因

IAP跳转死机的根因总结下来,90%都是这三点:

一是没有做中断向量表偏移。App在bootloader直接跳过去后,一进中断就找不到处理函数。排查时可以屏蔽所有中断,手写一个main死循环,如果这样能跑,那基本就是VTOR的事。CMSIS内核代码里有现成的SCB->VTOR寄存器定义,App启动处加上一行就够了。

二是跳转前没有关中断、清中断挂起。如果外设挂在升级过程中产生了中断请求,跳转时中断没有关,App一启动就跳进异常向量表入口,内存还会错乱。跳转前记得__disable_irq(),跳过去之后App启动代码自己会再开启中断。

三是App工程里链接脚本没有改。很多人只改了IROM1起始地址,忘了改Size,把App区写超了,结果覆盖了bootloader区。或者忘了在初始化里把VTOR改到App起始位置。这些编译时不会报错,只有跑起来才会发现异常。

6.4 一个我踩得最深的坑的经历

去年做的一个项目,MCU是HC32L136,Flash 64KB。客户要求OTA升级,我用的是双区方案:bootloader 16KB + App1 24KB + App2 24KB。一切正常,直到我调试升级失败的断电恢复时发现——bootloader在某些崩溃场景下会反复跳回App1,App1里面检测到升级标志又要求重新升级,但Flash里根本没有新固件包,设备就陷入了“升级启动-失败-复位-再升级”的死循环。

排查了很久才发现是我在bootloader里判断“是否需要升级”的条件不够严格:只要检测到某个RAM标志位为特定值就进入升级模式,但这个普通RAM变量在软复位后仍然是上电初始化后的值,既不是旧值也不是合法值,导致逻辑错乱。后来我把“升级请求”改成一个特定的Flash配置区字段(长度4字节的魔数+固件版本),每次App发起升级前先写这个字段,bootloader启动时只有读到完全匹配的魔数才进入升级模式,否则一律正常跳App。加了一个超时退出机制:进入升级模式后如果在30秒内没有收到合法固件帧头,自动复位退出升级模式。这个问题才算真正根治。

这个经验后来我在好几个项目里都用到了:任何IAP的握手标志都不要依赖RAM变量,要用Flash或带电池的备份寄存器;任何升级流程都要有超时兜底,防止设备变成“升级死循环砖”。

7. 无边界的延伸:ISP在图像领域是另一个概念,别搞混了

既然标题里的ISP是个高频词,我不妨多说一句:在图像处理领域,ISP指的是Image Signal Processor(图像信号处理器),而不是本文讲的In-System Programming。如果你搜“isp pipeline”“isp图像处理”,出来的都是CMOS传感器图像信号处理流水线的内容,和芯片烧录毫不相干。

这个混淆不是少见,在我带过的团队里,有一次一个新来的实习生看到芯片规格书里写“内置ISP”,就跑去研究怎么用串口烧录,查了半天才发现人家说的是图形处理单元。

所以当你看到“fpga isp”这个词时,大多时候指的是FPGA内部实现的图像处理算子模块(如降噪、HDR融合、色彩校正),而不是FPGA里的系统烧录逻辑。但在某些上下文里,“fpga isp”也可能指FPGA的在线配置(in-system programming),比如通过JTAG对FPGA进行配置。这种一词两义在行业里很普遍,只能靠上下文判断。写代码、查资料、跟人聊天时,先确认语境再下手,能省下大量乌龙时间。

从另一个角度说,这种高质量的“一词多义”提醒我们:做嵌入式这行,搜索能力和阅读原文手册的能力,永远比背诵概念重要。遇到一个新词,第一件事不是翻译,而是搞清楚它在当前语境里的指代是什么。

8. 折腾这几样东西多了,我最后的实用心得

文章写到这里,我不打算给你做一份“总结清单”。我只想分享几个作为过来人、用了十几年烧录方式后的真实体会。

第一,刚开始学单片机,一定要趁早把三样工具都备齐:一个ST-Link/J-Link用于ICP下载和调试,一根USB转TTL线用于ISP下载,以及一块带BOOT跳线设计的开发板用于玩ISP/IAP。这三样加起来不到100块钱,但能帮你把三种烧录机制的物理边界彻底摸清。只有亲手经历一次“BOOT0拉高进ISP”和“SWD直接下载”的区别,你才会真正理解为什么很多老工程师对BOOT0引脚那么执着。

第二,IAP关系到产品能不能远程升级,直接决定售后成本,所以在产品设计阶段就把它考虑进Flash分区和Boot引脚规划,别等固件写完、PCB定稿了再想。很多公司到了出货前才临时加IAP功能,结果Flash不够用、引脚没引出来,最后只能花成倍的改版成本去补救。

第三,所有带Flash自编程功能的产品,都要面对“写入中途断电”的灾难场景。无论你用的是双区方案、标志位方案还是bootloader超时方案,一定要把断电恢复逻辑当成一等公民来设计,而不是“以后再说”。我自己因为吃过亏,现在写IAP代码的第一版就把超时、断电、校验失败这些异常路径全部画出来,再写正常路径。这个习惯救了我不止一次。

芯片烧录看似是一个底层的、不起眼的环节,但它贯穿了整个嵌入式产品的一生:从最初的开发调试、到产线批量烧录、再到用户手里的远程升级。你把ISP/ICP/IAP这三条路想清楚、走通了,一个项目最基础的“程序生命周期管理”就稳了一大半。剩下的那些坑,踩一次长一次记性,正常。

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

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

立即咨询