本文参考了STC的官方例程,根据官方例程来进行讲解IAP的详细操作

一、内部FLASH的规划

STC不同型号的单片机,内部FLASH的规划大不相同,详细的使用规格书都有介绍,以下我只截图规格书当中我需要的部分进行讲解

1、首先是规格书当中STC8H8K64单片机的内部使用空间划分

FLASH 空间中,从地址 0000H 开始的连续 62.5K 字节的空间为用户程序区。当满足特定的下载条 件时,需要用户将 PC 跳转到用户 ISP 程序区,此时可对用户程序区进行擦除和改写,以达到更新用户 程序的目的。(只有0.5K的ISP空间所以实际使用代码是参考了官方例程的)

二、程序的基本框架

实际使用的程序框架如图所示:

三、复位程序

以下是官方代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#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++;
    }
}

官方代码复位的主要用到以下三段代码用来复位

1
2
3
typedef void (* FARPROC)(BYTE *);   

void (*Isp_Check)(BYTE *) = 0xFA00;      //定义指针函数

这是重定义了一下指针函数使其指向地址0XFA00,也就是用户ISP区,实际使用的时候只需要满足条件后调用

1
        Isp_Check(&cnt7f);     

即可进入用户ISP区,这里操作较为简单不做其他介绍

PS:规格书上也标注了调用 IAP_CONTR=0x60 即可软件复位,然后通过ISP的方式进行升级,这种也是可以的,就和重新上电烧程序的方式一致,这里不多做介绍,感兴趣的可以去官网看看有例程介绍

四、IAP升级代码

以下是官方例程,可以根据自己使用来修改协议(笔者是自己更改了串口、协议来进行升级,这个看个人需求) absacc.h文件 内容如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46

#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主程序

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
#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中函数的长度信息计算出各个函数重定位的地址,

1
2
3
4
5
ReadIAP的重定位的地址为0x8000

ProgramIAP的重定位的地址为0x8016

EraseIAP的重定位的地址为0x802C

第四步:打开项目选项中的“BL51 Locate”属性页

在上图的“Code”域中输入下列语句

1
“?PR?_READIAP?DEMO(0x8000), ?PR?_PROGRAMIAP?DEMO(0x8016), ?PR?_ERASEIAP?DEMO(0x802C)