最近这段时间使用stm8,要在项目中用到IAP升级,同时还要在BootLoader和App中同时使用中断,花了一些时间查找了很多博客,集合大家的知识,写了一篇文章来总结一下自己的成果。这是我第一次写文章,有错误的地方欢迎大家指出
BootLoader制作分析
1、单片机需要有一个对外的通信接口,一般使用的是单片机自带的串口
2、网上寻找一个稳定的通信协议,保证你的单片机在IAP升级的时,能正确稳定的传输数据。我使用的是
Ymodem协议(如果不知道的可以度娘去了解一下),因为网上有编写好的上位机,比较方便
3、了解stm8的内存分布情况,使用flash读写函数把升级的数据写入单片机的ROM里面
4、重定向中断向量表。这是BootLoader和App都可以使用中断的重点
5、程序跳转
这篇文章的重点是讲解内存和中断重定向的,所以略过1和2
**
STM8的内存分布
从stm8s的数据手册中可以看出stm8s的内存分布情况
前面的我们不用管,因为程序代码的存放是从0x8000开始的,0x8000~0x8080存放的是中断向量表。
所以我们制作BootLoader的思路如下:
根据自己的BootLoader的大小来确定App的存放位置,我这里选择的是0x8000~0x9FFF(8k)部分存放BootLoader的程序代码,0xA000之后的放App的程序代码,当然你也可以选择其他的地址。
在BootLoader里面编写好串口传输和接受函数,然后使用一个标志位来判断是否进入App或者等待接受数据升级程序,如果进入升级状态上位机或其他的设备可以通过USB转串口、蓝牙转串口等传输升级程序代码给单片机,然后BootLoader把数据写入0xA000之后的位置。
怎么把程序代码存入相应的位置呢?我是用的是IAR,可以在软件中进行如下设置:
我用的是stm8s207RB型号的mcu,大家可以根据自己的mcu选择相应的.icf文件。.icf文件可以在IAR的安装目录下找到,我的是在C:\Program Files (x86)\IAR Systems\Embedded Workbench 8.0\stm8\config文件夹中。找到相应的.icf文件之后复制到你的工程文件夹中,给App的.icf进行如下修改,就可以把App的程序位置放在0xA000处。
BootLoader的位置不用改变了,因为程序默认存储的位置就是从0x8000开始的,不过你要保证你的BootLoader程序的大小不能超过你留给它的空间(我这里预留的是8k(0x8000~0x9FFF),足够存储我的BootLoader程序),超过会占用App的位置,就会导致程序崩溃。
PS:
1、我这里使用的升级判断标志是在0x4050位置保存一个字符’U’,升级完成就把它改为’O’。你们也可以使用其他的标志。我这里之所以使用它是因为0x4000~0x47FFF这一段EEPROM在程序运行时没有使用到,当然你也可以使用其他的标志来判断。
2、BootLoader中接收到数据,需要写入flash,这就需要掌握stm8的flash读写函数,这里不做说明,不会的去问度娘。
3、BootLoader跳转到App的代码如下:
asm("LDW X, SP ");
asm("LD A, $FF");
asm("LD XL, A ");
asm("LDW SP, X ");
asm("JPF $A000"); //可以根据自己的App的位置选择
重定向中断向量表——这是这篇文章的重点
不像stm32有NVIC之类的中断控制器管理中断向量的地址,stm8的中断向量的地址是固定的,它的中断向量表被固定在0x8000~0x8080位置,最多可以有32个中断,有些单片机32个都使用了,有些只是使用其中的部分,我用的stm8s207rb就是只使用部分的。
既然它是固定的,那要怎么才能在BootLoader和App中都使用中断呢?
思路1:
mcu在BootLoader中运行的时候,0x8000~0x807F存储的是BootLoader的中断服务函数的地址(如下图)。因为0x8000 ~0x807F有128个字节,4个字节存储一个中断向量,最多有32个中断。如0x8004 ~0x8007这4个字节存储的是第二个中断向量为820099A1,82为操作数,后面的3个字节为中断服务函数的地址0x0099A1。
mcu从BootLoader跳转到App中运行的时候,把0x8000~0x807F存储的地址改为App的中断地址0xA000 ~0xA07F(如下图)。
0x8004 ~0x8007这4个字节存储的数据变为8200A004,当第二个中断产生时,mcu跳转到0x8004位置,然后继续跳转到0xA004的位置执行0xA004地址处的操作。0xA000 ~0xA07F存储的是App的中断服务函数的地址。
App产生中断,由①跳转到固定的中断向量地方,然后②跳转到App的中断向量表的位置,③跳转到App的中断服务函数处,执行相应的操作。
这种方式第一次用的时候,BootLoader和App都可以使用中断,但是当单片机复位之后,再次从BootLoader运行时,BootLoader中就不能使用中断了,因为flash里面的数据是不会随着复位改变的,0x8000~0x807F存储的内容被上一次跳转进入App时改变了,没有存储BootLoader的中断服务函数的地址,这时候当然在BootLoader中就不能使用中断了。
所以我们可以在BootLoader最开始运行没有开启中断的时候,再把0x8000~0x807F的数据改变为BootLoader的中断服务函数的地址,然后再开启中断,这样BootLoader就可以正常使用中断了。
不过这里又存在一个问题,在BootLoader运行时,怎么知道BootLoader的中断服务函数的地址呢?
我这里用了一种比较简单的方法(不过肯定不只这一种方法):
第一次运行BootLoader的时候,读取出0x8000~0x807F的存储的数据,保存到0x4100 ~0x417F处(0x4000 ~0x47FFF这一段EEPROM在程序运行时没有使用到),单片机复位第二次、第三次或更多次重新从BootLoader运行时,又读取0x4100 ~0x417F处的数据,改变0x8000 ~0x807F处的数据。代码如下:
#define MIN_USER_Start_ADDR 0xA000//用户代码(App)的起始地址 字节偏移5个字节
uint32_t FLASH_ReadWord(uint32_t Address)
{
return(*(PointerAttr uint32_t *) (uint16_t)Address);
}
void STM8_HanderIqr_Default(void)
{
uint32_t data[0x20] = {
0};
uint8_t Index;
FLASH_Unlock(FLASH_MEMTYPE_PROG);
FLASH_Unlock(FLASH_MEMTYPE_DATA);
for(Index = 1; Index < 0X20;Index++) //从1开始,是因为0x8000处存放的是复位中断,不需要重定向
{
data[Index] = FLASH_ReadWord(0X8000+4*Index); //读取初始中断向量值,也就是BootLoader的中断向量值
if(FLASH_ReadByte(0X4060) != 'R') //判断是否把中断向量的值写入EEPROM 这个函数是stm8s的库函数
{
//'R'是用来表示0x4100 ~0x417F处是否有数据
FLASH_ProgramWord(0X4100+4*Index, data[Index