打印
[其他]

在MM32F5微控制器上使用外扩SRAM作为主内存(转)

[复制链接]
2145|32
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
在MM32F5微控制器上使用外扩SRAM作为主内存苏勇,2022年8月
引言
MM32F5微控制器基于Arm STAR-MC1微控制器,最高主频可达120MHz,集成了FPU单元和DSP扩展指令集,有不错的算力。但片内集成的128KB的RAM和256KB的FLASH,如果想支持代码量比较大的软件框架,就可能会力不从心,例如,TensorFlow Lite或者基于MicroPython的OpenMV这样的应用就需要更多的内存空间做缓存。但MM32F5微控制器带有FSMC接口和QSPI接口并支持基于QSPI的XIP(eXecute In Place,就地执行),可以分别外扩SRAM和FLASH存储器,这就为扩展存储资源提供了可能。在本文中,将介绍使用FSMC接口外接SRAM扩展内存的过程。在后续的文章中,在后续文章中,还会继续介绍使用QSPI对接qspiflash存储器实现外扩FLASH的过程。

使用特权

评论回复
评论
裤脚口感好 2022-9-27 23:43 回复TA
———————————————— 版权声明:本文为CSDN博主「suyong_yq」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/suyong_yq/article/details/126348057 
沙发
裤脚口感好|  楼主 | 2022-9-27 23:47 | 只看该作者
硬件电路

MM32F5微控制器上集成了FSMC(Flexable Static Memory Controller)接口,可以外接并口的SRAM存储器。

使用特权

评论回复
板凳
裤脚口感好|  楼主 | 2022-9-27 23:48 | 只看该作者
在PLUS-F5270开发板上,对应外扩了一个1MB大小的PSRAM存储器作为扩展内存,如图x所示。

使用特权

评论回复
地板
裤脚口感好|  楼主 | 2022-9-27 23:49 | 只看该作者
软件设计
使用FSMC接口外扩的SRAM存储设备之前,必须先激活微控制器的FSMC接口,包括启用对FSMC接口外设的访问开关、配置FSMC接口对应的外部引脚,以及配置FSMC的时钟源和工作模式等操作。基于这样的使用前提,一般情况下使用外扩SRAM,都是在应用程序中激活FSMC硬件外设接口,之后通过在指定地址分配内存,或者访问绝对地址的方式访问新扩展出来的内存,但此时默认的主内存还是片内的SRAM。这种使用扩展SRAM的方式对于规模较小或者绑定具体应用的项目,因为涉及到对代码的改动以及对存储管理的工作量较小并且明确,在一定程度上是可以接受的。但对于移植已有现有的项目,或者是规模较大的框架性软件,开发者通常不愿意(也不建议)深入到代码库中去人为指定每个可能的全局变量的绝对地址,仅将管理的目标地址区间从片内SRAM转移到了外扩的SRAM而已,而希望能够一如既往地让编译器自动管理内存的分配机制。

使用特权

评论回复
5
裤脚口感好|  楼主 | 2022-9-27 23:53 | 只看该作者
编译器自动管理内存,就涉及到在芯片上电初始化过程中对编译器运行时环境的初始化过程中对堆栈进行初始化,配置栈顶和栈底、堆底和堆顶指针等,也包括将内存中BSS段的数据清零,将DATA段数据的初值从FLASH搬运到SRAM中等。这些操作的过程,大多被封装在集成开发环境自带的库中(例如Keil的__main函数,经过一系列同编译器相关的准备工作后才跳转到用户的main()函数),不开放给用户修改,而其中使用的和计算出的内存地址,也都是在编译过程中预先定义的。

使用特权

评论回复
6
裤脚口感好|  楼主 | 2022-9-27 23:53 | 只看该作者
如果用户强行在链接命令文件中指定默认的主内存空间为外扩存储,那么在芯片启动过程中,预定义的初始化运行时环境的操作,将会在未初始化好FSMC接口等硬件的时候直接访问FSMC扩展出的内存空间,必然出错。可能会提示的错误是hardfault,标记为访问了无效的地址。此时,若是用户在集成开发环境的__main函数之前的SystemInit()函数中先激活FSMC等外扩SRAM相关的硬件也是可行的,但必须要注意,这个过程中,除了CPU中仅有的寄存器外,不能使用任何栈内存,因为此时烧写在默认的中断向量表首位的栈顶地址所指向的空间还是不可访问的。具体来说,就需要用汇编命令完成对所有相关硬件外设的初始化操作,这确实是一个考验人耐心的事情。这里简单看一下SDK中的复位中断服务程序中的启动程序代码,见代码x。

使用特权

评论回复
7
裤脚口感好|  楼主 | 2022-9-27 23:54 | 只看该作者
代码x SDK中的启动程序代码

使用特权

评论回复
8
裤脚口感好|  楼主 | 2022-9-27 23:55 | 只看该作者
一些技术高超的工程师可能会想到一些巧妙的做法,能不能先用缺省的片内SRAM支持编译工具链的初始化过程,然后在应用程序中初始化FSMC外设(此时仍使用片内SRAM),然后再试图重建内存管理系统,将芯片系统中内存相关的指针人为重建在外扩SRAM中呢?且不说这是一个极其麻烦的过程,需要把编译工具链中的每个同内存相关的配置变量都翻出来重新人为计算并赋值一遍,一个明显的限制在于,所有将要放在外扩的大SRAM中的数据必须在较小的片内SRAM中必须预先存放一份副本,之后在应用程序运行的过程中转移到外扩SRAM。此时,在编译阶段,编译器会限定整个程序能使用的存储空间不能大于片内SRAM的大小,否则编译器仍然会报错并拒绝生成可执行文件。这就限制了能够直接使用外扩SRAM的空间,同最常用的将外扩SRAM当成辅助存储空间的做法没有实质区别。

使用特权

评论回复
9
裤脚口感好|  楼主 | 2022-9-27 23:55 | 只看该作者
使用bootloader初始化硬件环境的思路
为了让用户的工程直接在一个可用的外扩SRAM上建立存储管理系统,一个可行的设计,是使用额外使用一个bootloader工程(或者在芯片内部的电路实现上直接用一段ROM承载bootloader工程中的操作),在使用少量片内SRAM的条件下,通过常规的调用驱动API的方式(而不用汇编语句序列),先准备好使用FSMC的硬件环境,例如配置时钟系统、引脚复用功能、FSMC接口外设等等。在bootloader工程最后的部分,直接跳转到一个约定的、存放用户application工程的地址,开始自行application工程。在application是一个完全独立的工程,不用激活FSMC就可以直接使用外扩的SRAM,因此可以利用编译工具链直接在外扩的SRAM上重建存储管理系统。在application工程中,用户将完全不用干预内存的分配情况,就像之前一样完全交由编译器自行管理;由于不再使用bootloader工程,片内的SRAM可以作为独立的一块可用的存储空间(就像之前在片内SRAM看外扩SRAM一样),继续为应用程序提供存储服务。

使用特权

评论回复
10
裤脚口感好|  楼主 | 2022-9-27 23:56 | 只看该作者
进一步分析,探讨把bootloader工程放到ROM中的可能性。程序一旦写入ROM中,就不能有任何改变了。但在配置外扩SRAM的时候,仍需要人为指定外扩SRAM映射的地址范围(开始地址和空间大小),这个设定在不同应用场景中可能会不一样,受成本和功能的权衡,可能有时候会用或大或小的存储器设备,因具体选型不同配置参数也会随之发生变化,因此不适合直接固化在ROM中。除非是对应合封的芯片,需要固定规格的SRAM芯片的晶元已经同微控制器一起被封在芯片内部,倒是不失为一种高集成的SOC解决方案。或者也可以用类似回调的方式,由用户在某种基础的协议下向ROM中的小程序提供外扩SRAM芯片专属的配置,例如在手册里说明在特定的用户可编程的FLASH存储区中存放了关于SRAM的配置信息,也是可行的,但处理过程就多了几个步骤。没有扩展多种SRAM的情况下,实在没必要在芯片里设计这么一块ROM。不过,这种方式在外扩FLASH的时候确实用到了,在后续的文章中将会提及,外扩FLASH的各厂家设计生产的nor FLASH型号芯片在使用上存在差异,在flashless的芯片中必须在ROM中设计程序首先识别外扩spiflash芯片的型号,从而使用对应的配置信息初始化spiflash芯片,到时也将会有一番细致地阐述。

使用特权

评论回复
11
裤脚口感好|  楼主 | 2022-9-27 23:57 | 只看该作者
详细介绍创建bootloader工程和用户在application工程中开发应用的实现过程和应用要点。

使用特权

评论回复
12
裤脚口感好|  楼主 | 2022-9-27 23:57 | 只看该作者
创建bootloader工程
顶级执行流程函数main()
在实现最简单功能的bootloader工程中,缺省使用片内的SRAM作为主内存设备,仅仅需要完成的工作包括:
初始化外扩SRAM的接口FSMC在NVIC_VTOR寄存器中重定位中断向量表的基地址。后续application工程中的中断向量表将位于自己可执行二进制文件的最开始。application工程执行过程中,将通过NVIC_VTOR寄存器和中断向量表项的偏移值确定实际的中断服务程序入口地址。为application工程的栈指针寄存器SP(MSP/PSP)赋初值。这个初值即为application工程中的中断向量表的第一个表项中存放的数值,这个值是由application工程的链接器算出来的。
最后跳转到application可执行程序的位置,后续执行application工程。

使用特权

评论回复
13
裤脚口感好|  楼主 | 2022-9-27 23:58 | 只看该作者
为了确定从芯片上电到执行application程序这段时间,bootloader确实按照预期正常工作,在本例实现的bootloader工程中使用了一个GPIO控制的小灯指示执行过程中可能出现的错误:
初始化配置指示灯亮如果顺利执行到跳转到application的前一步,那么就可以熄灭指示灯,顺利进入跳转过程如果在bootloader执行的过程中遇到任何问题,例如可以增加一个验证外扩SRAM可以工作的检测过程,就在原地等待,此时指示灯将保持常亮

使用特权

评论回复
14
裤脚口感好|  楼主 | 2022-9-27 23:59 | 只看该作者
本例创建的bootloader工程中的main()函数,见代码x。、
代码x bootloader工程中的main() 函数

使用特权

评论回复
15
裤脚口感好|  楼主 | 2022-9-27 23:59 | 只看该作者
用户实际开发application工程时,是不应该感受到这个附加的bootloader工程的,因此,bootloader的执行时间应尽量短,执行完毕后应尽量复原至芯片上电复位的状态。在本例中,为了尽量加速bootloader工程的运行,在BOARD_Init()函数中初始化启用的PLL,从芯片内部的8MHz时钟源倍频到120MHz,用最快速度执行完bootloader的语句后,在临跳转到application工程之前,又将系统时钟复原成原来上电缺省使用的8MHz内部时钟,尽量还原到芯片刚上电后进入用户程序的状态。但同外扩内存相关的外设资源(引脚、时钟等),则必须保持激活状态。

使用特权

评论回复
16
裤脚口感好|  楼主 | 2022-9-28 23:43 | 只看该作者
验证固件函数app_check_image()
关于验证bootloader跳转的硬件环境,本例做得比较简单,仅在app_check_image()函数中检查即将在application中使用的栈顶指针值和复位向量入口(中断向量表的前两个表项)。如果希望做得更严谨一些,可以再把即将使用的外扩SRAM存储空间都遍历一遍,写入数据之后再读出来查看是否一致,以确认即将使用的SRAM也是有效的。本例创建的app_check_image()函数,见代码x。

使用特权

评论回复
17
裤脚口感好|  楼主 | 2022-9-28 23:44 | 只看该作者
代码x 实现app_check_image()函数

uint32_t app_check_image(void * addr)
{
    uint32_t * vectorTable = (uint32_t *)addr;

    /* validate the addr for sp. */
    if ((vectorTable[0] < BOARD_APP_EXEC_RAM_BASE) || (vectorTable[0] > BOARD_APP_EXEC_RAM_LIMIT ))
    {
        return 1u; /* unavailable sram area. */
    }
    /* validate the addr for pc. */
    if ((vectorTable[1] < BOARD_APP_EXEC_ROM_BASE) || (vectorTable[1] > BOARD_APP_EXEC_ROM_LIMIT ))
    {
        return 2u; /* unavailable sram area. */
    }
   
    return 0u;
}

使用特权

评论回复
18
裤脚口感好|  楼主 | 2022-9-28 23:45 | 只看该作者
跳转函数app_jump_to_image()
最关键的跳转函数app_jump_to_image(),实现代码,见代码x。

使用特权

评论回复
19
裤脚口感好|  楼主 | 2022-9-28 23:45 | 只看该作者
代码x 实现app_jump_to_image()函数
typedef void(*func_0_t)(void);
volatile uint32_t sp_base;
volatile uint32_t pc_base;
void app_jump_to_image(void * addr)
{
    uint32_t * vectorTable = (uint32_t *)addr;

    sp_base = vectorTable[0];
    pc_base = vectorTable[1];

    /* set new MSP and PSP.
     * when the SP is changed, the address of variables in stack would be remapped according to the new SP.
    */
    __set_MSP(sp_base);
    __set_PSP(sp_base);

#if __VTOR_PRESENT == 1
    SCB->VTOR = (uint32_t)addr; /* the func's param is kept in R1 register, which would not be changed per the SP update. */
#endif

    /* jump to application. */
    ((func_0_t)(pc_base))();
    //pc_func();

    /* the code should never reach here. */
    while (1)
    {}
}

使用特权

评论回复
20
裤脚口感好|  楼主 | 2022-9-28 23:46 | 只看该作者
在app_jump_to_image()函数中,通过传入的即将跳转到可执行二进制代码区的首地址,提取位于可执行文件程序开始位置的中断向量表的前两个表项,分别为栈顶SP指针的初始值和PC指针的初始(复位中断服务程序入口地址),然后用各自不同的方法将它们赋值到硬件寄存器中生效:MSP和PSP寄存器可以直接使用汇编语句赋值,而PC指针不能由程序直接操作,但通过函数跳转命令实际可以载入新的PC值。

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

43

主题

324

帖子

0

粉丝