×
嵌入式 > 技术百科 > 详情

STM8的IAP与APP

发布时间:2021-01-20 发布时间:
|

最近项目需要,开发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




『本文转载自网络,版权归原作者所有,如有侵权请联系删除』

热门文章 更多
灾难本可避免?美媒:波音偷改737Max防失速系统 少装一个传