最近项目需要,开发STM8的升级系统,也就是通过IAP进行升级APP。
MCU:STM8S207M8(不同的型号只要修改相应的接口即可)
IAP:bootloader v1.1(AN2659code)
APP:项目应用程序
开发环境:STVD+Cosmic
升级软件:官方的Flash Loader Demonstrator(后期根据协议自定义实现)
原理:MCU上电后,从0x8000地址执行,也就是bootloader的起始代码,当收到同步码时,进入升级状态,
等待命令,升级软件通过协议进行flash烧写。完成后,跳转到代码APP的起始地址,MCU开始执行APP程序。
在APP状态下,如果收到升级固件命令,跳转到IAP所在起始地址0x8000,进入升级状态。
PS:STM8内部固化了一个bootloader,可以通过配置字OPTBL打开。该bootloader支持iap uart下载程序。
由于硬件的情况,这里就不用内置bootloader,这里所讲的bootlader是指IAP。
1、由于bootloader代码并不是基于STM8S207M8的,所以需要移植,主要问题是段定义问题。
根据代码的段使用情况,在setting->Linker->Input中定义相应的段,尤其是FLASH_CODE段,
因为该段是定义在RAM中,意义是指FLASH操作全部是在RAM中运行的。
注意,设置中断向量地址为0x8000,代码段的起始地址为0x8080(向量表占0x80个字节)。
注意设置C编译优化,因为代码太大的话,APP的起始地址要向后移动,不要重叠了。
另外,还要根据FLASH、EEPROM、RAM等大小在main.h文件中进行一些配置:
#define BLOCK_BYTES 128
#define ADDRESS_BLOCK_MASK (BLOCK_BYTES - 1)
//memory boundaries
#define RAM_START 0x000000ul
#define RAM_END 0x0017FFul
#define EEPROM_START 0x004000ul
#define EEPROM_END 0x0047FFul
#define OPTION_START 0x004800ul
#define OPTION_END 0x00487Ful
#define UBC_OPTION_LOCATION 0x004801
#define FLASH_START 0x008000ul
#define FLASH_END 0x017FFFul
#define BLOCK_SIZE 0x80
#define BLOCK_PER_SECTOR 0x08
2、编译好的IAP通过SWIM工具下载到flash起始地址0x8000中。
3、根据通信协议《STM8 bootloader.pdf》,调通升级软件与IAP之间的串口通信操作。
4、这里自定义APP的起始地址为0xa000,那么在APP的工程里,设置中断向量地址为0xa000,代码段的起始地址为0xa080。
5、APP中跳转到IAP,使用汇编_asm("jp $0x8000")即可。
APP的中断执行流程:
由于STM8的中断向量表是固定的,当STM8产生中断异常后,会跳转到0x8000,执行相应的中断服务程序。但是APP的中断向量起始地址是0xa000。
那么要执行真正的中断服务程序就必须在IAP的中断向量进行定重向。一般的处理方面就是从IAP的向量表中跳转到APP的中断向量表中。
先来看IAP的中断向量表:
extern void _stext(); /* startup routine */
struct interrupt_vector const UserISR_IRQ[32] @ 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 */
};
注意到MAIN_USER_RESET_ADDR的定义:
#define MAIN_USER_RESET_ADDR 0xA000ul
MAIN_USER_RESET_ADDR就是APP的起始地址,同时也是APP的中断向量表的地址。
从IAP的中断向量表中,可以看到,当APP产生中断时,就会先跳转到IAP的中断向量表,再从IAP的中断向量表跳转到APP的中断向量表。
关于时钟的配置:
当MCU是由内部时钟启动的,启动后往往需要切换到外部高速时钟HSE,一般的代码操作是:
CLK_DeInit();
CLK_HSECmd(ENABLE);
while(SET != CLK_GetFlagStatus(CLK_FLAG_HSERDY));
CLK_SYSCLKConfig(CLK_PRESCALER_CPUDIV1);
CLK_ClockSwitchCmd(ENABLE);
CLK_ClockSwitchConfig(CLK_SWITCHMODE_AUTO , CLK_SOURCE_HSE , DISABLE , CLK_CURRENTCLOCKSTATE_ENABLE);
这里本来没有什么问题,但需要注意的是,如果在IAP和APP都进行时钟切换到HSE的时候,就可能代码一直在这个循环里:
while(SET != CLK_GetFlagStatus(CLK_FLAG_HSERDY));
所以在bootloader中切换时钟后,在APP里就不要再重复操作了。
关于该版本IAP的BUG:
void ProcessCommands(void)
{
u8 result;
do
{
//init pointer
ReceivedData = DataBuffer;
//receive data 1-st byte
if(!Receive(ReceivedData++))
continue;
//receive data 2-nd byte
if(!Receive(ReceivedData++))
continue;
//check if command bytes are complements
if(DataBuffer[N_COMMAND] != (u8)(~DataBuffer[N_NEG_COMMAND]))
{
Transmit(NACK);
continue;
}
//parse commands
Transmit(ACK);
switch(DataBuffer[0])
{
case (GT_Command): result = GT_Command_Process(); break;
case (RM_Command): result = RM_Command_Process(); break;
case (GO_Command): result = GO_Command_Process(); break;
case (WM_Command): result = WM_Command_Process(); break;
case (EM_Command): result = EM_Command_Process(); break;
}
}while( (!result) || (DataBuffer[0] != GO_Command) ); //until GO command received
其中,DataBuffer为全局数组:
//input buffer
u8 DataBuffer[130];
从ProcessCommands函数看到,在switch中判断DataBuffer[0]值,在循环中仍然用DataBuffer[0] != GO_Command来判断。
由于在执行XX_Command_Process中,通信缓冲区是用DataBuffer,所以有可能会改变DataBuffer[0]的值,当DataBuffer[0]的
值被改变为GO_Command时,就可能跳出循环,导致函数ProcessCommands返回,无法继续执行下一条命令,从而出错。
笔者遇到该问题,在升级过程中,需要写181页的数据,刚好在第61页的数据中接收到的第一个数据为0x21,也就是GO_Command的值,
从而导致无法继续升级。所以需要改为:
u8 cmd;
... ...
cmd = DataBuffer[0];
switch(cmd)
{
case (GT_Command): result = GT_Command_Process(); break;
case (RM_Command): result = RM_Command_Process(); break;
case (GO_Command): result = GO_Command_Process(); break;
case (WM_Command): result = WM_Command_Process(); break;
case (EM_Command): result = EM_Command_Process(); break;
}
}while( (!result) || (cmd != GO_Command) ); //until GO command received
『本文转载自网络,版权归原作者所有,如有侵权请联系删除』