ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

芯片烧录全解析:ICP、ISP、IAP三种方式与实战避坑

芯片烧录全解析:ICP、ISP、IAP三种方式与实战避坑 芯片烧录这个词很多刚入行的朋友第一反应是“把程序写进芯片就完事”但真正干过几轮硬件调试后你会发现烧录这件事背后有一整套门道ISP、ICP、IAP三种方式分别解决什么问题、什么场景该用哪一种、为什么明明代码没问题却烧不进去这些问题如果不提前搞清楚很容易在样板阶段就卡住进度。这篇文章我不打算讲得太教科书化就用做项目时的真实视角把芯片烧录的本质、三种模式的原理和区别、实际踩过的坑以及IAP升级里那些容易被忽略的细节一次说清。不管是刚接触单片机的新手还是被现场升级问题折磨过的工程师读完应该都能对“怎么把固件弄进芯片”这件事建立起完整的判断框架。1. 先从“烧录”这件事说起1.1 烧录的本质把编译好的二进制放进Flash很多人第一次用开发板点一下编译、再点一下下载程序就跑了感觉整个过程特别简单。但“烧录”这个词背后的关键动作并不是“传输”而是“写入存储器”。微控制器内部用来保存代码的是Flash存储器。我们在Keil、IAR、GCC这些工具里写完C代码经过编译、链接之后会生成两种最常见的文件格式HEX和BIN。HEX文件是带有地址信息的文本格式每一行都告诉你这段数据该写到Flash的哪个地址BIN文件则是纯二进制数据流一个字节接一个字节地对应Flash中的存储内容。烧录器把Flash当作一张可以擦写的“草稿纸”先把对应的扇区擦除再把程序数据按地址写入最后做一次校验确保写入的内容和原始固件完全一致。以STM32F103为例它的内部Flash起始地址是0x08000000编译出来的程序就是从0x08000000开始存放的。烧录器会按芯片手册的时序把二进制内容从这个地址开始逐个写入这个过程严格来说叫Flash编程。擦除、写入、校验这三个动作缺一不可任何一步失败程序都可能跑飞。1.2 为什么叫“烧录”而不是“复制”“烧录”这个词有点历史感。早期有一类可编程存储器叫EPROM里面的数据是靠紫外线照射擦除的芯片上有一个石英窗口每次修改代码都得用专门的紫外线灯“晒”上十几分钟才能擦干净。更早还有熔丝型PROM编程时是真的会把芯片内部的熔丝“烧断”用物理烧断的方式记录数据。所以“烧”这个字是从硬件层面真实发生过的事情演变来的。现代单片机虽然早就不用这种暴力方式了但术语保留了下来。现在说的烧录本质上就是擦除写入校验。理解了这个历史背景你就知道为什么芯片擦写是有次数限制的比如NOR Flash通常标称10万次擦写寿命虽然日常工作根本用不完但在做产品升级逻辑设计时还是要尽量避免频繁整片擦写。2. 核心概念拆解ICP、ISP、IAP三种模式到底差在哪2.1 ICP用烧录器直接操作芯片开发调试和量产的主力ICP的全称是In-Circuit Programming直译是“在电路编程”也就是把烧录器通过调试接口直接连到目标芯片上由外部工具主导擦写Flash。这是最常见、最直接的一种烧录方式开发阶段几乎天天都在用。常见接口有两类一类是JTAG四线制TMS、TCK、TDI、TDO速度快但占用的引脚多另一类是SWD两线制SWDIO、SWCLK在ARM芯片上非常主流只需要两根信号线加电源和地。ST-Link、J-Link、DAP-Link这些调试器就是通过SWD接口实现程序下载和在线调试的。SWD连接说起来很简单就是四根线调试器端芯片端作用SWDIOPA13 / SWDIO双向数据线SWCLKPA14 / SWCLK时钟线GNDGND共地基准3V3VDD供电或参考电平实际项目中容易出现一个很有意思的坑有些工程师为了省事只接SWDIO、SWCLK和GND三根线不接目标板的供电靠调试器给板子供电结果大电流器件一启动就把电压拉垮烧录反复失败。我的建议是能接电源就接电源让烧录器只做通信不承担供电任务这样排查问题会简单很多。ICP还有一个独特优势是“不依赖芯片里是否已有代码”。哪怕芯片是全新的、内部Flash是空的只要物理连接没问题就能烧录。开发阶段改代码、量产前写序列号、读取芯片Flash内容做备份基本都是ICP干的活。2.2 ISP借芯片自带Bootloader完成串口下载成本低但有限制ISP的全称是In-System Programming直译是“在系统编程”。它不再需要外部烧录器而是利用芯片出厂时固化在ROM里的一段引导程序Bootloader通过串口、USB、SPI、CAN等通信接口把新的固件接收进来并写入Flash。用STM32举一个很典型的例子。芯片内部有一个“系统存储器”System Memory里面固化了一段ISP Bootloader它的启动条件是特定组合的BOOT引脚电平。比如STM32F1系列把BOOT0拉高、BOOT1拉低复位后芯片就会从系统存储器启动而不是从主Flash启动。这时用USB转TTL工具连上串口1配合ST官方工具或第三方软件就能通过串口把固件下发到芯片Flash里。为什么要设计ISP这种方式两个直接好处。第一生产阶段不需要购买昂贵的烧录器一条USB转串口线就能批量下载程序第二芯片已经焊到板子上也能烧录不用像早期那样先烧好芯片再贴片。很多8位单片机比如STC系列至今依然靠ISP作为主要的程序下载方式原因就是成本足够低、使用足够便捷。但ISP也有明显的限制。它依赖芯片出厂自带的Bootloader这部分代码是芯片厂商固化好的你改不了里面的逻辑。通信协议固定了可用的外设引脚也固定了比如STM32F1的ISP固件通常就绑定在USART1上如果你的产品设计时把USART1的引脚复用了别的功能那ISP这条路就走不通。此外ISP通常不能对Bootloader自身所在的存储区编程也就是说它只能更新用户Flash区域没办法像IAP那样灵活地规划整个Flash布局。2.3 IAP应用程序运行中更新自己OTA升级的底层原理IAP的全称是In-Application Programming直译是“在应用编程”。理解IAP最好的方式是把它跟ISP放在一起对比。ISP是芯片里出厂时就已经有了一段Bootloader开机先跑Bootloader再根据外部信号决定要不要接收固件IAP则是你自己写一段Bootloader放在Flash的最前面上电先执行你的Bootloader由你的代码决定接下来是正常跳转到应用程序还是接收新固件完成升级。ISP的引导程序和通信协议是芯片厂商定的IAP完全是由开发者自己设计和控制的。可以说IAP给了你对Flash布局的最高控制权。常见的做法是把Flash分成两个区域Bootloader区放在最低地址用户应用程序区放在Bootloader之后。Bootloader负责检查升级标志、接收固件数据包、写入应用程序区应用程序收到升级指令后把自己的升级标志位设置好然后跳转到Bootloader执行Bootloader再完成最后的数据搬运和跳转回调。你手机上收到系统更新通知点击升级系统重启后进入“正在安装更新”界面这个过程的嵌入式版本就是IAP。IoT领域常说的OTA本质上就是通过网络传输固件再通过IAP机制完成Flash更新。跑在设备上的代码最终要利用IAP这个通道把自己替换成新版本。三种方式放到一张表里看更直观维度ICPISPIAP编程发起方外部烧录器/调试器芯片自带Bootloader用户自定义Bootloader/应用程序额外硬件需要ST-Link/J-Link等调试器需要串口/USB转接工具通过现有通信接口即可是否依赖芯片内部代码不依赖空片也能烧依赖出厂Bootloader依赖自己写的Bootloader能否升级Bootloader本身可以通过烧录器通常不能可以但需特殊设计典型场景开发调试、量产烧录低成本小批量生产产品在线升级、OTA3. 从实际应用出发怎么选才不会后悔3.1 按开发阶段和量产阶段来匹配选ISP、ICP还是IAP没有绝对的“哪个更好”只有“哪个更合适当前阶段”。个人开发或小批量样板阶段首选ICP。原因很现实你需要频繁修改代码SWD接口不但能下载程序还能打断点、看变量、单步调试调试器配合IDE能大幅缩短“改代码—验证效果”的循环周期。如果你在这个阶段用ISP意味着每次改代码都要手动切换BOOT引脚、进引导模式、再用串口下载折腾几次你就想摔板子了。试产或成本敏感的小批量生产阶段ISP值得考虑。不需要买几十个调试器一条量产下载线接上换板子、点下载简单培训就能上手。但要注意流水线效率比如每次下载前需要人工做BOOT引脚切换或者需要复位后快速进入引导程序这些都会影响节拍。如果量比较大更专业的做法是使用“离线烧录器”先把固件存入烧录器内部接上芯片后按一下按钮完成烧录不用连电脑、不用装软件产线工人操作门槛极低。产品已经上市或需要远程维护的阶段IAP就必须提前规划了。你不可能要求用户拆开设备插上烧录器也不能指望现场工程师都带着SWD调试器。IAP配合无线模块、4G模块或以太网就能实现远程发版本这就是OTA。设计IAP方案时最关键的是在产品方案初期就把Bootloader写进架构不要等到产品已经量产了才想起要升级那时Flash分区、中断向量、通信协议全都定死了改起来代价极高。3.2 同一个芯片怎么在三种方式之间切换很多单片机其实同时支持三种方式。拿STM32F103举例它既能通过SWD用ICP烧录也能通过串口1用ISP烧录用户还可以自己实现IAP。这里有一个很重要的BOOT引脚逻辑新手经常在这上面栽跟头。STM32F1系在复位瞬间会根据BOOT0和BOOT1的电平决定从哪里启动BOOT0BOOT1启动区域实际用途0任意主Flash正常运行用户程序10系统存储器进入ISP Bootloader11SRAM调试用掉电即丢做开发板时通常会在BOOT0上接一个拨码开关或跳线就是这个原因。想用ISP下载就先把BOOT0拨到1复位一下让芯片进入厂商Bootloader下载完毕再把BOOT0拨回0再次复位程序才会从主Flash启动。如果你不拨回来直接运行芯片会一直停在ISP引导程序里看似“程序烧录成功但跑不起来”。很多新手的困惑来自这里明明烧录成功程序为什么不运行答案往往是BOOT引脚状态不对。所以遇到“程序不运行”的第一反应应该是先确认芯片是从主Flash启动的再看代码逻辑。3.3 量产烧录时的文件和配置细节量产阶段还有一个容易被忽视的点烧录文件格式和烧录选项。开发阶段用IDE下载通常是直接用调试器把可执行文件烧进去参数都是IDE配好的。但量产时如果用离线烧录器就需要自己导出烧录文件并确认芯片型号、地址范围、加密选项等参数。HEX和BIN在量产中的差异很明显。HEX带地址信息烧录器可以根据地址自动分布数据适合分段烧录BIN是纯数据必须指定起始地址只要地址配置错了程序就会跑到错误位置。我见过工厂把BIN文件的起始地址配在0x08000000但实际上代码是从0x08008000开始的结果每一台机器都不启动最后才发现是地址参数的问题。量产时还应该考虑“读保护”的配置。给芯片打开读保护RDP级别1后外部调试器就不能随意读取Flash内容这能在一定程度上保护固件不被抄板。但要注意开启读保护后再次用ICP烧录时需要先解除保护而解除保护通常伴随着Flash全片擦除。也就是说你不能在不解保护的情况下直接增量烧录第一次烧录就要把保护和程序一并完成。4. 实操避坑烧录失败排查和那些没人告诉你的小细节4.1 下载失败时的标准排查顺序“找不到设备”“连接超时”“下载失败”应该是嵌入式工程师最高频的报错三兄弟。我总结出来一套排查顺序按这个顺序走大多数问题都能定位。第一步查供电。芯片有没有复位循环电源纹波大不大如果目标板上有电机、继电器这类大负载设备烧录时一定先把它们断开。SWD烧录时若出现电压跌落调试器很容易断开连接这种问题换再好的调试器也没用。第二步查接线。SWDIO、SWCLK有没有接反GND有没有共地排线有没有松动。特别要注意杜邦线连接时接触不良的问题看起来插上了实际上氧化层导致信号时断时续。第三步查电平占用。芯片的SWD引脚如果被程序设计成普通GPIO而且当前正被强制拉低或拉高调试器依然可能无法建立连接。这时需要用到“连接时复位”功能在IDE或烧录工具中勾选Under Reset模式让调试器在复位期间抓住芯片再抢占SWD接口。第四步查Flash保护。如果芯片之前开过读保护标准的连接和读取会被拒绝。解决方案一般是执行全片擦除或解除保护但这会清空Flash数据所以文件备份要提前做好。最后一步降速率。很多奇怪的连接失败其实是SWCLK速率太高引起的。把SWD时钟从默认的4MHz或8MHz降到1MHz经常就能稳定连接了。这个操作在调试器超长排线或者芯片与调试器电平不匹配时尤其见效。4.2 ISP模式进不去的几个常见原因用ISP方式下载时最常见的失败现象是“上位机软件一直提示等待超时”。先检查BOOT0电平。很多板子上的BOOT0跳线帽接触不好或者引脚被其他外围电路拉低导致芯片实际没有进入系统存储器。建议用万用表量一下BOOT0引脚的实际电压不要只看跳线帽位置。再检查串口接线。ISP下载用的串口波特率通常较高比如115200或57600如果USB转TTL模块质量太差或者线材太长数据就容易丢包。另外TX和RX必须交叉连接芯片的RX接USB转TTL模块的TX芯片的TX接模块的RX接反了就完全不通。还有一个细节是“断电上电复位”。ISP握手过程一般要求芯片在上电复位后才能进入引导程序所以很多工具都提示你“先按复位按钮”或“先重新上电”。如果你只是用接线方式连接但没有复位芯片芯片可能还运行着旧的应用程序自然不听串口指令。4.3 关于“去弹窗”这类工具的提醒网络上搜芯片烧录相关内容时经常会看到“某系列ISP软件去弹窗”“去弹窗版下载工具”之类的词。这类工具确实能省掉一些等待时间和推广弹窗但我的态度一直是开发和生产工具尽量用正版官方渠道。厂商在ISP软件里加入启动等待或推送虽然烦人但至少行为可预期。如果真想绕开厂商ISP软件的弹窗等待更稳妥的做法是使用官方提供的命令行工具或者直接通过串口用底层协议与Bootloader通信。比如ST就提供了STM32CubeProgrammer的命令行模式脚本化执行烧录既干净又适合流水线集成。为省几秒钟的等待去用来路不明的修改版软件万一工具里夹带了额外数据或者错误配置烧坏一批芯片的代价远远大于省下的时间。4.4 烧录器的选择建议个人建议开发阶段至少准备两个调试器。一个固定的ST-Link或DAP-Link连主力开发板一个便宜的备用调试器应对突发状况。J-Link这类高端调试器功能强大但早期很多兼容版固件升级会出问题反而影响效率。做脱机量产烧录时可以关注支持多芯片型号的烧录器比如一些主打“一拖八”或“一拖十六”的离线烧录器可以同时烧录多块板子产线效率提升明显。不过要注意烧录器对芯片序列号的写入支持有些产品需要在固件中写入唯一序列号普通的“只烧程序”工具就搞不定得选支持序列号生成和写入的型号。5. 深入IAP中断向量、升级协议和变量复位的那些事5.1 先搞清楚Flash分区和中断向量重映射我接触过不少做IAP升级半途而废的项目原因惊人地一致按照网上的教程把程序分成了Boot和App两个部分烧录都正常跳转也能跳过去但一进中断就死机。问题基本都出在中断向量表。ARM Cortex-M系列的芯片上电后会从固定地址一般是0x00000000或0x08000000读取向量表这里存放着栈顶指针和各个中断服务函数的入口地址。如果你的App放在0x08008000但是中断向量表还是指向0x08000000那么一旦发生中断CPU就会去读Boot区开头的中断向量找不到对应的App中断服务函数程序立刻跑飞。解决办法是在App代码启动的最早阶段把向量表重新定位到App所在的地址。STM32F1/F2/F3系列常用这样的方式#define APP_ADDR 0x08008000U void app_main(void) { SCB-VTOR APP_ADDR; // 后续初始化外设和中断 }F7/H7系列也是用SCB-VTOR但H7还涉及Flash接口和地址别名的问题配置时如果发现跳转后仍然异常需要确认芯片是否有“地址映射重定向寄存器”需要同步设置。GD32虽然号称兼容STM32但个别型号在中断向量重映射时的行为有差异踩坑概率比想象中高升级前一定要对着具体型号的手册核对。5.2 Bootloader跳转App的完整步骤和注意点跳转这件事看似简单实际上有几个步骤必须按顺序做。一段最常见的跳转代码大概是这样的typedef void (*app_entry_t)(void); void jump_to_app(uint32_t app_addr) { uint32_t app_sp *(volatile uint32_t *)app_addr; uint32_t app_pc *(volatile uint32_t *)(app_addr 4); app_entry_t entry (app_entry_t)app_pc; __disable_irq(); // 跳转前关闭全局中断 SCB-VTOR app_addr; // 设置新中断向量表 __set_MSP(app_sp); // 切换到App的栈指针 entry(); // 跳转到App的Reset_Handler }跳转前为什么要先关中断因为Bootloader运行时可能已经打开了某个外设中断如果带着中断跳过去App的启动代码还没来得及配置这个外设中断一来就是未定义状态。跳转后App的SystemInit和main会重新配置时钟和中断所以Boot侧关掉中断后不需要再手动开启交由App接管即可。这个过程中还容易犯一个错在Bootloader里直接用函数指针跳转跳转之后又用局部变量判断“是否跳转成功”。实际上跳转后Bootloader的代码就不再执行了栈空间也会被App重新初始化任何在Boot里定义的普通全局变量都会在这个切换过程中失效。5.3 “IAP Boot里面定义的变量复位后会怎样”这个问题在相关搜索里出现频率很高其实问的是“我在Bootloader里设置一个标志位重启后让App知道要不要执行升级这个标志位还在吗”答案分两种情况。如果是正常的软件复位、引脚复位或看门狗复位MCU的启动代码会把RAM清零你在Bootloader里设置的普通全局变量不会保留。更准确地说启动代码会执行初始化操作把.bss段清零、把.data段从Flash复制到RAM所以你之前在RAM里存的值早就被覆盖了。如果你指望复位后还能读到这个值必须“绕过”常规RAM的初始化机制。一个常用的做法是把变量放在“不初始化”段__attribute__((section(.noinit))) volatile uint8_t upgrade_flag 0;同时需要在链接脚本里把.noinit段保留下来不让启动代码清零。这种做法本质上就是在RAM里划出一块“自留地”复位后内容保持不变。比这更稳妥的设计是把握手信息放在备份寄存器中。备份寄存器在芯片掉电或复位时不一定会丢失因为由VBAT域供电代码可以通过RTC或备份寄存器的接口来读写。这类方案在低功耗设备和需要断电重启升级的场景下更可靠因为有些设备的IAP流程会走到“断电后重新上电”彻底刷新RAM只有备份域数据能撑过这个阶段。5.4 大容量芯片和外部Flash的IAP特殊操作不同芯片做IAP时的差异很大。比如STM32H750VBT6它的内部Flash在部分量产型号上容量偏紧张很多方案会把应用程序放到外部QSPI Flash中运行或存放。这时IAP的流程就变得更复杂Bootloader要先初始化QSPI控制器从外部Flash读取固件然后写入内部Flash或者直接在外部Flash中执行代码。直接外部Flash执行代码涉及“映射地址”的概念需要把外部存储器的数据映射到芯片的地址空间中断向量表也要指向这个映射区。此时SCB-VTOR的设置值就不再是内部Flash地址而是外部映射基地址。这个问题如果在设计阶段没想清楚后期排查会非常痛苦因为现象千奇百怪有时能跑起来、有时一触发中断就挂、有时上电顺序不对就完全没反应。我的建议是凡是涉及外部Flash执行代码的项目至少提前用官方评估板验证方案再动PCB设计。外部Flash的读取速率、等待周期、内存映射页大小、烧录算法每一项都可能影响系统稳定性把问题留在硬件定型前去解效率最高。5.5 升级过程断电了怎么办IAP在设计时必须把“断电”当成正常流程来考虑而不是异常情况。因为产品在客户端升级时用户很可能随时拔电、锁屏、断网。最简单的容错方案是“双分区”也叫A/B升级。Flash里同时保留两个应用程序分区一个运行、一个待写入。升级写入时只影响待升级分区即使写入一半断电另一个分区还是完好可用的。下次启动时Bootloader检查新分区不完整就自动回退到旧分区用户几乎无感。如果芯片Flash容量不允许双分区也可以用“备份恢复”策略先把旧固件备份到外部Flash或足量的剩余扇区再写入新固件。但这种方式对Flash剩余空间要求不低而且备份和恢复都要消耗时间现场体验差一些。除此之外升级协议本身也要做好“断点续传”意识。固件分包编号、每包CRC校验、总固件长度和版本号都要在应用层设计好。即便传输中断重新升级时也不需要从头开始或者至少能快速确认目标分区是否完整避免在错误的分区上花时间。6. 最后再分享一点个人经验做了这么多年嵌入式我最深的体会是烧录问题看起来千奇百怪但绝大多数都逃不过“连接不稳定、电平不匹配、Flash保护状态不明、中断向量配置错误、启动条件不满足”这几类。遇到现象诡异的问题先冷静下来按供电、接线、电平、保护、速率这个顺序排查比反复拔插调试器有用得多。另外每一次涉及IAP的固件发布我都会强制要求做一个“回滚验证”先升级到新版本再回滚到旧版本确认Bootloader在两个方向上都稳定。这个动作看起来简单但能在出货前拦下大量现场故障。烧录这件事单个环节都不复杂难的是把每个环节的细节都照顾到位。一块板子能稳定地烧录一百次并且每次烧录后程序都能按预期跑起来你的工程化能力就过关了。
返回列表