本文参考了STC的官方例程,根据官方例程来进行讲解IAP的详细操作
一、内部FLASH的规划
STC不同型号的单片机,内部FLASH的规划大不相同,详细的使用规格书都有介绍,以下我只截图规格书当中我需要的部分进行讲解
1、首先是规格书当中STC8H8K64单片机的内部使用空间划分
FLASH 空间中,从地址 0000H 开始的连续 62.5K 字节的空间为用户程序区。当满足特定的下载条 件时,需要用户将 PC 跳转到用户 ISP 程序区,此时可对用户程序区进行擦除和改写,以达到更新用户 程序的目的。(只有0.5K的ISP空间所以实际使用代码是参考了官方例程的)
二、程序的基本框架
实际使用的程序框架如图所示:
三、复位程序
以下是官方代码
#include "reg51.h"
typedef unsigned char BYTE;
typedef unsigned int WORD;
typedef void (* FARPROC)(BYTE *);
#define FOSC 22118400L //系统时钟频率
#define BAUD (65536 - FOSC/4/115200) //定义串口波特率
/* 定义串口相关SFR */
sfr AUXR = 0x8E; //波特率发生器控制寄存器
sfr BRT = 0x9C; //波特率发生器定时器
sfr T2H = 0xd6;
sfr T2L = 0xd7;
void (*Isp_Check)(BYTE *) = 0xfa00; //定义指针函数
BYTE cnt7f; //Isp_Check内部使用的变量
void uart() interrupt 4 using 1 //串口中断服务程序
{
if (TI) TI = 0; //发送完成中断
if (RI) //接收完成中断
{
Isp_Check(&cnt7f); //ISP检测
RI = 0; //清接收完成标志
}
}
void main()
{
SCON = (SCON & 0x3f) | 0x50;
T2L = BAUD;
T2H = BAUD>>8;
AUXR |= 0x15;
ES = 1; //使能串口中断
EA = 1; //打开全局中断开关
while (1)
{
P0++;
}
}
官方代码复位的主要用到以下三段代码用来复位
typedef void (* FARPROC)(BYTE *);
void (*Isp_Check)(BYTE *) = 0xFA00; //定义指针函数
这是重定义了一下指针函数使其指向地址0XFA00,也就是用户ISP区,实际使用的时候只需要满足条件后调用
Isp_Check(&cnt7f);
即可进入用户ISP区,这里操作较为简单不做其他介绍
PS:规格书上也标注了调用 IAP_CONTR=0x60 即可软件复位,然后通过ISP的方式进行升级,这种也是可以的,就和重新上电烧程序的方式一致,这里不多做介绍,感兴趣的可以去官网看看有例程介绍
四、IAP升级代码
以下是官方例程,可以根据自己使用来修改协议(笔者是自己更改了串口、协议来进行升级,这个看个人需求)absacc.h
文件
内容如下:
#ifndef __ABSACC_H__
#define __ABSACC_H__
#define CBYTE ((unsigned char volatile code *) 0)
#define DBYTE ((unsigned char volatile data *) 0)
#if !defined (__CX2__)
#define PBYTE ((unsigned char volatile pdata *) 0)
#endif
#define XBYTE ((unsigned char volatile xdata *) 0)
#define CWORD ((unsigned int volatile code *) 0)
#define DWORD ((unsigned int volatile data *) 0)
#if !defined (__CX2__)
#define PWORD ((unsigned int volatile pdata *) 0)
#endif
#define XWORD ((unsigned int volatile xdata *) 0)
#if defined (__CX51__) || defined (__CX2__)
#define FVAR(object, addr) (*((object volatile far *) (addr)))
#define FARRAY(object, base) ((object volatile far *) (base))
#define FCVAR(object, addr) (*((object const far *) (addr)))
#define FCARRAY(object, base) ((object const far *) (base))
#else
#define FVAR(object, addr) (*((object volatile far *) ((addr)+0x10000L)))
#define FCVAR(object, addr) (*((object const far *) ((addr)+0x810000L)))
#define FARRAY(object, base) ((object volatile far *) ((base)+0x10000L))
#define FCARRAY(object, base) ((object const far *) ((base)+0x810000L))
#endif
#if defined (__CX2__)
#define HBYTE ((unsigned char volatile huge *) 0)
#define HWORD ((unsigned int volatile huge *) 0)
#define HVAR(object, addr) (*((object volatile huge *) (addr)))
#define HARRAY(object, base) ((object volatile huge *) (base))
#endif
#define CVAR(object, addr) (*((object volatile code *) (addr)))
#define CARRAY(object, base) ((object volatile code *) (base))
#define DVAR(object, addr) (*((object volatile data *) (addr)))
#define DARRAY(object, base) ((object volatile data *) (base))
#define XVAR(object, addr) (*((object volatile xdata *) (addr)))
#define XARRAY(object, base) ((object volatile xdata *) (base))
#endif
IAP主程序
#include "reg51.h"
#include "absacc.h"
/*定义常数*/
#define FOSC 22118400L //系统时钟频率
#define BAUD (65536 - (FOSC/4/115200)) //定义串口波特率
#define MAX_SIZE 118 //用户程序最大的可用扇区数
#define ENABLE_IAP 0x80 //使能IAP功能
/* 定义串口相关SFR */
sfr AUXR = 0x8E; //波特率发生器控制寄存器
sfr WDT_CONTR = 0xC1; //看门狗控制寄存器
sfr IAP_DATA = 0xC2; //IAP数据寄存器
sfr IAP_ADDRH = 0xC3; //IAP高地址寄存器
sfr IAP_ADDRL = 0xC4; //IAP低地址寄存器
sfr IAP_CMD = 0xC5; //IAP命令寄存器
sfr IAP_TRIG = 0xC6; //IAP命令触发寄存器
sfr IAP_CONTR = 0xC7; //IAP控制寄存器
sfr IAP_TPS = 0xF5; //IAP等待时间控制寄存器
sfr T2H = 0xd6;
sfr T2L = 0xd7;
//?PR?_ISP_CHECK?ISP(0xEE00),?PR?ISP_SOFTRESET?ISP(0xEF38),?PR?_ISP_SENDUART?ISP(0xEF3C),?PR?ISP_RECVUART?ISP(0xEF49),?PR?_ISP_RECVBLOCK?ISP(0xEF5B),?C?LIB_CODE(0xEF72)
typedef unsigned char BYTE;
typedef unsigned int WORD;
typedef struct
{
BYTE cmd;
WORD addr;
WORD len;
BYTE chk;
} CBW;
void Isp_Check(BYTE *p);
BYTE Isp_RecvUart();
void Isp_RecvBlock(BYTE *p, BYTE n);
void Isp_SendUart(BYTE dat);
void Isp_SoftReset();
BYTE cnt7f; //Isp_Check内部使用的变量,接收7F的计数器,当连续接收到16次7F后进入ISP下载模式
CBW cbw; //串口命令块
BYTE sum; //校验和变量
BYTE buf[64]; //数据缓冲区
/*------------------------------------------------
串口中断服务程序
------------------------------------------------*/
void uart() interrupt 4
{
if (TI) TI = 0; //发送完成中断
if (RI) //接收完成中断
{
Isp_Check(&cnt7f); //ISP检测
RI = 0; //清接收完成标志
}
}
void main()
{
SCON = (SCON & 0x3f) | 0x50;
T2L = BAUD;
T2H = BAUD>>8;
AUXR |= 0x15;
ES = 1; //使能串口中断
EA = 1; //打开全局中断开关
while (1);
}
/*------------------------------------------------
串口ISP命令序列检测模块
------------------------------------------------*/
void Isp_Check(BYTE *p)
{
BYTE i;
WORD j;
if (SBUF != 0x7f) //检测串口数据是否为7F
{
*p = 0; //若不是7F,则清7F计数值
}
else
{
(*p)++; //若是7F,则7F计数值+1
if (*p >= 16) //判断7F是否已连续接收到16次
{ //若>=16次,则进入ISP下载模式
IE = 0; //关闭所有中断
PSW = 0; //ISP模块使用第0组寄存器
SP = 0x5f; //重置ISP模块的堆栈指针
RI = 0; //清除串口接收标志
TI = 1; //置串口发送标志
Isp_SendUart(0x5a); //返回5A 69到PC,表示ISP模块已准备就绪
Isp_SendUart(0x69);
while (1) //ISP下载模式,主循环
{
sum = 0; //清校验和值
if ((Isp_RecvUart() == 0x5a) && //判断命令头是否为5A,69
(Isp_RecvUart() == 0x69))
{
Isp_RecvBlock((BYTE *)&cbw, 6); //接收6字节的命令序列
if (sum != 0) //判断命令序列是否正确
{
Isp_SoftReset(); //若命令出错,则程序复位
}
switch (cbw.cmd)
{
case 0: //0号命令为擦除命令
IAP_ADDRL = 0; //从第0扇区开始擦除
IAP_ADDRH = 0;
IAP_CONTR = ENABLE_IAP; //使能IAP功能
IAP_TPS = 23;
IAP_CMD = 3; //擦除命令
if (cbw.len > MAX_SIZE) //判断擦除扇区数是否超出范围
{
cbw.len = MAX_SIZE;
}
while (cbw.len--) //判断是否擦除完成
{
WDT_CONTR = 0x17; //清看门狗
IAP_TRIG = 0x5a; //触发ISP命令
IAP_TRIG = 0xa5;
IAP_ADDRH += 2; //目标地址+512
}
Isp_SendUart(0); //正确返回
break;
case 1: //1号命令为编程命令
sum = 0; //清除校验和值
Isp_RecvBlock(buf, 64); //接收64字节的编程数据
Isp_RecvUart(); //接收校验和
if (sum != 0) //判断数据是否正确
{
Isp_SoftReset(); //若数据出错,则程序复位
}
IAP_CONTR = ENABLE_IAP; //使能IAP功能
IAP_TPS = 23;
IAP_CMD = 2; //编程命令
j = cbw.addr; //编程目标地址
for (i=0; i<64; i++) //编程64字节数据
{
WDT_CONTR = 0x17; //清看门狗
IAP_DATA = buf[i]; //将当前数据送IAP数据寄存器
IAP_ADDRL = j; //目标地址送IAP地址寄存器
IAP_ADDRH = j >> 8;
IAP_TRIG = 0x5a; //触发ISP命令
IAP_TRIG = 0xa5;
j++; //目标地址+1
}
j = cbw.addr; //校验目标地址
for (i=0; i<64; i++) //校验64字节数据
{
WDT_CONTR = 0x17; //清看门狗
if (buf[i] != CBYTE[j]) //源数据与目标数据进行比较
break; //不相等,则编程出错
j++; //校验下一个字节
}
Isp_SendUart(!(i == 64)); //校验成功返回0; 否则返回1
break;
default:
Isp_SoftReset(); //接收到非法命令时,复位系统
break;
}
}
}
}
}
}
/*------------------------------------------------
接收1字节串口数据
出口参数: ACC (接收到的数据)
------------------------------------------------*/
BYTE Isp_RecvUart()
{
BYTE dat;
while (!RI) //等待接收完成
{
WDT_CONTR = 0x17; //清看门狗
}
dat = SBUF; //读取串口数据
RI = 0; //清除标志
sum += dat; //计算校验和
return dat; //返回接收到的串口数据
}
/*------------------------------------------------
接收一块串口数据
入口参数: R0 (数据缓冲区地址)
R7 (缓冲区长度)
------------------------------------------------*/
void Isp_RecvBlock(BYTE *p, BYTE n)
{
while (n--) //检测长度
{
*p = Isp_RecvUart(); //接收1字节,并保存到缓冲区
p++; //缓冲区地址+1
}
}
/*------------------------------------------------
发送1字节串口数据
入口参数: ACC (待发送的数据)
------------------------------------------------*/
void Isp_SendUart(BYTE dat)
{
while (!TI) //等待前一个数据发送完成
{
WDT_CONTR = 0x17; //清看门狗
}
TI = 0; //清除标志
SBUF = dat; //发送当前数据
}
/*------------------------------------------------
软件复位
------------------------------------------------*/
void Isp_SoftReset()
{
IAP_CONTR = 0x20; //用户程序区复位
}
五、KEIL中定位IAP升级代码的方法
这是最后一步,这里函数定位结束后IAP就可以使用了,这里参考了官方文档,具体定位方法如下:
下面以演示程序进行说明
演示程序中有ReadIAP、ProgramIAP和EraseIAP三个函数
最终目的是将这三个函数都定位到0x8000之后
第一步:新建一个项目“Demo”,并将源文件“Demo.C”添加到项目中
第二步:直接编译,并打开编译后生成的“Demo.M51”文件
从M51文件的“CODE MEMORY”信息中,可以看到3个函数的链接名称、链接地址和函数长度
ReadIAP的链接名称为“?PR?_READIAP?DEMO”,链接地址为“0003H”,长度为16H字节
ProgramIAP的链接名称为“?PR?_PROGRAMIAP?DEMO”,链接地址为“0019H”,长度为16H字节
EraseIAP的链接名称为“?PR?_ERASEIAP?DEMO”,链接地址为“0044H”,长度为14H字节
第三步:根据M51中函数的长度信息计算出各个函数重定位的地址,
ReadIAP的重定位的地址为0x8000
ProgramIAP的重定位的地址为0x8016
EraseIAP的重定位的地址为0x802C
第四步:打开项目选项中的“BL51 Locate”属性页
在上图的“Code”域中输入下列语句
“?PR?_READIAP?DEMO(0x8000), ?PR?_PROGRAMIAP?DEMO(0x8016), ?PR?_ERASEIAP?DEMO(0x802C)”