IAP(In Application Program)在线应用编程
官方资料(STVD开发环境):例程AN2659,
《 AN2659 Application Note.pdf 》
要实现在线升级,MCU代码须分为 : bootloader和 用户代码App 两个部分。
图1.STM8下IAP程序的存储方式
用户启动区域(UBC): (可理解为用户自定义的bootloader的存放区域)
包含有复位和中断向量表,它可用于存储IAP及通讯程序。UBC有一个两级保护结构可保护用户代码及数据在IAP编程中免于无意的擦除或修改。这意味着该区域总是写保护的,而且写保护不能通过使用MASS密钥来解锁。它的大小可通过配置option bytes 设置。
一、中断向量表:
STM8的中断向量表的地址是固定的,位于0x8000~0x8080,即发生中断时,程序将会跳转至0x8000~0x8080的中断向量表中寻找中断入口地址,继而跳转至对应的中断服务函数中执行中断程序。
若是要实现IAP,而0x8000~0x8080的中断向量表将会位于UBC区域,被bootloader所占用。如此一来,UBC区域中的bootloader程序与MAIN PROGRAM区域中的App发生中断时,程序同样都是跳转至UBC区域中的中断向量表中寻找中断入口地址。所以,若App要执行App自己的中断服务函数,则须在App中建立自己的中断向量表,并对bootloader中的中断向量表进行重定向,将程序跳转至App的中断向量表。如图2、图3所示。
图2.中断跳转示意图 图3.中断跳转流程图
中断向量表的重定向:(以STVD环境下的官方例程AN2659为例)
以下重定向即可使App程序能够找到自己对应的中断服务函数,但是bootloader的中断将不可使用
原中断向量表为:
struct interrupt_vector const _vectab[] = {
{0x82, (interrupt_handler_t)_stext}, /* reset */
{0x82, NonHandledInterrupt}, /* trap */
{0x82, NonHandledInterrupt}, /* irq0 */
{0x82, NonHandledInterrupt}, /* irq1 */
{0x82, NonHandledInterrupt}, /* irq2 */
{0x82, NonHandledInterrupt}, /* irq3 */
{0x82, NonHandledInterrupt}, /* irq4 */
{0x82, NonHandledInterrupt}, /* irq5 */
{0x82, NonHandledInterrupt}, /* irq6 */
{0x82, NonHandledInterrupt}, /* irq7 */
{0x82, NonHandledInterrupt}, /* irq8 */
{0x82, NonHandledInterrupt}, /* irq9 */
{0x82, NonHandledInterrupt}, /* irq10 */
{0x82, NonHandledInterrupt}, /* irq11 */
{0x82, NonHandledInterrupt}, /* irq12 */
{0x82, NonHandledInterrupt}, /* irq13 */
{0x82, NonHandledInterrupt}, /* irq14 */
{0x82, NonHandledInterrupt}, /* irq15 */
{0x82, NonHandledInterrupt}, /* irq16 */
{0x82, NonHandledInterrupt}, /* irq17 */
{0x82, NonHandledInterrupt}, /* irq18 */
{0x82, NonHandledInterrupt}, /* irq19 */
{0x82, NonHandledInterrupt}, /* irq20 */
{0x82, NonHandledInterrupt}, /* irq21 */
{0x82, NonHandledInterrupt}, /* irq22 */
{0x82, NonHandledInterrupt}, /* irq23 */
{0x82, NonHandledInterrupt}, /* irq24 */
{0x82, NonHandledInterrupt}, /* irq25 */
{0x82, NonHandledInterrupt}, /* irq26 */
{0x82, NonHandledInterrupt}, /* irq27 */
{0x82, NonHandledInterrupt}, /* irq28 */
{0x82, NonHandledInterrupt}, /* irq29 */
};
改为
struct interrupt_vector const UserISR_IRQ[32] @ MAIN_USER_RESET_ADDR; //MAIN_USER_RESET_ADDR为主程序区的首地址 的宏定义
//redirected interrupt table
struct interrupt_vector const _vectab[] = {
{0x82, (interrupt_handler_t)_stext}, /* reset */
{0x82, (interrupt_handler_t)(UserISR_IRQ+ 1)}, /* trap */
{0x82, (interrupt_handler_t)(UserISR_IRQ+ 2)}, /* irq0 */
{0x82, (interrupt_handler_t)(UserISR_IRQ+ 3)}, /* irq1 */
{0x82, (interrupt_handler_t)(UserISR_IRQ+ 4)}, /* irq2 */
{0x82, (interrupt_handler_t)(UserISR_IRQ+ 5)}, /* irq3 */
{0x82, (interrupt_handler_t)(UserISR_IRQ+ 6)}, /* irq4 */
{0x82, (interrupt_handler_t)(UserISR_IRQ+ 7)}, /* irq5 */
{0x82, (interrupt_handler_t)(UserISR_IRQ+ 8)}, /* irq6 */
{0x82, (interrupt_handler_t)(UserISR_IRQ+ 9)}, /* irq7 */
{0x82, (interrupt_handler_t)(UserISR_IRQ+10)}, /* irq8 */
{0x82, (interrupt_handler_t)(UserISR_IRQ+11)}, /* irq9 */
{0x82, (interrupt_handler_t)(UserISR_IRQ+12)}, /* irq10 */
{0x82, (interrupt_handler_t)(UserISR_IRQ+13)}, /* irq11 */
{0x82, (interrupt_handler_t)(UserISR_IRQ+14)}, /* irq12 */
{0x82, (interrupt_handler_t)(UserISR_IRQ+15)}, /* irq13 */
{0x82, (interrupt_handler_t)(UserISR_IRQ+16)}, /* irq14 */
{0x82, (interrupt_handler_t)(UserISR_IRQ+17)}, /* irq15 */
{0x82, (interrupt_handler_t)(UserISR_IRQ+18)}, /* irq16 */
{0x82, (interrupt_handler_t)(UserISR_IRQ+19)}, /* irq17 */
{0x82, (interrupt_handler_t)(UserISR_IRQ+20)}, /* irq18 */
{0x82, (interrupt_handler_t)(UserISR_IRQ+21)}, /* irq19 */
{0x82, (interrupt_handler_t)(UserISR_IRQ+22)}, /* irq20 */
{0x82, (interrupt_handler_t)(UserISR_IRQ+23)}, /* irq21 */
{0x82, (interrupt_handler_t)(UserISR_IRQ+24)}, /* irq22 */
{0x82, (interrupt_handler_t)(UserISR_IRQ+25)}, /* irq23 */
{0x82, (interrupt_handler_t)(UserISR_IRQ+26)}, /* irq24 */
{0x82, (interrupt_handler_t)(UserISR_IRQ+27)}, /* irq25 */
{0x82, (interrupt_handler_t)(UserISR_IRQ+28)}, /* irq26 */
{0x82, (interrupt_handler_t)(UserISR_IRQ+29)}, /* irq27 */
{0x82, (interrupt_handler_t)(UserISR_IRQ+30)}, /* irq28 */
{0x82, (interrupt_handler_t)(UserISR_IRQ+31)}, /* irq29 */
};
typedef void @far (*interrupt_handler_t)(void); //定义中断函数地址类型
struct interrupt_vector {
unsigned char interrupt_instruction; //指令,其中0x82为跳转指令
interrupt_handler_t interrupt_handler; //函数地址
};
二、FLASH块编程程序的存放:
对于stm8的块编程,代码必须在Ram中运行,因为在块编程时Flash程序将会停止运行。因此,存储在Flash中的代码(与Flash编程相关的代码)必须拷贝至Ram中进行编译、链接、运行。这样,程序将在Ram中执行块编程,块编程时Flash的状态也不会影响到Ram中的程序继续运行。
方法如下:
1、代码的编写:
将Flash编程相关的代码存放至FLASH_CODE段:
#pragma section (FLASH_CODE) //将代码存放至RAM
void Write_Flash(void)
{
//....
}
void Erase_Flash(void)
{
//....
}
#pragma section () //代码放置至默认段
注意,在调用这些函数之前,必须把这些代码拷贝至RAM中,
STVD的cosmic编译器可以使用内置函数 int _fctcpy(char name)实现此功能。
int _fctcpy(char name) //name为定义的段名首字母
如本例中Flash块编程所在的段名为FLASH_CODE,其首字母为 ‘F’ 故在调用这些函数之前须执行
_fctcpy('F');
2、STVD的设置:在STVD--Settings--Linker选项卡下,选择目录Category下的Input,
在Ram下新建Section,Option填-ic,表示可移至RAM。
三、程序的跳转
若bootloader程序要跳转到app,须将程序跳转至主程序区的首地址。
可使用汇编指令完成:
{
//reset stack pointer (lower byte - because compiler decreases SP with some bytes)
_asm("LDW X, SP ");
_asm("LD A, $FF");
_asm("LD XL, A ");
_asm("LDW SP, X ");
//跳转至App程序 :
_asm("JPF $A000");//这里假设app首地址为0x00A000。
/***或者可以***/
_asm("JPF [_MainUserApplication]");//跳转至app程序首地址,其中_MainUserApplication的定义在下面有所解释
}
/*****其中:*****/
//typedef @far void (*)(void) TFunction;
typedef @far void (*TFunction)(void);
//main application code (user reset) - init user code start - to interrupt table reset jump
const TFunction MainUserApplication = (TFunction)MAIN_USER_RESET_ADDR;