【系列专栏】:博主结合工作实践输出的,解决实际问题的专栏,朋友们看过来!
《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,设置读写时序参数。
代码语言:javascript复制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】:
代码语言:javascript复制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】:
代码语言:javascript复制//使用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,已归档
———————————————————————————————————
本文为博主原创文章,转载请注明出处!