完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
|
|
相关推荐
1个回答
|
|
一、技术背景
以前我用过一款庆科的WiFi模组——EMW3162,它由一块STM32F205RG芯片 + SDIO接口的射频芯片组成,有趣的是官方将这颗STM32芯片内部Flash做了很多块的划分,如下图所示。 EMW316x FLASH分配情况 可以看到1MB的Flash被分割成了5部分,分别是: 1. Bootloader,一段引导代码,一般用于更新APP程序。 2. 信息区,存放OTA的一些信息和用户参数。 3. 用户应用区,也就是APP区,用户可以二次开发后将代码烧录到此处。 4. OTA暂存区,接收OTA数据,接收完成后再复制到用户应用区。 5. 射频驱动区,用于存放SDIO射频模组的驱动,供上层使用。 这样划分的好处是显而意见的,厂家不用提供射频驱动的源码,甚至连LIB库都不用提供,并且用户在OTA时,仅需更新应用区的代码,无疑加快了OTA的速度。缺点嘛也是有的,因为每个区都要预留一些Flash空间,所以划分得越细,浪费的空间也就越多。当然相比起优点,这点缺点还是可以接受的。下文提供了一种方法来实现这种应用,可能和庆科的实现方式不一样,仅可作为一种思路。 二、技术方案 以下将用户业务层称为App,将驱动层称为Driver,我们要实现的是将Driver编译成一个固定的bin文件,即Firmware.bin,让App能够跨bin文件调用Driver中的函数,而且要求Driver层的更新不能影响App的访问。 我们知道,C语言中的函数名实际上是个地址,只要知道了函数的地址、函数的格式,就可以调用这个函数。所以关键点在于如何让App知道Firmware.bin中各个函数的地址。以下提供了几种不同的方案,为了方便说明,下文所有所述内容均不包括Bootloader区、Param区,仅有App区和Driver区。 方案一:在Driver工程中,声明一个结构体,结构体成员为供App使用的函数指针,然后定义一个初始化函数,该函数用于完成上述结构体指针的初始化,然后将该函数放到某一约定好的地址,App需要根据该地址调用这个初始化函数,这样App就获得了所需的Driver中的函数指针。 方案二:将Driver中的对外函数地址按4字节对齐,顺序地排列到Driver区的起始地址上,App层声明一个结构体,结构体成员为所需的函数指针,然后定义一个该结构体指针,强制指向Driver区的起始地址,因为STM32中的函数指针是4字节的,所以这个结构体指针的成员实际已经指向了Driver层对应的函数。 由于方案二更简洁,所以本文使用方案二来实现目的。 三、技术原理 3.1 核心思想 根据方案二所述,最大的难点在于如何将函数地址按顺序排列到Driver区的起始地址上。事实上,答案就在ST提供的启动文件里,启动文件为中断向量表分配了固定的Flash空间,我们完全可以仿照这一方法。对启动文件不熟悉的读者可以参考我的另一篇文章:STM32启动流程详解。 下面的例子使用的是Keil MDK编译平台,芯片为STM32F103RCT6,Flash和Ram分配如下: Flash和Ram分配情况 3.2 Driver工程代码 以下是Driver工程仿照启动文件编写的汇编文件,用于将所需函数地址按序排列在起始地址上。 PRESERVE8 THUMB ; Vector Table Mapped to Address 0 at Reset AREA FIRMWARE, DATA, READONLY EXPORT __Vectors EXPORT __Vectors_End EXPORT __Vectors_Size __Vectors DCD FirmwareInit DCD LED_ON DCD LED_OFF DCD uart_put_char DCD delay_ms DCD GetCount __Vectors_End __Vectors_Size EQU __Vectors_End - __Vectors AREA |.text|, CODE, READONLY EXPORT FirmwareInit [WEAK] EXPORT LED_ON [WEAK] EXPORT LED_OFF [WEAK] EXPORT uart_put_char [WEAK] EXPORT delay_ms [WEAK] EXPORT GetCount [WEAK] FirmwareInit LED_ON LED_OFF uart_put_char delay_ms GetCount B . END 因为分配了Flash和Ram地址,且上述汇编文件中定义了新的段:FIRMWARE段,Driver工程的分散加载文件也需要修改,以下是修改后的分散加载文件。对分散加载不够了解的读者,可以看我的另一篇文章:STM32链接脚本详解。 LR_IROM1 0x08030000 0x00010000 { ; 加载时域 ER_IROM1 0x08030000 0x00010000 { ; 第一段运行时域 *.o (FIRMWARE, +First) ; FIRMWARE段最先编译 ;*(InRoot$$Sections) ; 因为FirmWare没有__main,所以不需要这个段 .ANY (+RO) } RW_IRAM1 0x20008000 0x00004000 { ; 第二段运行时域 .ANY (+RW +ZI) } } 由于没有__main函数帮忙重定位RW数据段和ZI数据段,所以Driver层还需要手动将Flash中的RW段复制到Ram对应的运行地址,并将Ram中对应的ZI段清零,代码如下。 static void RW_And_ZI_Init (void) { extern unsigned char Image$$ER_IROM1$$Limit; // 获取RW段在FLASH中的加载地址 extern unsigned char Image$$RW_IRAM1$$Base; // 获取RW段在RAM中的运行地址 extern unsigned char Image$$RW_IRAM1$$RW$$Limit; // 获取RW段在RAM中的结束地址 extern unsigned char Image$$RW_IRAM1$$ZI$$Limit; // 获取ZI段在RAM中的结束地址 unsigned char * psrc, *pdst, *plimt; psrc = (unsigned char *)&Image$$ER_IROM1$$Limit; pdst = (unsigned char *)&Image$$RW_IRAM1$$Base; plimt = (unsigned char *)&Image$$RW_IRAM1$$RW$$Limit; while(pdst < plimt) // 将FLASH中的RW段拷贝到RAM的RW段运行地址上 { *pdst++ = *psrc++; } psrc = (unsigned char *)&Image$$RW_IRAM1$$RW$$Limit; plimt = (unsigned char *)&Image$$RW_IRAM1$$ZI$$Limit; while(psrc < plimt) // 将RAM中的ZI段清零 { *psrc++ = 0; } } 然后将RW_And_ZI_Init函数再做一层封装。以下程序同时初始化了一些外设,并提供了一个测试函数供App获取Firmware中的全局变量。 /* 初始化Firmware */ void FirmwareInit(void) { SystemInit(); // 初始化系统时钟、中断向量 RW_And_ZI_Init(); // 初始化Firmware的RW段、ZI段 delay_init(); // systick初始化 uart_init(115200); // 串口1初始化 LED_Init(); // LED初始化 } /* 返回Firmware中的一个全局变量并加1 */ unsigned int GetCount(void) { return cnt++; } 3.3 App工程代码 #define FIRMWARE_ADDR 0x08030000 /* Firmware的起始地址 */ typedef struct FUN /* 声明Firmware提供的函数类型 */ { /* 注意函数的顺序要和Firmware保持一致 */ void (*FirmwareInit)(void); void (*LED_ON)(void); void (*LED_OFF)(void); int (*uart_put_char)(int ch); void (*delay_ms)(u16 nms); u32 (*GetCount)(void); }FUNC_S; /* 定义一个全局结构体指针,强制指向Firmware的起始地址 */ FUNC_S *gFunc = (FUNC_S*)(FIRMWARE_ADDR); int main(void) { gFunc->FirmwareInit(); /* 初始化Firmware */ while(1) { printf("Cnt = %drn",gFunc->GetCount()); gFunc->LED_ON(); gFunc->delay_ms(500); gFunc->LED_OFF(); gFunc->delay_ms(500); } } 为了方便说明,上述程序裁剪掉了printf的实现,并将结构体的声明直接放在了c文件里。整个工程仅需一个main.c和ST提供的启动文件即可,连标准库或HAL库都不用添加,因为底层的初始化已经在Firmware中完成了。App的分散加载文件也需要根据Flash和Ram的划分做简单的修改,这里就不再赘述了。 四、总结 上述例子只是一个demo,实际要考虑的问题更多,例如哪些代码存放在Firmware中,哪些代码应该存放App中。上述例子直接将标准库也放到了Firmware中,实际上会有一些问题,因为没有考虑到中断向量的偏移,但可以事先在Firmware中写好对应的中断处理函数,然后在App的中断服务函数中被调用。关于中断的设想暂时没有去实际验证,但这样有一个好处:如果有Boot工程,那么Boot也可以调用Firmware的API,例如避免了Boot和App都存一份标准库,大大节省空间。 这种开发方式将单片机开发拉向了Linux,分成了驱动开发和应用开发。在软件上做好分层的规划、使用低耦合的程序框架,才能写出优秀的代码。 |
|
|
|
只有小组成员才能发言,加入小组>>
调试STM32H750的FMC总线读写PSRAM遇到的问题求解?
1785 浏览 1 评论
X-NUCLEO-IHM08M1板文档中输出电流为15Arms,15Arms是怎么得出来的呢?
1621 浏览 1 评论
1088 浏览 2 评论
STM32F030F4 HSI时钟温度测试过不去是怎么回事?
729 浏览 2 评论
ST25R3916能否对ISO15693的标签芯片进行分区域写密码?
1680 浏览 2 评论
1939浏览 9评论
STM32仿真器是选择ST-LINK还是选择J-LINK?各有什么优势啊?
735浏览 4评论
STM32F0_TIM2输出pwm2后OLED变暗或者系统重启是怎么回事?
571浏览 3评论
597浏览 3评论
stm32cubemx生成mdk-arm v4项目文件无法打开是什么原因导致的?
560浏览 3评论
小黑屋| 手机版| Archiver| 电子发烧友 ( 湘ICP备2023018690号 )
GMT+8, 2024-12-25 02:41 , Processed in 0.861078 second(s), Total 77, Slave 61 queries .
Powered by 电子发烧友网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191 工商网监 湘ICP备2023018690号