STM32的内存扩展应用实现,小内存的单片机也能干大事(FSMC+SRAM)

举报
Winter_world 发表于 2022/03/16 10:35:28 2022/03/16
【摘要】          最近做的项目有这样一个需求:从FLASH读取数据后进行显示、发送、本地SD卡存储,显示部分是显示在串口屏上。这个需求乍一看其实还不难实现,但是如果要从FLASH中读取的数据量很大,远超过MCU的内部RAM容量怎么办?其实,可以分多次读取,但是一样的道理,就需要分多次发送数据给串口屏,这样多次读取+多次发送会造成总体时间的增大;另外一个解决办法就是扩展RAM,一次性读取大量数据到外

【系列专栏】:博主结合工作实践输出的,解决实际问题的专栏,朋友们看过来!

QT开发实战

嵌入式通用开发实战

《从0到1学习嵌入式Linux开发》

《Android开发实战》

《实用硬件方案设计》

 长期持续带来更多案例与技术文章分享;

欢迎商业项目咨询,10年+软硬全栈内功,助力解决您的尊贵需求。

——————————————————————————————————

目录

0 引言

1 FSMC有什么用?

2 FSMC总体框图

3 FSMC外部设备地址映像

4 FSMC相关寄存器及配置参数

4.1 FSMC_BCRx 片选控制寄存器

4.2 FSMC_BTRx 片选时序寄存器

4.3 FSMC_BWTRx 片选写时序寄存器

5 FSMC扩展外部SRAM的硬件实现

6 FSMC扩展外部SRAM的软件实现

7 总结


0 引言

         最近做的项目有这样一个需求:从FLASH读取数据后进行显示、发送、本地SD卡存储,显示部分是显示在串口屏上。这个需求乍一看其实还不难实现,但是如果要从FLASH中读取的数据量很大,远超过MCU的内部RAM容量怎么办?其实,可以分多次读取,但是一样的道理,就需要分多次发送数据给串口屏,这样多次读取+多次发送会造成总体时间的增大;另外一个解决办法就是扩展RAM,一次性读取大量数据到外部RAM,再发送给串口屏,这样能很大程度减小整体的耗时。

        因为有上面这个需求,才有了本篇博文,此处涉及的技术点包括:FSMC接口、内存管理两大块,这两块在网络上已经有大量的资料了,本篇博文本着记录总结的目的,综合讲述下FSMC的原理、相关寄存器、参数设置方法、内存管理方法。

1 FSMC有什么用?

        玩过单片机的都知道,控制外部存储器涉及到地址线、数据线、控制线,再按照时序读写就行了,这个FSMC其实基本原理就是上面说的,FSMC,即灵活的静态存储控制器,能够与同步或异步存储器和 16 位 PC 存储器卡连接,STM32 的 FSMC 接口支持包括 SRAM、 NAND FLASH、 NOR FLASH 和 PSRAM 等存储器。

        为了有一个更清晰的认识,博主在网上搜索了不少资料,有一位老手在论坛中讲的是真不错,一看就是从51时代过来的,下面文字略微修改,请欣赏:

看起来你没有玩过51,我给你讲讲历史吧。
最早CPU要访问外部RAM需要三条总线:地址总线、数据总线(以16条地址线8位存储器为例)和控制总线。
地址总线:A0到A15共计16根地址线;
数据总线:D0到D7共计8根数据线;
控制总线:至少包括读写控制等控制线;
CPU要访问外部RAM,就得靠这些线来进行控制:

1)首先,在这16根地址线上呈现地址值,指示要访问的目标地址,使得外部RAM可以定位到存储单元;
2)接着,要让控制总线上呈现是读还是要写,好让外部RAM做好准备;
3)最后,如果是读,则外部RAM就把指定地址存储器的值8位呈现在D0到D7上,由CPU取走。如果是写,则CPU自己把8位值输出呈现在D0到D7上,由外部RAM接收后改写存储器的值。
        这整个过程都是由硬件来实现的,完全没有任何一句用户代码来参与的,是CPU设计之初就定义实现好的,这个过程的时间关系就叫做时序。一定要记住一点,总线是硬件实现的,有严格的规定好的时序。
        可以看到,这种对引脚要求是比较多的,16条地址线加8条数据线再加控制线必须有25根线以上,所以8051为了省线,将8条数据线和地址线的低8位进行了时分复用(称为AD0到AD7),这样就可以省掉8根线,但代价是必须由外部增加锁存器来锁存地址的低8位(现在的外部RAM可以理解为将锁存器做到了芯片中)。但过程仍是上述描述的内容。即使这样,仍需要引脚近20根,如果存储量大,地址线更多,这些线就是STM32的FSMC,FSMC即灵活的静态存储器控制器,就是用来驱动外部总线,做上面所描述的工作的。

        为什么说是灵活的,我想主要是因为它可以通过事先对一些时间等参数进行设置调整,可以适应不同厂家参数有差异的SRAM或者像LCD、OLED等类似外设。但这些参数设定一次之后,整个控制时序关系就固定了,总线在具体工作的时候就不再需要用户来操心了,这就是硬件实现的优点,速度快且不占用CPU的计算资源。
        至于IO,我想就不用解释了,就是CPU的输入输出端口,可以由CPU控制读写的一个个外部引脚,既然可以控制,就有人仿造总线的时序,用多个IO来通过软件控制的方式来模拟外部总线,比如8051没有SPI接口,就可以用至少三个IO口来分别模拟SCLK时钟,MOSI和MISO数据线。事实上,你也可以用二十几个IO来模拟上面所说的三条总线,但每一次的读写你都得按照时间顺序来控制这二十几个IO端口,你可以把它编好后写成函数,但仍然是占用CPU大量资源的,这就是软件实现的弊端,速度慢且占用CPU的计算资源。
        STM32向外提供了灵活的总线访问接口即FSMC,无须你用IO来模拟,就如同8051的地址数据总线一样以硬件的方式来自动工作。不仅如此,如果你的系统用不到FSMC接口,STM32还可以把预备FSMC使用的端口让出来,使它可以当成普通IO一样来使用,从而节省宝贵的外部引脚空间。

2 FSMC总体框图

        由如下FSMC框图可知,STM32 的 FSMC 将外部设备分为 3 类: NOR/PSRAM 设备、 NAND设备、 PC 卡设备。他们共用地址数据总线等信号,他们具有不同的 CS 以区分不同的设备。用一个FSMC接口就可以匹配不同的外部存储设备,自已看出这个接口的灵活了。
        若要连接外部 SRAM,外部 SRAM 的控制一般有:地址线(如 A0~A18)、数据线(如D0~D15,FSMC支持8/16/32位数据宽度)、写信号(WE)、读信号(OE)、片选信号(CS),如果 SRAM 支持字节控制,那么还有 UB/LB 信号,硬件连线没有多么复杂。

3 FSMC外部设备地址映像

        如下图所示,STM32 的FSMC 将外部存储器划分为固定大小为 256M 字节的四个存储块Bank,总共管理1GB的空间。

         此处及其后,以Bank1为例进一步说明,存储块Bank1分为4个区,每个区管理 64M 字节空间,每个区都有独立的寄存器对所连接的存储器进行配置。 Bank1 的 256M 字节空间由 28 根地址线(HADDR[27:0])寻址。这 里 HADDR 是 内 部 AHB 地址 总线 ,其 中 HADDR[25:0] 来自 外部存 储 器地 址FSMC_A[25:0],而 HADDR[26:27]对 4 个区进行寻址

4 FSMC相关寄存器及配置参数

STM32 的 FSMC 各 Bank 配置寄存器如下:

        红框标记的是对于NOR FLASH或SRAM配置涉及的寄存器,通过这 3 个寄存器, 可以设置FSMC 访问外部存储器的时序参数,拓宽了可选用的外部存储器的速度范围

        FSMC 的 NOR FLASH 控制器支持同步和异步突发两种访问方式。

【同步突发访问方式】 :FSMC 将 HCLK(系统时钟)分频后,发送给外部存储器作为同步时钟信号 FSMC_CLK。此时需要的设置的时间参数有 2 个
1) HCLK 与 FSMC_CLK 的分频系数(CLKDIV),可以为 2~16 分频;
2)同步突发访问中获得第 1 个数据所需要的等待延迟(DATLAT)。

【异步突发访问方式】: FSMC 主要设置 3 个时间参数:地址建立时间(ADDSET)、数据建
立时间(DATAST)和地址保持时间(ADDHLD)。 FSMC 综合了 SRAM/ROM、PSRAM 和 NOR
Flash 产品的信号特点,定义了 4 种不同的异步时序模型。选用不同的时序模型时,需要设置不
同的时序参数:

        在实际扩展时,根据选用存储器的特征确定时序模型,从而确定各时间参数与存储器读/写周期参数指标之间的计算关系;利用该计算关系和存储芯片数据手册中给定的参数指标,可计算出 FSMC 所需要的各时间参数,从而对时间参数寄存器进行合理的配置。

4.1 FSMC_BCRx 片选控制寄存器

FSMC_BCRx(x=1~4),该寄存器包含每个存储块的使能配置信息,各位描述:

  • EXTMOD:扩展模式使能位,也就是是否允许读写不同的时序。
  • WREN:写使能位。
  • MWID[1:0]:存储器数据总线宽度。 00,表示 8 位数据模式; 01 表示 16 位数据模式; 10 和11 保留。
  • MTYP[1:0]:存储器类型。 00 表示 SRAM、 ROM; 01 表示 PSRAM; 10 表示 NOR FLASH;11保留。
  • MBKEN:存储块使能位。

4.2 FSMC_BTRx 片选时序寄存器

        FSMC_BTRx(x=1~4),该寄存器包含每个存储块的时序控制信息,可用于 SRAM、ROM 和 NOR 闪存存储器,各位描述:

  • 有两个时序寄存器:如果 FSMC_BCRx 寄存器中设置了 EXTMOD 位,则有两个时序寄存器分别对应读(本寄存器)和写操作(FSMC_BWTRx 寄存器)。
  • ACCMOD[1:0]:访问模式。 00 表示访问模式 A; 01 表示访问模式 B; 10 表示访问模式 C;11 表示访问模式 D。
  • DATAST[7:0]:数据保持时间。0为保留设置,其他设置则代表保持时间为: DATAST个HCLK时钟周期,最大为 255 个 HCLK 周期。
  • ADDSET[3:0]:地址建立时间。其建立时间为: ADDSET 个 HCLK 周期,最大为 15 个 HCLK周期。

4.3 FSMC_BWTRx 片选写时序寄存器

        FSMC_BWTRx(x=1~4),该寄存器各位描述:

        该寄存器在本章用作写操作时序控制寄存器,需要用到的设置同样是: ACCMOD、DATAST 和 ADDSET 这三个设置。这三个设置的方法同 FSMC_BTRx 一模一样,只是这里对应的是写操作的时序。

        需要注意的是,在 MDK 的寄存器定义里面,并没有定义 FSMC_BCRx、 FSMC_BTRx、 FSMC_BWTRx 等这个单独的寄存器,而是将他们进行了一些组合。

5 FSMC扩展外部SRAM的硬件实现

【硬件资源】:

  • MCU:STM32F103ZET6
  • SRAM:IS62WV51216,16位宽512K(512*16,1M字节)的CMOS静态内存芯片,45ns/55ns访问速度,低功耗,TTL电平兼容,全静态操作,三态输出,支持高低字节控制。功能框图如下:

  【MCU与SRAM硬件连接】

        这里本没什么好讲的,唯一值的说的就是MCU的FSMC_A0-A18和SRAM的A0-A18没有对应,是乱序的,但是并不影响使用,这个原因是什么?

        原因就是SRAM地址具有唯一性。假设原来FSMC_A0-A18和A0-A18是一一对应的,这时,你把FSMC_A0和A1对调下,当MCU控制写地址1的时候,实际上写的是0x00000002,反过来读地址1的时候,实际上也是读的这个0x00000002地址,读写都是一个地址,那数据自然就不会错啊,就是这么个道理。起初我也没想明白,后来才懂了,而且这样乱序有另外一个好处,布线的时候更方便了。

6 FSMC扩展外部SRAM的软件实现

软件主要涉及的就是FSMC的配置工作,涉及到几个结构体(这里都是以NOR和SRAM为例):

【FSMC_NORSRAMInitTypeDef】:前 13 个基本类型(unit32_t)的成员变量用来配置片选控制寄存器 FSMC_BCRx,后面两个SMC_NORSRAMTimingInitTypeDef 指针类型的成员变量分别用来配置寄存器 FSMC_BTRx 和 FSMC_BWTRx,设置读写时序参数。

typedef struct
{
  uint32_t FSMC_Bank;               //设置使用到的存储块标号和区号
  uint32_t FSMC_DataAddressMux;     //设置地址/数据复用使能,若设置为使能,那么地址的低 16 位
和数据将共用数据总线,仅对 NOR 和 PSRAM 有效
  uint32_t FSMC_MemoryType;          //设置存储器类型
  uint32_t FSMC_MemoryDataWidth;     //设置数据宽度
  uint32_t FSMC_BurstAccessMode;     //成组模式同步模式才需要设置
  uint32_t FSMC_AsynchronousWait;    //成组模式同步模式才需要设置
  uint32_t FSMC_WaitSignalPolarity;  //成组模式同步模式才需要设置
  uint32_t FSMC_WrapMode;            //成组模式同步模式才需要设置
  uint32_t FSMC_WaitSignalActive;    //成组模式同步模式才需要设置
  uint32_t FSMC_WriteOperation;      //设置写使能
  uint32_t FSMC_WaitSignal;          //成组模式同步模式才需要设置
  uint32_t FSMC_ExtendedMode;        //设置扩展模式使能位,也就是是否允许读写不同的时序
  uint32_t FSMC_WriteBurst;          //成组模式同步模式才需要设置

  FSMC_NORSRAMTimingInitTypeDef* FSMC_ReadWriteTimingStruct;//初始化片选控制寄存器FSMC_BTRx 
  FSMC_NORSRAMTimingInitTypeDef* FSMC_WriteTimingStruct;   //初始化写操作时序控制寄存器FSMC_BWTRx
}FSMC_NORSRAMInitTypeDef;

【FSMC_NORSRAMTimingInitTypeDef】:

typedef struct
{
  uint32_t FSMC_AddressSetupTime;      //地址建立保持时间
  uint32_t FSMC_AddressHoldTime;       //地址保持时间
  uint32_t FSMC_DataSetupTime;         //数据建立时间

  uint32_t FSMC_BusTurnAroundDuration;  //总线周转期
  uint32_t FSMC_CLKDivision;           //分频系数
  uint32_t FSMC_DataLatency;           //

  uint32_t FSMC_AccessMode;            //模式
}FSMC_NORSRAMTimingInitTypeDef;

        C程序这里仅给出sram.c程序,在STM32F103平台使用的话加入.h 函数声明和FSMC 固件库文件 stm32f10x_fsmc.c、stm32f10x_fsmc.h 文件即可。

【sram.c】:

//使用NOR/SRAM的 BANK 4,地址位HADDR[27,26]=10 
//对IS61LV25616/IS62WV25616,地址线范围为A0~A17 
//对IS61LV51216/IS62WV51216,地址线范围为A0~A18
#define Bank1_SRAM3_ADDR    ((u32)(0x60000000 | 0x08000000))	
  						   
//初始化外部SRAM
void FSMC_SRAM_Init(void)
{	
	FSMC_NORSRAMInitTypeDef  FSMC_NORSRAMInitStructure;
	FSMC_NORSRAMTimingInitTypeDef  readWriteTiming;
	GPIO_InitTypeDef  GPIO_InitStructure;
 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD|RCC_APB2Periph_GPIOE|RCC_APB2Periph_GPIOF|RCC_APB2Periph_GPIOG|RCC_APB2Periph_AFIO,ENABLE);
  	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_FSMC,ENABLE);
  
	GPIO_InitStructure.GPIO_Pin = 0xFF33; 			 	//PORTD复用推挽输出 
 	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; 		 //推挽输出
 	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
 	GPIO_Init(GPIOD, &GPIO_InitStructure);

 
	GPIO_InitStructure.GPIO_Pin = 0xFF83; 			 	//PORTE复用推挽输出 
 	GPIO_Init(GPIOE, &GPIO_InitStructure);

 	GPIO_InitStructure.GPIO_Pin = 0xF03F; 			 	//PORTD复用推挽输出 
 	GPIO_Init(GPIOF, &GPIO_InitStructure);

	GPIO_InitStructure.GPIO_Pin = 0x043F; 			 	//PORTD复用推挽输出 
 	GPIO_Init(GPIOG, &GPIO_InitStructure);
 
	 			  	  
 	readWriteTiming.FSMC_AddressSetupTime = 0x00;	 //地址建立时间(ADDSET)为1个HCLK 1/36M=27ns
    readWriteTiming.FSMC_AddressHoldTime = 0x00;	 //地址保持时间(ADDHLD)模式A未用到	
    readWriteTiming.FSMC_DataSetupTime = 0x03;		 //数据保持时间(DATAST)为3个HCLK 4/72M=55ns(对EM的SRAM芯片)	 
    readWriteTiming.FSMC_BusTurnAroundDuration = 0x00;
    readWriteTiming.FSMC_CLKDivision = 0x00;
    readWriteTiming.FSMC_DataLatency = 0x00;
    readWriteTiming.FSMC_AccessMode = FSMC_AccessMode_A;	 //模式A 

    FSMC_NORSRAMInitStructure.FSMC_Bank = FSMC_Bank1_NORSRAM3;//  这里我们使用NE3 ,也就对应BTCR[4],[5]。
    FSMC_NORSRAMInitStructure.FSMC_DataAddressMux = FSMC_DataAddressMux_Disable; 
    FSMC_NORSRAMInitStructure.FSMC_MemoryType =FSMC_MemoryType_SRAM;//SRAM   
    FSMC_NORSRAMInitStructure.FSMC_MemoryDataWidth = FSMC_MemoryDataWidth_16b;//存储器数据宽度为16bit  
    FSMC_NORSRAMInitStructure.FSMC_BurstAccessMode =FSMC_BurstAccessMode_Disable;// FSMC_BurstAccessMode_Disable;
    FSMC_NORSRAMInitStructure.FSMC_WaitSignalPolarity = FSMC_WaitSignalPolarity_Low; 
	FSMC_NORSRAMInitStructure.FSMC_AsynchronousWait=FSMC_AsynchronousWait_Disable;
    FSMC_NORSRAMInitStructure.FSMC_WrapMode = FSMC_WrapMode_Disable;  
    FSMC_NORSRAMInitStructure.FSMC_WaitSignalActive = FSMC_WaitSignalActive_BeforeWaitState; 
    FSMC_NORSRAMInitStructure.FSMC_WriteOperation = FSMC_WriteOperation_Enable;	//存储器写使能 
    FSMC_NORSRAMInitStructure.FSMC_WaitSignal = FSMC_WaitSignal_Disable;  
    FSMC_NORSRAMInitStructure.FSMC_ExtendedMode = FSMC_ExtendedMode_Disable; // 读写使用相同的时序
    FSMC_NORSRAMInitStructure.FSMC_WriteBurst = FSMC_WriteBurst_Disable; 
    FSMC_NORSRAMInitStructure.FSMC_ReadWriteTimingStruct = &readWriteTiming; // 读写使用相同的时序
    FSMC_NORSRAMInitStructure.FSMC_WriteTimingStruct = &readWriteTiming;  // 读写使用相同的时序

    FSMC_NORSRAMInit(&FSMC_NORSRAMInitStructure);  //初始化FSMC配置

   	FSMC_NORSRAMCmd(FSMC_Bank1_NORSRAM3, ENABLE);  // 使能BANK3										  
											
}
	  														  
//在指定地址开始,连续写入n个字节.
//pBuffer:字节指针
//WriteAddr:要写入的地址
//n:要写入的字节数
void FSMC_SRAM_WriteBuffer(u8* pBuffer,u32 WriteAddr,u32 n)
{
	for(;n!=0;n--)  
	{										    
		*(vu8*)(Bank1_SRAM3_ADDR+WriteAddr)=*pBuffer;	  
		WriteAddr+=2;//这里需要加2,是因为STM32的FSMC地址右移一位对其.加2相当于加1.
		pBuffer++;
	}   
}																			    
//在指定地址开始,连续读出n个字节.
//pBuffer:字节指针
//ReadAddr:要读出的起始地址
//n:要写入的字节数
void FSMC_SRAM_ReadBuffer(u8* pBuffer,u32 ReadAddr,u32 n)
{
	for(;n!=0;n--)  
	{											    
		*pBuffer++=*(vu8*)(Bank1_SRAM3_ADDR+ReadAddr);    
		ReadAddr+=2;//这里需要加2,是因为STM32的FSMC地址右移一位对其.加2相当于加1.
	}  
} 

7 总结

        STM32的FSMC接口对于有扩展内存需求的应用来说很便利,根据器件读写时序参数进行设置即可,本博文详细讲解了FSMC的原理、寄存器设置,相关的设置结构体参数,并给出了C程序源码。扩展内存,使用时最好结合内存管理,内存管理的原理也很简单,此处不再赘述了。若使用RTOS,像freeRTOS和uC/OS,有好几种内存管理方法,常用的就是heap4,需求的查阅下相关资料,此处收尾了,祝生活愉快。


【参考资料】:

IO口和FSMC的详细区别


作于202109092010,已归档

———————————————————————————————————

本文为博主原创文章,转载请注明出处!

若本文对您有帮助,轻抬您发财的小手,关注/评论/点赞/收藏,就是对我最大的支持!

祝君升职加薪,鹏程万里!

【版权声明】本文为华为云社区用户原创内容,未经允许不得转载,如需转载请自行联系原作者进行授权。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。