STC8系列单片机IAP更新方法
Contents
起因
最近所使用的单片机全都是基于STC8系列单片机实现,由于项目需要,需要实现在线远程更新功能。
本文所使用的单片机是STC8A8K64S4A12。注意:只有eeprom写的是IAP的型号才支持。
先交代一个 STC8 的存储特点:程序 Flash 与 EEPROM 统一编址在同一个 64K 空间,IAP 命令两者都能操作,下文说的“写到 eeprom 的某个地址”指的都是这个统一空间里的地址。
关于此芯片的重要说明:
2022-2-24到2022-3-21期间生产的此型号 程序空间为63K, EEPROM 固定为1K, EEPROM大小无法修改 其它时间生产的此型号的程序空间和EEPROM参数都是正确的 即: 程序空间为63.5K, EEPROM默认为0.5K, EEPROM大小可修改

需求
- 设备装在现场,不拆机、不接 ISP 工具,通过串口或网络接收新固件并自我更新
- 更新过程即使中途断电,用来救芯片的“搬运代码”也不能被破坏
技术实现原理
为什么“自己更新自己”这么别扭(根因)
STC8 是增强型 8051 内核,取指执行始终直接走 Flash:PC 走到哪条指令,就从 Flash 哪个地址取。而 IAP 的擦除以 512 字节扇区为单位。于是有一个绕不开的矛盾:正在运行的那段代码所在的扇区一旦被擦掉,之后取指读到的就是乱码指令,直接跑飞。这就是“用来更新代码的代码,必须与被更新的代码放在不同位置”的底层原因。
对比 STM32:它自更新同样要做 bootloader,但 Cortex-M 有向量表重定位(VTOR),部分型号还有双 bank(一边跑 bank1 一边写 bank2),做法已经标准化;STC8 没有这些硬件支持,地址布局全靠自己规划。
Flash 布局:三块区域各管一段
- 0x0000:正在运行的固件。8051 复位后固定从 0x0000 取指,中断向量表也在最前面,所以运行代码永远在 Flash 最前端;
- 0x3000:新固件暂存区,串口/网络收到的代码先写到这里;
- 0xf800:搬运工具(bootloader 核心),负责把 0x3000 的内容逐扇区擦写回 0x0000,完成后触发软件复位,芯片从 0x0000 重新取指,新固件生效。
为什么搬运工具用汇编写而不用 C:它要在“亲手擦掉 0x0000”的环境里跑,必须体积极小、位置固定;C 编译器加上运行库的代码布局不可控,几十字节的手写汇编最稳妥。文末的 iapprogram[] 数组就是这段汇编对应的机器码。
IAP 是怎么动作的(寄存器与触发序列)
IAP 操作本质是往一组 SFR 写好命令再“按门铃”:
- IAP_CMD 写 1/2/3 分别是读/编程/扇区擦除;IAP_ADDRH/L 放目标地址;IAP_DATA 放读出/写入的字节;
- IAP_CONTR 使能 IAP 并配等待时间,汇编里的
WT_12M EQU 83H就是 12MHz 主频对应的等待参数——这个值必须与实际主频匹配,配快了读写不稳定; - “按门铃”的方式是向 IAP_TRIG 依次写 0x5A、0xA5,序列全对命令才执行,这是防止程序跑飞时误触发擦除的硬件保险;
- 每个操作完成后都走一遍 IAP_IDLE:关使能、清命令、把地址指到非 IAP 区,同样是防误触发。
防变砖的安装逻辑(Init_iapprogram 的用意)
0xf800 的工具码是整个方案的命根子:应用区 0x0000 坏了还能靠它救回来,它坏了就只能拆机接 ISP。所以初始化代码上电先 IapRead 逐字节比对 0xf800 内容与数组:一致说明工具区完好,直接返回;不一致才擦掉重装。既保证工具区永远有效,又不会每次上电都白白消耗一个扇区的擦写寿命(Flash 擦写次数是有限的)。
更新调用
调用入口是一个指向 0xf800 的函数指针。我写的汇编代码定义了代码地址在0xf800位置,然后通过R7、R6、R5、R4四个变量分别传了需要更新的代码位置以及大小——正好对上 Keil C51 的传参约定:第一个 unsigned short 参数放 R6R7,第二个放 R4R5。
调用方法如下:
|
|
但是在执行这个代码之前,需要保证0xf800这个地址已经是工具代码了,可以用如下代码实现。
|
|
IapRead、IapErase与IapProgram的具体实现,直接看官方文档即可。
至于数组iapprogram怎么来的,可以参考本文最后面的汇编代码。
下载配置注意
最后需要注意的是,ISP下载程序时,eeprom必须选择64k

这个选项选小了,烧录器对空间的划分就与本文的地址布局对不上,0xf800 的工具区放不进去。
汇编源码
下面分享下我写的stc8的iap更新工具代码(汇编)。几个关键位置与上面的原理一一对应:
- 开头
CLR EA关全部中断:搬移途中来一个中断,向量表正在 0x0000 被改写,中断服务程序的入口地址是随机数; CJNE R0,#00H,NOERASE与JB ACC.0,NOERASE:只在偏移量是 512 整数倍时才执行擦除——按扇区边界对齐着擦;- 全部搬完后
MOV IAP_CONTR,#60H触发软件复位,LJMP $原地等待复位生效,重启后从 0x0000 取指执行新固件。
|
|
踩坑记录与注意事项
- 升级窗口内断电 = 只能拆机救:0x0000 只写了一半就断电,下次上电会在坏固件里跑飞,而 STC8 没有“先跑 bootloader 再跳应用”的向量重映射。对策:新固件先做完整校验(CRC 或和校验)再触发搬移;搬移窗口要短(数据量固定,几十 KB 也就几百毫秒);要求更高的产品把 0xf800 扩成常驻 boot,上电先校验应用区标志位,坏了先等重传,永远不直接跳 0x0000;
- CLR EA 不是可有可无:升级前必须关中断,原因见上文汇编说明;
- WT_12M 必须与实际主频匹配:换了晶振或改了主频忘了改等待参数,IAP 读写会随机出错;
- 触发序列 0x5A、0xA5 顺序不能反,反了命令不执行;
- 新固件别越过暂存区:0x3000 到 0xf800 之间约 50K,R4R5 传的是实际字节数,固件超长会把工具区一起冲掉;
- 先比对再擦写:Init_iapprogram 对工具区这么做,应用区同样适用,没变化的扇区不擦,省擦写寿命;
- 批次问题:2022-2-24~2022-3-21 生产的芯片 EEPROM 固定 1K(见文首说明),做地址规划前先核对芯片生产日期与手册。
Author 软件开发大郭
LastMod 2022-05-26