非IAP系列STC单片机的bootloader实现
来源:互联网 发布:不出门而知天下 编辑:程序博客网 时间:2024/05/17 22:33
以下内容受网友启发,结合Keil软件的中断矢量位置,编译设置选项和Hex文件合并实现假的bootloader,只为好玩,毕竟STC12CA60S2上面竟然有64K Flash,写个带液晶带串口(非中断模式)的bootloader绰绰有余。不足之处还请各位牛人指导,各位高手斧正,各位喷子勿喷。
非IAP系列的stc单片机实现bootloader的关键问题之一就是用户代码和bootloader代码都存储于同一块flash中,一旦更新了用户程序,则原先的bootloader也被擦除了。不过也不是没有解决的办法,对于大容量的stc单片机,如STC12C5A60S2来说,内部有64K Flash。足以同时存储用户代码和一段功能齐全的bootloader代码。
好了,存储容量的问题解决了,第二个问题来了。用户代码可以放到代码段任意位置,那么中断处理程序呢。按照单片机的中断执行过程来说,中断矢量的位置是固定不变的,从编译器生成的Hex的00003H地址开始,用到多少个中断则编译器对应的在0000BH,00013H,0001BH…地址生成LJMP xxxxH跳转指令,(其中xxxxH为编译完成后对应中断程序的入口地址)。发生中断时堆栈处理PC,然后跳转到上面所说的程序存储器中的中断服务区,执行对应的LJMP指令跳转到对应的中断程序服务区执行中断程序,返回时恢复PC。
Keil中C51选项卡下有一个Interrupt vectors at a 0xXXXX选项,这个选项是做什么的呢:
既然中断矢量不可以修改,但是这里怎么可以重新设置呢?我们试一下将其定义到0x0100,修改CStartup.A51文件中中断矢量表内容,使其指向设定的地址
EXTRN CODE (?C_START)
PUBLIC ?C_STARTUP
CSEG AT 0003H //外部中断0指向新地址
LJMP 0103H
CSEG AT 000BH
LJMP 010BH
CSEG AT 0013H
LJMP 0113H
CSEG AT 001BH
LJMP 011BH
… //用到几个中断就依次修改多少个,如stc12系列有8个中断,则修改8个中断矢量
CSEG AT 0 //这里决定代码生成后占用的起始地址,先不修改
?C_STARTUP: LJMP STARTUP1
编写程序,主程序中开通外部中断0和1,再编写一个简单的只有编译完成后只有几个代码的void leds()函数,但是我们不调用它,那么编译器会把它当作中断程序处理(由于这段程序生成的代码很少,则不会生成LJMP指令,编译器会把它放到某个中断的中断矢量表位置,具体看编译后的结果)。编译一下代码:
#include <stc12c5a.h>
#define uchar unsigned char
#define uint unsigned int
void delay(void) //22.1184M,1S,误差 -0.000000000227us
{
unsigned char a,b,c;
for(c=23;c>0;c--)
for(b=216;b>0;b--)
for(a=184;a>0;a--);
}
void leds()
{
P1=0x02;
delay();
(*(void (*)(void))(0x00000078))();// //跳转到CStartup.A51入口,见生成的.M51后修改
}
void int0(void) interrupt 0
{
IE0=0;
P1=0x04;
delay();
}
void int1(void) interrupt 2
{
IE0=0;
P1=0x08;
delay();
}
void main(void)
{
IT0=0;
EX0=1;
IT1=0;
EX1=1;
EA=1;
P1=0x01;
delay();
(*(void (*)(void))(0x00000084))();//跳转到流水灯函数,见生成的.M51文件后修改
while(1)
;
}
以下是编译后生成的*.M51文件,描述了编译完成时各个程序段的入口地址
TYPE BASE LENGTH RELOCATION SEGMENT NAME
-----------------------------------------------------
* * * * * * * D A T A M E M O R Y * * * * * * *
REG 0000H 0008H ABSOLUTE "REG BANK 0"
IDATA 0008H 0001H UNIT ?STACK
* * * * * * * C O D E M E M O R Y * * * * * * *
CODE 0000H 0003H ABSOLUTE
CODE 0003H 0003H ABSOLUTE
0006H 0005H *** GAP ***
CODE 000BH 0003H ABSOLUTE
000EH 0005H *** GAP ***
CODE 0013H 0003H ABSOLUTE
0016H 0005H *** GAP ***
CODE 001BH 0003H ABSOLUTE
CODE 001EH 001CH UNIT ?PR?INT0?MAIN
CODE 003AH 001CH UNIT ?PR?INT1?MAIN
CODE 0056H 0015H UNIT ?PR?MAIN?MAIN
CODE 006BH 000DH UNIT ?PR?DELAY?MAIN
CODE 0078H 000CH UNIT ?C_C51STARTUP
CODE 0084H 0009H UNIT ?PR?LEDS?MAIN
008DH 0076H *** GAP ***
CODE 0103H 0003H ABSOLUTE
0106H 000DH *** GAP ***
CODE 0113H 0003H ABSOLUTE
说明,红色的部分就是中断矢量表和修改后的中断矢量表,其中ABSOLUTE表示生成的是一个绝对跳转地址,见后面Hex文件。上面说到的void leds( ) 函数验证了编译器对未调用的程序则处理为中断程序,本例中编译器把它放到了未使用的定时器0中断的位置,见绿色部分内容。紫色部分是C语言中绝对地址跳转指令,相当于汇编语言中的地址跳转指令,后面会讲他的用法。
下面我们来看下新的中断矢量表代码:
00003H为外部中断0的中断矢量,我们看到其数值为02 01 03,翻译过来就是LJMP 0103H的意思,那么我们看下00103H的位置,即上图中高亮部分02 00 1E。那么难道说外部中断0的中断处理函数在0001EH处?我们看下生成的.M51文件,果然:
CODE 001EH 001CH UNIT ?PR?INT0?MAIN
Int0处理程序在code段的001EH处,长度为001CH字节。
好了,现在我们知道C51选项卡下中断矢量位置修改的意义了。运行程序,可以看到对应的指示灯跳变。好了,下面我们修改一下生成的Hex文件,把外部中断0处理函数指向外部中断1的处理函数,那么两个中断会执行同一段代码吗?修改过程见下图中阴影,注意,直接修改Hex文件后不要勾选Download/下载 按钮下的第一项:“每次下载前重新调入已在缓冲区的文件…”这一选项,这回重新读取先前的Hex文件,导致修改无效。
重新下载,运行,发现结果和预料的一样。
注意:0x0000地址为CStartup.A51中C_C51STARTUP的地址,单片机总是从这个地址开始执行代码。这个函数很重要,主要完成硬件初始化和变量赋初值的作用,
好了,这次大家应该看明白了:中断程序和其他函数一样,其实可以放到任意位置。前者需要修改中断矢量表,即利用Option->C51选项卡下中断矢量位置一栏在指定位置生成跳转矢量(即软件中断矢量)表地址,但是单片机中断处理时是从默认的中断矢量表开始的,所以必须在汇编里面在对应位置写入跳转指令指向这个新的中断矢量0x100,同样,用到多少个中断就修改几个,而后者需要修改Startup.A51中的C_C51STARTUP函数的入口地址:
这里,我们尝试修改其地址到00100H,Startup.A51中修改下对应C_C51STARTUP函数的位置:
PUBLIC ?C_STARTUP
CSEG AT 0003H
LJMP 0103H
CSEG AT 000BH
LJMP 010BH
CSEG AT 0013H
LJMP 0113H
CSEG AT 001BH
LJMP 011BH
CSEG AT 0100H
?C_STARTUP: LJMP STARTUP1
此外,修改下其他函数开始的位置:
在Option->BL51 Locate选项卡中去掉Use Memory Layout from Dev选项卡前的复选框,在Code Range中写入0x0100,告诉编译器生成的代码由0x100开始:
重新编译,看生成的.M51和Hex文件。
TYPE BASE LENGTH RELOCATION SEGMENT NAME
-----------------------------------------------------
* * * * * * * D A T A M E M O R Y * * * * * * *
REG 0000H 0008H ABSOLUTE "REG BANK 0"
IDATA 0008H 0001H UNIT ?STACK
* * * * * * * C O D E M E M O R Y * * * * * * *
0000H 0003H *** GAP ***
CODE 0003H 0003H ABSOLUTE
0006H 0005H *** GAP ***
CODE 000BH 0003H ABSOLUTE
000EH 0005H *** GAP ***
CODE 0013H 0003H ABSOLUTE
0016H 0005H *** GAP ***
CODE 001BH 0006H ABSOLUTE
0021H 00E2H *** GAP ***
CODE 0103H 0003H ABSOLUTE
CODE 0106H 000DH UNIT ?PR?DELAY?MAIN
CODE 0113H 0003H ABSOLUTE
CODE 0116H 001CH UNIT ?PR?INT0?MAIN
CODE 0132H 001CH UNIT ?PR?INT1?MAIN
CODE 014EH 0015H UNIT ?PR?MAIN?MAIN
CODE 0163H 000CH UNIT ?C_C51STARTUP
CODE 016FH 0009H UNIT ?PR?LEDS?MAIN
下载,运行?等等,出了什么情况,进不了主程序?
看下Hex文件中代码,00000H处应该是跳转指令指向0x100才对,此时为00H,在单片机顺序执行到第一个跳转即外部中断的中断矢量位置,所以看到的是外部中断0执行的情况。手动修改Hex文件,将00000H地址改为跳转到0100H(02 01 00),再下载,ok。
好了,一个bootloader的雏形已经诞生。什么?没看清…
总结一下:
bootloader程序编写:自然是上电复位就要执行,而中断矢量区需要留给用户程序用,即bootloader程序中不可以含有任何中断。之后,根据情况,需要下载程序时调用STC单片机的软件复位指令ISP_CONTR = 0x66复位到ISP监视区,更新整块flash。否则用绝对地址跳转函数跳转到0x100(这个地址根据bootloader大小来决定),执行用户程序的C_C51STARTUP函数。注意:要在bootloader的Startup.A51源程序中将所有中断矢量设置到0x103开始的位置(即C_C51STARTUP入口地址+3)。
用户程序的编写:利用Option->C51选项卡下中断矢量位置一栏在指定位置生成跳转矢量(即软件中断矢量)表地址;在Option->BL51 Locate选项卡中去掉Use Memory Layout from Dev选项卡前的复选框,在Code Range中写入0x0100,告诉编译器生成的代码由0x100开始:用户程序的Startup.A51文件不用做任何修改。
文件合并:将用户程序的Hex文件拷贝到bootloader的0x100地址开始的地方,将二者合并下载即可。
后记:由于STC不会公布ISP下载软件的源代码,我考虑自己编写一个VC程序,将bootloader和用户程序的Hex文件合并后再通知STC_ISP软件进行下载。
- 非IAP系列STC单片机的bootloader实现
- 关于STC IAP系列单片机下载不进的解决办法
- STC系列单片机内部AD的应用
- STC的IAP功能使用
- STC系列单片机烧录
- STC单片机的延时
- 使用STC系列单片机和Atmel系列单片机存在的问题
- STC单片机PWM的实现方法与原理参考实例
- STC单片机的protues仿真
- STC单片机的命名规则
- STC单片机的下载协议
- KEIL中怎样添加STC系列单片机
- stc单片机远程升级12系列
- stc单片机远程升级89系列
- STM32 IAP的bootloader编程
- STM32的bootloader IAP编程
- STM32的bootloader IAP编程
- STM32的bootloader IAP编程
- js获取url参数
- 深入理解js闭包
- 2011-06-09 shell脚本学习
- JSP页面之间传递参数时乱码的解决
- SQl 语句(常见) 新建,删除,修改表结构
- 非IAP系列STC单片机的bootloader实现
- 软件开发人员的作战手册 - 让程序员活的久一点
- 设置网站404页面的正确做法
- Android中的FrameBuffer
- Android GUI更新过程
- android make 命令使用
- 一、数据库启动
- 从FrameBuffer中获取Android屏幕截图
- ColorPicke 拾色器组件 Grid 组件