非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代码。

好了,存储容量的问题解决了,第二个问题来了。用户代码可以放到代码段任意位置,那么中断处理程序呢。按照单片机的中断执行过程来说,中断矢量的位置是固定不变的,从编译器生成的Hex00003H地址开始,用到多少个中断则编译器对应的在0000BH,00013H,0001BH…地址生成LJMP xxxxH跳转指令,(其中xxxxH为编译完成后对应中断程序的入口地址)。发生中断时堆栈处理PC,然后跳转到上面所说的程序存储器中的中断服务区,执行对应的LJMP指令跳转到对应的中断程序服务区执行中断程序,返回时恢复PC

KeilC51选项卡下有一个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

 

编写程序,主程序中开通外部中断01,再编写一个简单的只有编译完成后只有几个代码的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.A51C_C51STARTUP的地址,单片机总是从这个地址开始执行代码。这个函数很重要,主要完成硬件初始化和变量赋初值的作用,

好了,这次大家应该看明白了:中断程序和其他函数一样,其实可以放到任意位置。前者需要修改中断矢量表,即利用Option->C51选项卡下中断矢量位置一栏在指定位置生成跳转矢量(即软件中断矢量)表地址,但是单片机中断处理时是从默认的中断矢量表开始的,所以必须在汇编里面在对应位置写入跳转指令指向这个新的中断矢量0x100,同样,用到多少个中断就修改几个,而后者需要修改Startup.A51中的C_C51STARTUP函数的入口地址:

这里,我们尝试修改其地址到00100HStartup.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开始:

重新编译,看生成的.M51Hex文件。

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函数。注意:要在bootloaderStartup.A51源程序中将所有中断矢量设置到0x103开始的位置(C_C51STARTUP入口地址+3)

用户程序的编写:利用Option->C51选项卡下中断矢量位置一栏在指定位置生成跳转矢量(即软件中断矢量)表地址;在Option->BL51 Locate选项卡中去掉Use Memory Layout from Dev选项卡前的复选框,在Code Range中写入0x0100,告诉编译器生成的代码由0x100开始:用户程序的Startup.A51文件不用做任何修改。

文件合并:将用户程序的Hex文件拷贝到bootloader0x100地址开始的地方,将二者合并下载即可。

后记:由于STC不会公布ISP下载软件的源代码,我考虑自己编写一个VC程序,将bootloader和用户程序的Hex文件合并后再通知STC_ISP软件进行下载。

原创粉丝点击