STM8 bootloader 升级方案程序设计(一)

1、前言

        上一篇 单片机 IAP 功能基础开发篇之APP升级(一)讲到了单片机 IAP 功能给 APP 程序升级的设计思路,这篇介绍的是具体实现方式。

这篇介绍关于 STM8 系列实现 bootloader 功能的部分程序实现方案。

以 STM8AF5268 为例,开发环境基于 IAR EW for STM8 3.11.1。


2、实现方案

这里不介绍具体的升级方式和流程,之前的文章中都提到了。

2.1、分区选择

首先需要划分 boot 和 app 两个工程的 Flash 区域(该系列的Flash大小为32K, 其中boot 预留 8K,其他都是app的预留空间)

MCU分区

描述

起始地址

大小备注

Bootloader 程序区

BOOT 可执行程序(程序启动区)

0x8000

8K

用户程序区(APP)

APP 可执行程序(应用程序区)

0xA000

24K - 4
APP 可执行程序有效标志0xFFFB40x55AA55AA

根据上述分配的空间修改对应的链接文件 lnkstm8af5268.icf。

首先可以看一下工程默认的链接文件内容(右击工程->options...->Linker->Config,可以去IAR安装找到打开IAR Systems\Embedded Workbench 8.3\stm8\config\lnkstm8af5268.icf ):

具体可以下载了解:IAR中的链接文件.icf详解-嵌入式文档类资源-CSDN下载

/
//      Example ILINK command file for
//      STM8 IAR C/C++ Compiler and Assembler.
//
//      Copyright 2019 IAR Systems AB.
//
/

define memory with size = 16M;

define region TinyData = [from 0x00 to 0xFF];

define region NearData = [from 0x0000 to 0x17FF];

define region Eeprom = [from 0x4000 to 0x43FF];

define region BootROM = [from 0x6000 to 0x67FF];

define region NearFuncCode = [from 0x8000 to 0xFFFF];

define region FarFuncCode = [from 0x8000 to 0xFFFF];

define region HugeFuncCode = [from 0x8000 to 0xFFFF];


/

define block CSTACK with size = _CSTACK_SIZE  {};

define block HEAP  with size = _HEAP_SIZE {};

define block INTVEC with size = 0x80 { ro section .intvec };

// Initialization
initialize by copy { rw section .far.bss,
                     rw section .far.data,
                     rw section .far_func.textrw,
                     rw section .huge.bss,
                     rw section .huge.data,
                     rw section .huge_func.textrw,
                     rw section .iar.dynexit,
                     rw section .near.bss,
                     rw section .near.data,
                     rw section .near_func.textrw,
                     rw section .tiny.bss,
                     rw section .tiny.data,
                     ro section .tiny.rodata };

initialize by copy with packing = none {section __DLIB_PERTHREAD };

do not initialize  { rw section .eeprom.noinit,
                     rw section .far.noinit,
                     rw section .huge.noinit,
                     rw section .near.noinit,
                     rw section .tiny.noinit,
                     rw section .vregs };

// Placement
place at start of TinyData      { rw section .vregs };
place in TinyData               { rw section .tiny.bss,
                                  rw section .tiny.data,
                                  rw section .tiny.noinit,
                                  rw section .tiny.rodata };

place at end of NearData        { block CSTACK };
place in NearData               { block HEAP,
                                  rw section __DLIB_PERTHREAD,
                                  rw section .far.bss,
                                  rw section .far.data,
                                  rw section .far.noinit,
                                  rw section .far_func.textrw,
                                  rw section .huge.bss,
                                  rw section .huge.data,
                                  rw section .huge.noinit,
                                  rw section .huge_func.textrw,
                                  rw section .iar.dynexit,
                                  rw section .near.bss,
                                  rw section .near.data,
                                  rw section .near.noinit,
                                  rw section .near_func.textrw };

place at start of NearFuncCode  { block INTVEC };
place in NearFuncCode           { ro section __DLIB_PERTHREAD_init,
                                  ro section .far.data_init,
                                  ro section .far_func.textrw_init,
                                  ro section .huge.data_init,
                                  ro section .huge_func.textrw_init,
                                  ro section .iar.init_table,
                		  ro section .init_array,
                                  ro section .near.data_init,
                                  ro section .near.rodata,
				  ro section .near_func.text,
				  ro section .near_func.textrw_init,
                                  ro section .tiny.data_init,
                                  ro section .tiny.rodata_init };

place in FarFuncCode            { ro section .far.rodata,
                                  ro section .far_func.text };

place in HugeFuncCode           { ro section .huge.rodata,
                                  ro section .huge_func.text };

place in Eeprom                 {    section .eeprom.noinit };

place in Eeprom                 {    section .eeprom.data };

place in Eeprom                 {    section .eeprom.rodata };

/

其中 NearFuncCode、FarFuncCode 和 HugeFuncCode 就是代表 Flash 的区域,因此我们将 lnkstm8af5268.icf 拷贝分别放置 boot 和 app 工程目录下,同时进行修改。

boot 工程的修改内容如下:

define region NearFuncCode = [from 0x8000 to 0x9FFF];

define region FarFuncCode = [from 0x8000 to 0x9FFF];

define region HugeFuncCode = [from 0x8000 to 0x9FFF];

app 工程的修改内容如下:

define region NearFuncCode = [from 0xA000 to 0xFFFF];

define region FarFuncCode = [from 0xA000 to 0xFFFF];

define region HugeFuncCode = [from 0xA000 to 0xFFFF];

修改完成后,分别将修改的文件作为boot和app的链接文件,参考如下(右击工程->options...->Linker->Config)

 

2.2、中断向量表重定向

        正常情况 APP 程序中是需要中断功能的,一般在实现boot 功能的情况下都需要重定向中断向量表,而 STM8 不像 STM32 一样专门有一个寄存器管理中断向量表的地址(因此 STM32 中断向量表设置比较自由,而STM8是固定的)。

如果不进行特殊处理,在boot跳转app程序后,app的中断处理函数不会执行,反而进入的是boot中断处理函数,容易引发异常。

通过以下方式可以将中断向量表定位至app程序中,在 boot 的程序代码中添加以下字段:

/**
  * @brief 中断重映射表.
  * @note  1.应用程序 FLASH 起始地址为 0xA000
  *        2.当应用程序地址不是 0xA000 时则要相应改掉除第一个 0x82008080 以外的数值
  */ 
__root const long reintvec[]@".intvec"=
{   0x82008080, 0x8200A004, 0x8200A008, 0x8200A00c,
    0x8200A010, 0x8200A014, 0x8200A018, 0x8200A01c,
    0x8200A020, 0x8200A024, 0x8200A028, 0x8200A02c,
    0x8200A030, 0x8200A034, 0x8200A038, 0x8200A03c,
    0x8200A040, 0x8200A044, 0x8200A048, 0x8200A04c,
    0x8200A050, 0x8200A054, 0x8200A058, 0x8200A05c,
    0x8200A060, 0x8200A064, 0x8200A068, 0x8200A06c,
    0x8200A070, 0x8200A074, 0x8200A078, 0x8200A07c,
};

这种方式的缺点在于boot不能使用中断,否则会导致在boot程序中触发中断进入app程序的中断处理函数。

当然,如果需要boot也能使用中断,可以将中断向量表定位至 RAM 中,通过 RAM 映射至 boot 或者 app 程序中的中断向量表处理。之后有机会再写一篇。

此时编译,会出现以下的问题(是因为中断向量表的 Flash 分配大小只有 128 字节,定义的 reintvec[] 中断重映射表和STM8默认的中断映射关系加起来超了)

Linking 
Error[Lp004]: actual size (0x100) exceeds maximum size (0x80) for block "INTVEC" 
Error while running Linker 

修改boot的 .icf 文件如下(从默认的 0x80扩大一倍成0x100):

define block INTVEC with size = 0x100 { ro section .intvec };

关于更多的细节问题之后有机会再写一篇。

2.3、程序跳转

2.3.1、boot 跳转 app

检查 app 程序是否存在,并进行跳转。

const uint32_t *pAppValid = (uint32_t *)0xFFFB;

if (*pAppValid == 0x55AA55AA)
{
    // 这里可以执行跳转前的处理

    // 跳转
    asm("LDW X,SP");

    asm("LD A,$FF");

    asm("LD XL,A");

    asm("LDW SP,X");

    asm("JPF $A200");
}

2.3.1、app 跳转 boot

一般可以通过看门狗进行软复位直接进入,可以不用特意处理跳转前的动作

/**
  * @brief      系统软复位函数
  */
void SoftWareReset(void)
{
    WWDG->CR |= 0x80;
    WWDG->CR &= ~0x40;
}

2.4、注意事项

boot 程序启动时,必须先关闭中断(因为目前的方案 boot 程序不能使用中断)。

void main()
{
    __asm("sim\n");    /* 关全局中断 */

    // 初始化动作   

    while (1)
    {}   
}

STM8 因为 Flash 的特性需要在 RAM 中执行擦写 Flash 动作,因此需要将擦写 Flash 的操作指令放在 RAM 中执行,而 STM8 的固件库已经实现了,只需要定义宏定义 RAM_EXECUTION。便可完成擦写动作在 RAM 中执行。

 3、代码参考

Flash 擦写代码封装参考(包含 CodeFlash 和 DataFlash):



/**
  * @brief  解锁 CODE FLASH 块的数据
  * @retval 0,成功; 1,失败
  */
static uint8_t CodeFlashBlockUnlock(void)
{
  FLASH_Unlock(FLASH_MEMTYPE_PROG);
  
  /* Wait until Flash Program area unlocked flag is set*/
  while (FLASH_GetFlagStatus(FLASH_FLAG_PUL) == RESET)
  {}
  
  return 0;
}

/**
  * @brief  锁定 CODE FLASH 块的数据
  * @retval 0,成功; 1,失败
  */
static uint8_t CodeFlashBlockLock(void)
{
  /* Unlock flash data eeprom memory */
  FLASH_Lock(FLASH_MEMTYPE_PROG);
  
  /* Wait until CODE EEPROM area unlocked flag is set*/
  while (FLASH_GetFlagStatus(FLASH_FLAG_PUL) == SET)
  {}
  
  return 0;
}

/**
  * @brief  解锁 DATA FLASH 块的数据
  * @retval 0,成功; 1,失败
  */
static uint8_t DataFlashBlockUnlock(void)
{
  FLASH_Unlock(FLASH_MEMTYPE_DATA);
  
  /* Wait until Flash Program area unlocked flag is set*/
  while (FLASH_GetFlagStatus(FLASH_FLAG_DUL) == RESET)
  {}
  
  return 0;
}

/**
  * @brief  锁定 DATA FLASH 块的数据
  * @retval 0,成功; 1,失败
  */
static uint8_t DataFlashBlockLock(void)
{
  /* Unlock flash data eeprom memory */
  FLASH_Lock(FLASH_MEMTYPE_DATA);
  
  /* Wait until Data EEPROM area unlocked flag is set*/
  while (FLASH_GetFlagStatus(FLASH_FLAG_DUL) == SET)
  {}
  
  return 0;
}

/**
  * @brief      从指定地址读取一定空间大小数据.
  * @param[in]  blockAddr: Flash源地址.
  * @param[in]  destAddr: 目的地址.
  * @param[in]  size: 数据大小.
  * @retval     0,成功; 1,失败.
  */
uint8_t BSP_Flash_Read(uint16_t blockAddr, uint16_t destAddr, uint32_t size)
{
    uint8_t res = 0;
    uint8_t* pSource = (uint8_t*)blockAddr;
    uint8_t* pdest = (uint8_t*)destAddr;

    while(size--)
    {
        *(pdest++) = *(pSource++);
    }

    return res;
}

/**
  * @brief  从 CODE FLASH 指定块中编程一定大小的数据
  * @param  block : block 块
  * @param  buf : 数据
  * @param  len : 数据大小
  * @retval 0,成功; 1,失败
  */
static uint8_t CodeFlashBlockProgram(uint32_t block, uint8_t *buf, uint16_t len)
{
    FLASH_Status_TypeDef status;
        
    FLASH_ProgramBlock(block, FLASH_MEMTYPE_PROG, FLASH_PROGRAMMODE_STANDARD, buf);
     
    status = FLASH_WaitForLastOperation(FLASH_MEMTYPE_PROG);
    
    if (FLASH_STATUS_TIMEOUT == status ||
        FLASH_STATUS_WRITE_PROTECTION_ERROR == status)
    {
        return 1;
    }
    
    return 0;
}

/**
  * @brief  从 DATA FLASH 指定块中编程一定大小的数据
  * @param  block : block 块
  * @param  buf : 数据
  * @param  len : 数据大小
  * @retval 0,成功; 1,失败
  */
static uint8_t DataFlashBlockProgram(uint32_t block, uint8_t *buf, uint16_t len)
{
    FLASH_Status_TypeDef status;
        
    FLASH_ProgramBlock(block, FLASH_MEMTYPE_DATA, FLASH_PROGRAMMODE_STANDARD, buf);
     
    status = FLASH_WaitForLastOperation(FLASH_MEMTYPE_DATA);
    
    if (FLASH_STATUS_TIMEOUT == status ||
        FLASH_STATUS_WRITE_PROTECTION_ERROR == status)
    {
        return 1;
    }
        
    return 0;
}

/**
  * @brief      Flash写入.
  * @param[in]  blockAddr: Code Flash的相关地址(必须为8的倍数).
  * @param[in]  sourceAddr: 数据源地址.
  * @param[in]  size: 数据大小(单位1字节且必须为4的倍数).
  * @retval     0,成功; 1,失败.
  */

uint8_t BSP_Flash_Write(uint32_t blockAddr, uint16_t srcAddr, uint32_t size)
{
    uint8_t res = 0;
    uint8_t *srcBuf = (uint8_t *)srcAddr;
    uint8_t arrFlashBuf[128];
    uint16_t index = 0;
    uint16_t block;
    uint16_t blockNum;
    
    // 在代码 Flash 中的处理
    if (blockAddr >= STM8_CODE_FLASH_BASE && 
        blockAddr <= (STM8_CODE_FLASH_BASE + STM8_CODE_FLASH_SIZE))
    {
        CodeFlashBlockUnlock();
        block = (blockAddr - STM8_CODE_FLASH_BASE) / 128;
        blockNum = size % 128 ? size / 128 + 1 : size / 128;
        
        while (blockNum)
        {
            memcpy(arrFlashBuf, &srcBuf[index], (size > 128 ? 128 : size));
            res = CodeFlashBlockProgram(block, arrFlashBuf, 128);
            block++;
            blockNum--;
            size -= 128;
            index += 128;
        }
        
        CodeFlashBlockLock();
    }
    else // 在 DataFlash 的处理
    {
        block = (blockAddr - STM8_DATA_FLASH_BASE) / 128;
        blockNum = size % 128 ? size / 128 + 1 : size / 128;
        
        DataFlashBlockUnlock();
        
        while (blockNum)
        {
            memcpy(arrFlashBuf, &srcBuf[index], (size > 128 ? 128 : size));
            res = DataFlashBlockProgram(block, arrFlashBuf, 128);
            block++;
            blockNum--;
            size -= 128;
            index += 128;
        }  
        
        DataFlashBlockLock();
    }
    
    return res;
}

  • 3
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

大橙子疯

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值