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 可执行程序有效标志 0xFFFB 4 0x55AA55AA

根据上述分配的空间修改对应的链接文件 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;
}

STM8 bootloader 升级方案程序设计(一)的更多相关文章

  1. Flutter项目之app升级方案

    题接上篇的文章的项目,还是那个空货管理app.本篇文章用于讲解基于Flutter的app项目的升级方案. 在我接触Flutter之前,做过一个比较失败的基于DCloud的HTML5+技术的app,做过 ...

  2. Android数据库无缝升级方案

    软件迭代过程中,业务不断更新,也要求软件持续更新.相应地,数据库更新升级也是不可避免的一个环节.Android作为客户端应用,数据库升级相对于服务端来说会麻烦一些.常见的升级方式有: 1.删除旧表和数 ...

  3. kafka线上滚动升级方案记录

    kafka升级方案 为什么进行kafka升级 一.修改unclean.leader.election.enabled默认值Kafka社区终于下定决心要把这个参数的默认值改成false,即不再允许出现u ...

  4. Nginx版本平滑升级方案

    背景:由于负载均衡测试服务器中nginx版本过低,存在安全漏洞,查询相关修复漏洞资料,需要采取nginx版本升级形式对漏洞进行修复. Nginx平滑升级方案 1.案例采用版本介绍 旧版本 nginx- ...

  5. Zookeeper的服务器的log4j升级为log4j2的升级方案(忽略配置化兼容问题)

    参考在线markdown编辑器: http://marxi.co/ Zookeeper的服务器的log4j升级为log4j2的升级方案(忽略配置化兼容问题) 目前希望可以升级将Zookeeper中lo ...

  6. Bootloader升级方式一————擦、写flash在RAM中运行

    在汽车ECU软件运行中,软件代码运行安全性是第一,在代码中尽可能的不要固化有flash_erase.flash_write操作存在,主要是防止当出现异常情况时,程序跑飞,误调用erase.write对 ...

  7. Rabbitmq集群升级方案

    升级Rabbitmq 3.6.3版本至3.6.6版本,升级过程中的一些关键步骤记录 Step 1: 顺序关闭集群所有节点,这里注意最后一个关闭的节点必须保证为硬盘节点,而非RAM节点: centOS ...

  8. winform自动升级方案

    未涉及过winform升级,研究一阵,大致出来个不成熟的方案. 我的解决方案(判断升级,升级程序下载安装包的压缩包,解压,自动安装,重新启动程序). 1.首先根据服务器中软件版本号和本地软件版本号是否 ...

  9. HK设备安全补丁升级方案

    1.背景:          当前很多HK行业设备的端口映射到公网上,其中一部分老版本设备是存在安全漏洞的,由于传统行业没有设备平台的概念,无法通过设备提示用户进行升级,导致这些存在漏洞的设备在互联网 ...

  10. STM32 BootLoader升级固件

    一.知识点 1.BootLoader就是单片机启动时候运行的一段小程序,这段程序负责单片机固件的更新,也就是单片机选择性的自己给自己下程序.可以更新,也可以不更新,更新的话,BootLoader更新完 ...

随机推荐

  1. 智慧城市大数据运营中心 IOC 之 Web GIS 地图应用

    前言 IOC(Intelligent Operations Center)--智慧城市智能运营中心就是智慧城市的大脑,是建立在各个智慧应用系统之上的系统.通过对政府各职能部门的业务信息共享与整合,聚焦 ...

  2. 详解异步任务 | 看 Serverless Task 如何解决任务调度&可观测性中的问题

    在上篇文章<解密函数计算异步任务能力之「任务的状态及生命周期管理」>中,我们介绍了任务系统的状态管理,并介绍了用户应如何根据需求,对任务状态信息进行实时的查询等操作.在本篇中我们将会进一步 ...

  3. 五、java操作redis

    系列导航 一.redis单例安装(linux) 二.redis主从环境搭建 三.redis集群搭建 四.redis增加密码验证 五.java操作redis --demo主方法 package com. ...

  4. 深入理解Kafka核心设计及原理(一):初识Kafka

    转载请注明出处: 1.1 kafka简介 Kafka 起初是由 Linkedin 公司采用 Scala 语言开发的一个多分区.多副本且基于 ZooKeeper协调的分布式消息系统,现己被捐献给 Apa ...

  5. Jinfo 查看 jvm 配置及使用 Jstat 查看堆内存使用与垃圾回收

    本文为博主远传,未经允许不得转载: 1. Jinfo 查看正在运行的Java应用程序的扩展参数: 包含 JVM 参数与 java 系统参数 命令:  jinfo pid 2. 使用 jstat 查看堆 ...

  6. SD协议-状态机

    1.SD卡状态回顾 2.SD卡数据传输模式 SD卡在接收到CMD3之后就会进入data transfer state,初始状态时standby state,表示空闲状态 SD卡在standby sta ...

  7. 【TouchGFX】MVP 示例分析

    控制流 数据流 硬按键改变View界面内容 backend --> model --> presenter --> view    View button 控制电路板LED亮灭 vi ...

  8. 海思Hi35xx uboot启动分析总结

    前言 在嵌入式linux设备中,uboot的最终目的就是启动kernel.对于uboot而言,没有人把它引导起来,所以uboot首先需要把自己加载起来,然后再去引导kernel的启动,这也就可以大致的 ...

  9. [转帖]Oracle中为什么需要NCHAR

    https://zhuanlan.zhihu.com/p/668402759 Oracle中已经有了char.varchar2等字符类型,为什么又弄出一个nchar.nvarchar2? Oracle ...

  10. [转帖]TiKV Control 使用说明

    https://docs.pingcap.com/zh/tidb/stable/tikv-control TiKV Control(以下简称 tikv-ctl)是 TiKV 的命令行工具,用于管理 T ...