在Keil中利用分散加载(scatter)在Flash中实现App区和固件区的分离

为什么要在Flash中实现独立的固件区

在嵌入式系统(这里主要指裸机系统或者使用RTOS的系统)中,经常使用各种协议栈(Modbus,mqtt,http)或者像一些加密算法等等,这些协议或者实现方式都是非常成熟的,基本在整个产品生命中其中都不太可能被修改,因此我称之为嵌入式软件中的固件。产品化的嵌入式系统的Flash一般都会划分为bootloader 和app 区,这些固件其实也可以单独分区,放在Flash中的独立的固定位置,也不用和app在一个project(或者不是一个project target)中一起编译,但是在app运行时可以被调用。这样做在工程中有如下的好处:

1. 加快编译速度

如果在app中都没有增加这些固定模块的代码,那必然会加快编译速度,这个是显而易见的。不过使用静态编译也能实现这一点;

2. 节省OTA时的流量

特别是在物联网嵌入式产品中,OTA时只需要升级APP区,不用升级固件区。这样做减少OTA时传输的数据;

3. 提高Flash的空间利用率

一些嵌入式产品中,app区采用A、B分区,可在A、B区两个区轮流升级和运行,实现不停机ota。但这样分区,会让flash资源大打折扣。例如原本256K的flash,去掉boot后再分A、B两个区,留给app的空间大小就只有100K多一点,就显得很紧张了。

如果将固件区单独分为一个区,可以让无论程序运行在A区或者B区时,都可以调用固件区的模块,这样就相当于可以将固件区的空间复用,提高了空间利用率。

固件区与APP区分开的可行性原理

对于一些嵌入式软件的初学者来说可能会有一些困惑,如果这些模块都不和APP在同一个project中编译,如何被app调用。任何一个函数都是以二进制的方式存储在Flash上的,如果用相同的编译器和编译参数,在同一MCU平台上,无论该函数被放置在什么地址,或者是被那个target或是project编译,其最后存储的二进制代码都是一样的。因此一个嵌入式APP软件只要知道一个函数的类型声明和在Flash位置,就可以直接调用该函数。

如何将函数放在Flash的固定区域

首先我们说一下如何将一个函数甚至是一个模块放在Flash的固定位置。由于笔者一般都是用keil,因此以下的方法都是基于keil环境:

方法一:

如果只有一个函数,我们可以在函数声明的时候在函数末尾加上__attribute__((section(".ARM.__at_0xXXXXXXXX")))的语句,将某个函数编译后固定存放在Flash的固定位置。

void function(void) __attribute__((section(".ARM.__at_0xXXXXXXXX")));

void function(void)
{
    ...
}

方法二:

如果要将几个函数一起放在一块区域内,那就不太可能使用方法一了,我们没法提前知道这些函数的长度,就算知道这些函数的长度,还要手动指定其在flash中的位置,让其紧密排列,这样过于麻烦。此时可以将几个函数统都加上__attribute__((section("abc"))),然后可以将几个函数都放在section abc中,编译器自动会将其紧密排列。但是怎样将section指定位置呢?这里就要引出Keil中的分散加载(scatter)了。

如何使用Keil中的分散加载

Keil中的分散加载在Target的设置文件中的Linker页面,点击取消左上角的勾,就可以编辑下面的Scatter File了。

分散加载的.sct文件一般长成这样:

具体的文件格式大家可以自己搜索查看其他文章或者查看官方文档,这里不在赘述了。

如果我们要将一个section放在一个固定的区域,可以如下修改scatter文档

上面这段代码中就是将section(abc)放在从地址0x08013000的固定区域,最大长度0x1000。

方法三

当然如果用分散加载,还可以有更方便的方法。如下的设置就是将整个"abc"模块都放在了这个app区中了,不用再为每个函数单独添加语句。

但是这里要注意,Flash中只能存放一个模块中的函数和常量,全局变量和静态变量是无法放在Flash中的。而且分散加载中的文件其实是可以使用通配符的,因此使用十分方便。

如何引用Flash中已知位置的函数

如果知道了在函数在Flash中的位置和函数的类型,就可以在程序中直接定义一个函数指针,然后指向该地址,直接引用即可。但是这里面有一个坑,如果函数存放的位置是0xXXXXXXXX,那函数指针指向的地址应该是0xXXXXXXXX+1,如果直接赋值0xXXXXXXXX,那程序就会运行到Hardfault中了。

可以参照如下文章,图片也引用自以下文章:内存地址空间,ARM单片机代码和数据在存储空间中的分布以及函数调用中的栈帧的返回地址和函数指针_changing at section .arm.__at_0x08000ff0 type from-CSDN博客

什么样的模块适合放在固件区

最后,要先说明一下,什么样的模块适合做固件。

1. 当然这个模块是成熟的模块,最好整个产品生命周期都不需要变动;

2. 如果一个模块中只有函数和常量,没有全局变量和静态变量。如果有全局变量和静态变量,编译的时候就要分配RAM空间。而RAM可能会随着APP的升级而变化,因此会导致冲突。

  • 10
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值