小沃最近所使用的单片机全都是基于STC8系列单片机实现,由于项目需要,需要实现在线远程更新功能。因此,小沃所使用的单片机是STC8A8K64S4A12。注意,只有eeprom写的是iap的型号才支持。
首先,stc8单片机不同于stm32系列,它运行时是实时读取flash中的代码的,而stm32是运行前先将flash中的程序搬到ram中然后运行的。因此,stc8的烧录要比stm32系列困难的多,用来更新代码的代码必须与被更新的代码放在不同位置。
由于stc8系列的单片机,运行code肯定是在eeprom的最前端,因此我们需要一开始准备一段,用来将eeprom别的位置代码搬移到运行位置的工具代码,小沃选择使用汇编语言写。然后就只需要将通过串口或是通过网络获取到的代码,写道eeprom的另一个位置即可了。
比如,先将通过网络获取到的代码下载到eeprom的0x3000位置,然后调用位于0xf800位置的工具代码将0x3000位置的内容搬移到0x0000位置即可。
小沃写的汇编代码定义了代码地址在0xf800位置,然后通过R7、R6、R5、R4四个变量分别传了需要更新的代码位置以及大小。
调用方法如下:
void (*run_iapprogram)(unsigned short iapaddr, unsigned short iapsize) = 0xf800; run_iapprogram(0x3000, newprogram_size);
但是在执行这个代码之前,需要保证0xf800这个地址已经是工具代码了,可以用如下代码实现。
char code iapprogram[] = { 0xc2, 0xaf, 0x78, 0x00, 0x79, 0x00, 0xb8, 0x00, 0x0b, 0xe9, 0x20, 0xe0, 0x07, 0x88, 0x82, 0x89, 0x83, 0x12, 0xf8, 0x82, 0xc3, 0xe8, 0x2f, 0xf5, 0x82, 0xe9, 0x3e, 0xf5, 0x83, 0x12, 0xf8, 0x50, 0x88, 0x82, 0x89, 0x83, 0x12, 0xf8, 0x69, 0xc3, 0x74, 0x01, 0x28, 0xf8, 0x74, 0x00, 0x39, 0xf9, 0xc3, 0xe8, 0x9d, 0x70, 0xd1, 0xc3, 0xe9, 0x9c, 0x70, 0xcc, 0x75, 0xc7, 0x60, 0x02, 0xf8, 0x3d, 0x75, 0xc7, 0x00, 0x75, 0xc5, 0x00, 0x75, 0xc6, 0x00, 0x75, 0xc3, 0x80, 0x75, 0xc4, 0x00, 0x22, 0x75, 0xc7, 0x83, 0x75, 0xc5, 0x01, 0x85, 0x82, 0xc4, 0x85, 0x83, 0xc3, 0x75, 0xc6, 0x5a, 0x75, 0xc6, 0xa5, 0x00, 0xe5, 0xc2, 0x12, 0xf8, 0x40, 0x22, 0x75, 0xc7, 0x83, 0x75, 0xc5, 0x02, 0x85, 0x82, 0xc4, 0x85, 0x83, 0xc3, 0xf5, 0xc2, 0x75, 0xc6, 0x5a, 0x75, 0xc6, 0xa5, 0x00, 0x12, 0xf8, 0x40, 0x22, 0x75, 0xc7, 0x83, 0x75, 0xc5, 0x03, 0x85, 0x82, 0xc4, 0x85, 0x83, 0xc3, 0x75, 0xc6, 0x5a, 0x75, 0xc6, 0xa5, 0x00, 0x12, 0xf8, 0x40, 0x22, 0x78, 0x7f, 0xe4, 0xf6, 0xd8, 0xfd, 0x75, 0x81, 0x07, 0x02, 0x00, 0x00 }; void (*run_iapprogram)(unsigned short iapaddr, unsigned short iapsize) = 0xf800; void Init_iapprogram() { unsigned short i; for (i=0;i<sizeof(iapprogram);i++) { if (IapRead(0xf800+i) != iapprogram[i]) { IapErase(0xf800); for (i=0;i<sizeof(iapprogram);i++) { IapProgram(0xf800+i, iapprogram[i]); } return; } } }
IapRead、IapErase与IapProgram的具体实现,直接看官方文档即可。至于数组iapprogram怎么来的,可以参考本文最后面的汇编代码。
最后需要注意的是,下载程序时,eeprom必须选择64k,如下图:
下面分享下小沃写的stc8的iap更新工具代码(汇编):
IAP_DATA DATA 0C2H IAP_ADDRH DATA 0C3H IAP_ADDRL DATA 0C4H IAP_CMD DATA 0C5H IAP_TRIG DATA 0C6H IAP_CONTR DATA 0C7H WT_12M EQU 83H ;本程序R1R0为偏移指针,R6R7为新的code的地址,R4R5为新code的大小 ORG 0000H LJMP MAIN ORG 0f800H MAIN: CLR EA ;关闭所有中断,以免中途发生中断影响升级 MOV R0,#00H MOV R1,#00H LOOP: CJNE R0,#00H,NOERASE MOV A,R1 JB ACC.0,NOERASE MOV DPL,R0 MOV DPH,R1 LCALL IAP_ERASE NOERASE: ;当R0不为0x00,同时R1.0不为0时,跳过erase操作 CLR C MOV A,R0 ADD A,R7 MOV DPL,A MOV A,R1 ADDC A,R6 MOV DPH,A ;将R1R0+R6R7的值存入DPTR中 LCALL IAP_READ MOV DPL,R0 MOV DPH,R1 LCALL IAP_PROGRAM CLR C MOV A,#01H ADD A,R0 MOV R0,A MOV A,#00H ADDC A,R1 MOV R1,A CLR C MOV A,R0 SUBB A,R5 JNZ LOOP CLR C MOV A,R1 SUBB A,R4 JNZ LOOP ;将R1R0与R4R5进行比较,不相等则继续循环 MOV IAP_CONTR,#60H ;让单片机重启 LJMP $ IAP_IDLE: MOV IAP_CONTR,#0 ;关闭 IAP 功能 MOV IAP_CMD,#0 ;清除命令寄存器 MOV IAP_TRIG,#0 ;清除触发寄存器 MOV IAP_ADDRH,#80H ;将地址设置到非 IAP 区域 MOV IAP_ADDRL,#0 RET IAP_READ: MOV IAP_CONTR,#WT_12M ;使能 IAP MOV IAP_CMD,#1 ;设置 IAP 读命令 MOV IAP_ADDRL,DPL ;设置 IAP 低地址 MOV IAP_ADDRH,DPH ;设置 IAP 高地址 MOV IAP_TRIG,#5AH ;写触发命令(0x5a) MOV IAP_TRIG,#0A5H ;写触发命令(0xa5) NOP MOV A,IAP_DATA ;读取 IAP 数据 LCALL IAP_IDLE ;关闭 IAP 功能 RET IAP_PROGRAM: MOV IAP_CONTR,#WT_12M ;使能 IAP MOV IAP_CMD,#2 ;设置 IAP 写命令 MOV IAP_ADDRL,DPL ;设置 IAP 低地址 MOV IAP_ADDRH,DPH ;设置 IAP 高地址 MOV IAP_DATA,A ;写 IAP 数据 MOV IAP_TRIG,#5AH ;写触发命令(0x5a) MOV IAP_TRIG,#0A5H ;写触发命令(0xa5) NOP LCALL IAP_IDLE ;关闭 IAP 功能 RET IAP_ERASE: MOV IAP_CONTR,#WT_12M ;使能 IAP MOV IAP_CMD,#3 ;设置 IAP 擦除命令 MOV IAP_ADDRL,DPL ;设置 IAP 低地址 MOV IAP_ADDRH,DPH ;设置 IAP 高地址 MOV IAP_TRIG,#5AH ;写触发命令(0x5a) MOV IAP_TRIG,#0A5H ;写触发命令(0xa5) NOP LCALL IAP_IDLE ;关闭 IAP 功能 RET END
文章作者:沃航科技