当前位置: 首页 > news >正文

【STM32】FSMC——扩展外部SRAM

🐱作者:一只大喵咪1201
🐱专栏:《STM32学习》
🔥格言:你只管努力,剩下的交给时间!
图

外部扩展SRAM

  • 📦描述
  • 📦扩展SRAM芯片介绍
  • 📦片内FSMC介绍
  • 📦FSMC的SRAM结构体说明
  • 📦读写时序的结构体配置
  • 📦硬件连接及引脚配置
  • 📦代码实现
  • 📦效果展示
  • 📦总结

📦描述

  • STM32控制器芯片内部有一定大小的SRAM及FLASH作为内存和程序存储空间,但当程序较大,内存和程序空间不足时,就需要在STM32芯片的外部扩展存储器了。STM32F103ZE系列芯片可以扩展外部SRAM用作内存。
  • 给STM32芯片扩展内存与给PC(电脑)扩展内存的原理是一样的,只是PC上一般以内存条的形式扩展,而且内存条实质是由多个内存颗粒(即SDRAM芯片)组成的通用标准模块,而STM32扩展时,直接直接与SRAM芯片连接。

📦扩展SRAM芯片介绍

这里本喵使用的是型号为IS62WV51216的SRAM芯片。

图

这是它的外观图,可以看到,该芯片有44个引脚。

下表是该SRAM芯片的信号线说明。

信号线类型说明
A0~A18I地址输入
I/O0~I/O7I/O数据输入输出信号,低字节
I/O8~I/O15I/O数据输入输出信号,高字节
CS2 和CS1#I片选信号,CS2高电平有效,CS1#低电平有效,部分芯片只有其中一个引脚
OE#I输出使能信号,低电平有效
WE#I写入使能,低电平有效
UB#I数据掩码信号Upper Byte,高位字节允许访问,低电平有效
LB#I数据掩码信号Lower Byte,低位字节允许访问,低电平有效

注意:

  • 该表是站在SARM芯片的角度描述的
  • 类型中,I表示输入,O表示输出
  • 带#号表示低电平有效

SRAM的控制比较简单,只要控制信号线(片选信号CS1#,输出使能信号OE#,输入使能信号WE#)进行了使能,再从地址线输入要访问的地址,即可从I/O数据线写入或读出数据。

图
这是它的内部功能框图。

  1. 地址译码器、列I/O及I/O数据电路
  • 地址译码器把N根地址线转换成2N根信号线,每根信号线对应一行或一列存储单元,通过地址线找到具体的存储单元,实现寻址。
  • 本喵使用的SRAM比较小,没有列地址线,它的数据宽度为16位,即一个行地址对应2字节空间,框图中左侧的A0-A18是行址信号,19根地址线一共可以表示219=29x1024=512K行存储单元,所以它一共能访问512Kx16bits大小的空间。访问时,使用UB#或LB#线控制数据宽度
  • UB#控制访问的是高字节,也就是D8 ~ D15,而LB#控制访问低字节,也就是D0 ~D17。
  1. 控制电路
  • 控制电路主要包含了片选CS2或CS1#、读写使能OE#和WE#以及上面提到的宽度控制信号UB#和LB#。
  • 利用CS2或CS1#片选信号(CS2高电平有效,CS1低电平有效),可以把多个SRAM芯片组成一个大容量的内存条。
  • OE#和WE#可以控制读写使能,防止误操作。

SRAM的存储矩阵

图
SRAM内部包含的存储阵列,可以把它理解成一张表格,数据就填在这张表格上。和表格查找一样,指定一个行地址,再指定该行的高字节还是低字节,就可以精确地找到目标单元格,这便是本喵使用的SRAM芯片寻址的基本原理。

接下来本喵就带大家来看看访问数据的过程是怎样的。

SRAM的读写流程

读写时序的流程很类似,过程如下:

  • (1) 主机使用地址信号线发出要访问的存储器目标地址;
  • (2) 控制片选信号CS1#或CS2#使能存储器芯片;(这里本喵使用的SRAM芯片只有CS1信号)
  • (3) 若是要进行读操作,则控制读使能信号OE#表示要读数据,若进行写操,则控制写使能信号WE#表示要写数据;
  • (4) 使用掩码信号LB#与UB#指示要访问目标地址的高、低字节部分;
  • (5) 若是读取过程,存储器会通过数据线向主机输出目标数据,若是写入过程,主要使用数据线向存储器传输目标数据。

下面分别看一下读和写的时序图。

  1. 对SRAM进行读数据时的时序流程

图
这是进行读的时候的时序图,注意图中画红色框的三个时间,这三个是我们后面需要配置的重点。

图
这是时序图中发生信号变化的限制时间,这里本喵使用的SRAM芯片是中速访问的,只需要看这个表中红色框中的那一列即可。

过程:

  • 在SRAM收到MCU给的地址信号后,ADDRESS信号发生变化,tRC开始计时,对照时间表得出,tRC是表示的是整个读取数据过程所用的时间,并且必须大于55ns。
  • 在OE#信号由高电平变为低电平的同时开始计时,经过时间tDOE后,数据线上出现了数据,时间tDOE表示输出通道时间,对照表中得出,时间tDOE的最小时间没有限制,但是最大不能超过25ns。
  • 在ADDRESS发生变化后到数据线上出现数据的这段时间tAA称为地址通道时间,对照表中得出,时间tAA最大不能过55ns。

其他时间所代表的意义以及取值范围,大家可以对照时序图以及时间表得出,因为在扩展SRAM时并不涉及到其他的时间,所以本喵就不介绍别的时间了。

  1. 对SRAM进行写数据时的时序流程

图

这是进行写的时候的时序图,注意图中画红色框的三个时间,这三个是我们后面需要配置的重点。

图
这是时序图中发生信号变化的限制时间,这里本喵使用的SRAM芯片是中速访问的,只需要看这个表中红色框中的那一列即可。

过程:

  • 在SRAM收到地址信号后,ADDRESS信号发生变化,并且tWC开始计时,对照时间表得出,tWC表示的是整个写数据过程所用的时间,并且必须大于55ns。
  • 在WE#信号由高电平变化为低电平的同时开始计时,经过时间tPWB后,数据线上出现了数据,时间tPWE表示写信号的时间宽度,对照表中得出的最小时间是45ns。
  • 在ADDRESS信号发生变化后到WE#信号发生变化所经过的时间tSA,对照表中得出,时间tSA表示地址建立时间,它没有时间限制,在ADDRESS信号发生变化后或者同时产生变化就可以。

其他时间所代表的意义以及取值范围,大家可以对照时序图以及时间表得出,因为在扩展SRAM时并不涉及到其他的时间,所以本喵就不介绍别的时间了。

将上面所用到的时间总结为下表:

时间参数要求时间说明
tRC大于55ns读操作周期时间
tAA大于0ns地址通道时间
tDOE不超过25ns输出通道时间
tWC大于55ns写操作周期时间
tPWE大于40ns写信号时间宽度
tSA没有限制地址建立时间

📦片内FSMC介绍

通过上面对SRAM的介绍,我们可以看到,在对SRAM进行的每一次操作,都必须按照SRAM的时序来进行操作,我们可以通过程序控制各个引脚的电平来实现这个时序,从而操作SRAM,但是这样不仅很复杂,而且灵敏度也会比较低,而本喵使用的STM32F103ZET6就有一个片内的外设,可以实现SRAM的时序,我们只需要将该外设配置好,就能像操作内部的SRAM一样操作外部扩展的SRAM,这个外设就是FSMC。

  • STM32F1系列芯片使用FSMC外设来管理扩展的存储器,FSMC是Flexible Static Memory Controller的缩写,译为灵活的静态存储控制器。
  • 它可以用于驱动包括SRAM、NOR FLASH以及NAND FLSAH类型的存储器。
  • 不能驱动如SDRAM这种动态的存储器,而在STM32F429系列的控制器中,它具有FMC外设,支持控制SDRAM存储器。

图
这是FSMC的框图。

因为我们扩展的是SRAM,所以我们使用到的只有红色框中的部分引脚,这些引脚也被叫做通讯引脚。

  1. 通讯引脚
  • 由于控制不同类型存储器的时候会有一些不同的引脚,所以看起来有非常多的引脚,但是其中地址线FSMC_A和数据线FSMC_D是所有控制器都共用的。

  • 其中比较特殊的是FSMC_NE,用于控制SRAM芯片的控制信号线,STM32具有FSMC_NE1/2/3/4号引脚,不同的引脚对应STM32内部不同的地址区域。

  • 例如,当STM32访问0x68000000-0x6BFFFFFF地址空间时,FSMC_NE3引脚会自动设置为低电平,由于它连接到SRAM的CE#引脚,所以SRAM的片选被使能,而访问0x60000000-0x63FFFFFF地址时,FSMC_NE1会输出低电平。当使用不同的FSMC_NE引脚连接外部存储器时,STM32访问SRAM的地址不一样,从而达到控制多块SRAM芯片的目的,具体的地址映射在后面会讲解。

FSMC引脚名称对应的SRAM引脚名称说明
FSMC_NBL[1:0]LB#、UB#数据掩码信号
FSMC_A[18:0]A[18:0]行地址线
FSMC_D[15:0]I/O[15:0]数据线
FSMC_NWEWE#写入使能
FSMC_NOEOE#输出使能(读使能)
FSMC_NE[1:4]CE#片选信号

在扩展时,按照上表讲FSMC的引脚和扩展SRAM的引脚对应连接起来。

  1. 存储器控制器

上面不同类型的引脚是连接到FSMC内部对应的存储控制器中的。NOR/PSRAM/SRAM设备使用相同的控制器,NAND/PC卡设备使用相同的控制器,不同的控制器有专用的寄存器用于配置其工作模式。

控制SRAM使用的控制器有三种,FSMC_BCR1/2/3/4控制寄存器、FSMC_BTR1/2/3/4片选时序寄存器以及FSMC_BWTR1/2/3/4写时序寄存器。每种寄存器都有4个,分别对应于4个不同的存储区域,各种寄存器介绍如下:

  • SRAM/NOR闪存片选控制寄存器 1…4 (FSMC_BCR1…4)

FSMC_BCR控制寄存器可配置要控制的存储器类型、数据线宽度以及信号有效极性能参数。

仅看我们需要配置的位:

图
该位是用来控制是否适用FSMC_BWTR寄存器的,也就是决定是否支持对外设的读和写使用俩种不同的时序,这个我们在后面会详细介绍。

图
该位控制的是写使能位,也就是决定是否向SRAM中写入数据。

图
该位是控制数据在SRAM中是按照16位方式存放还是8位方式存放。

图
该位是用来设置存储器类型的,我们这里扩展的是SRAM,所以将其设置为00即可。

图
该位决定着是否使用存储块。

  • SRAM/NOR闪存片读时序寄存器 1…4 (FSMC_BTR1…4)

FMC_BTR时序寄存器用于配置SRAM访问时的各种时间延迟,如数据保持时间、地址保持时间等。

图
该位决定采样哪个访问模式来访问扩展的SRAM,这里我们采样的是模式A,所以设置成00即可。

下面本喵来给大家介绍一下,模式A是什么:

访问的模式,就是按照什么样的时序来访问,不同的模式,时序是不同的,我们来看A模式下,FSMC访问时产生的时序。

读操作的访问时序:

图
该时序对应着的就是框图中引脚信号的变化时序。

写操作的访问时序:

图
该时序对应着的就是框图中引脚信号的变化时序。

注意到时序图最下边的红色框了吗?这俩个值的设置需要用到该寄存器中的另外俩个位

图
这俩个位就是专门用来设置上面时序图中的俩个红色框中的值的,也是就是地址建立时间和数据保持时间。

  • SRAM/NOR闪存写时序寄存器 1…4 (FSMC_BWTR1…4)

FMC_BWTR写时序寄存器与FMC_BTR寄存器控制的参数类似,它专门用于控制写时序的时间参数。

图
可以看到,该寄存器和上面的读时序寄存器配置的东西一模一样,如果读SRAM和写SRAM的时序不一样时,就会同时用到这俩个寄存器,如果读和写的时序是一样的,只需要配置这个写寄存器就可以了,读的不需要配置。

总的来说,在使用模式A的时候,我们需要配置下表中的这些位。

图

  1. 时钟控制逻辑
  • FSMC外设挂载在AHB总线上,时钟信号来自于HCLK(默认72MHz),控制器的同步时钟输出就是由它分频得到。例如,NOR控制器的FSMC_CLK引脚输出的时钟,它可用于与同步类型的SRAM芯片进行同步通讯,它的时钟频率可通过FSMC_BTR寄存器的CLKDIV位配置,可以配置为HCLK的1/2或1/3,也就是说,若它与同步类型的SRAM通讯时,同步时钟最高频率为36MHz。

本示例中,我们在看SRAM时序图的时候,发现它没有时钟信号,是按照一种特点的时序在进行访问的,所以这里的SRAM是异步类型的除尘器,不使用同步时钟信号,所以时钟分频配置不起作用。

  1. FSMC的地址映射

图

  • 图中左侧的是Cortex-M3内核的存储空间分配,右侧是STM32 FSMC外设的地址映射。
  • 可以看到FSMC的NOR/PSRAM/SRAM/NAND FLASH以及PC卡的地址都在External RAM地址空间内。
  • 正是因为存在这样的地址映射,使得访问FSMC控制的存储器时,就跟访问STM32的片上外设寄存器一样(片上外设的地址映射即图中左侧的“Peripheral”区域)。
  • FSMC把整个External RAM存储区域分成了4个Bank区域,并分配了地址范围及适用的存储器类型,NOR及SRAM存储器只能使用Bank1的地址。

在NOR及SRAM区域,每个Bank的内部又分成了4个小块,每个小块有相应的控制引脚用于连接片选信号,如FSMC_NE[4:1]信号线可用于选择BANK1内部的4小块地址区域,当STM32访问0x68000000-0x6BFFFFFF地址空间时,会访问到Bank1的第3小块区域,相应的FSMC_NE3信号线会输出控制信号。

图
本喵使用的SRAM的片选信号就是与FSMC_NE3相连的。

在了解了SRAM和FSMC以后,我们知道,扩展外部的SRAM需要用固定是时序来访问SRAM,而FSMC恰好可以提供时序,所以我们在使用的时候,将FSMC访问模式的时序配置成SRAM所需要的时序就可以时序对扩展SRAM的访问。

📦FSMC的SRAM结构体说明

控制FSMC使用SRAM存储器时主要是配置时序寄存器以及控制寄存器,利用ST标准库的SRAM时序结构体以及初始化结构体可以很方便地写入参数。

  1. 时序结构体

图
成员变量的意义:

  • FSMC_AddressSetupTime :
    本成员设置地址建立时间,它可以被设置为0-0xF个HCLK周期数,按STM32标准库的默认配置,HCLK的时钟频率为72MHz,即一个HCLK周期为1/72微秒。

  • FSMC_AddressHoldTime :
    本成员设置地址保持时间,它可以被设置为0-0xF个HCLK周期数。

  • FSMC_DataSetupTime :
    本成员设置数据建立时间,它可以被设置为0-0xF个HCLK周期数。

  • FSMC_BusTurnAroundDuration :
    本成员设置总线转换周期,在NOR FLASH存储器中,地址线与数据线可以分时复用,总线转换周期就是指总线在这两种状态间切换需要的延时,防止冲突。控制其它存储器时这个参数无效,配置为0即可。

  • FSMC_CLKDivision :
    本成员用于设置时钟分频,它以HCLK时钟作为输入,经过FSMC_CLKDivision分频后输出到FSMC_CLK引脚作为通讯使用的同步时钟。控制其它异步通讯的存储器时这个参数无效,配置为0即可。

  • FSMC_DataLatency :
    本成员设置数据保持时间,它表示在读取第一个数据之前要等待的周期数,该周期指同步时钟的周期,本参数仅用于同步NOR FLASH类型的存储器,控制其它类型的存储器时,本参数无效。

  • FSMC_AccessMode :
    本成员设置存储器访问模式,不同的模式下FSMC访问存储器地址时引脚输出的时序不一样,可选FSMC_AccessMode_A/B/C/D模式。一般来说控制SRAM时使用A模式。

  1. 初始化结构体

图

  • FSMC_Bank:
    本成员用于选择FSMC映射的存储区域,它的可选参数以及相应的内核地址映射范围见下面的表格。
可以输入的宏对应的地址区域
FSMC_Bank1_NORSRAM10x60000000-0x63FFFFFF
FSMC_Bank1_NORSRAM20x64000000-0x67FFFFFF
FSMC_Bank1_NORSRAM30x68000000-0x6BFFFFFF
FSMC_Bank1_NORSRAM40x6C000000-0x6FFFFFFF
  • FSMC_DataAddressMux:
    本成员用于设置地址总线与数据总线是否复,(FSMC_DataAddressMux_Enable /Disable),在控制NOR FLASH时,可以地址总线与数据总线可以分时复用,以减少使用STM32信号线的数量。

  • FSMC_MemoryType:
    本成员用于设置要控制的存储器类型,它支持控制的存储器类型为SRAM、PSRAM以及NOR FLASH(FSMC_MemoryType_SRAM/PSRAM/NOR),我们这里需要设置成SRAM。

  • FSMC_MemoryDataWidth:
    本成员用于设置要控制的存储器的数据宽度,可选择设置成8或16位(FSMC_MemoryDataWidth_8b /16b)。

  • FSMC_BurstAccessMode :
    本成员用于设置是否使用突发访问模式(FSMC_BurstAccessMode_Enable/Disable),突发访问模式是指发送一个地址后连续访问多个数据,非突发模式下每访问一个数据都需要输入一个地址,仅在控制同步类型的存储器时才能使用突发模式。

  • FSMC_AsynchronousWait:
    本成员用于设置是否使能在同步传输时使用的等待信号(FSMC_AsynchronousWait_Enable/Disable),在控制同步类型的NOR或PSRAM时,存储器可以使用FSMC_NWAIT引脚通知STM32需要等待。

  • FSMC_WaitSignalPolarity :
    本成员用于设置等待信号的有效极性,即要求等待时,使用高电平还是低电平(FSMC_WaitSignalPolarity_High/Low)。

  • FSMC_WrapMode :
    本成员用于设置是否支持把非对齐的AHB突发操作分割成2次线性操作(FSMC_WrapMode_Enable/Disable),该配置仅在突发模式下有效。

  • FSMC_WaitSignalActive:
    本成员用于配置在突发传输模式时,决定存储器是在等待状态之前的一个数据周期有效还是在等待状态期间有效(FSMC_WaitSignalActive_BeforeWaitState/DuringWaitState)。

  • FSMC_WriteOperation :
    这个成员用于设置是否写使能(FSMC_WriteOperation_ Enable /Disable),禁止写使能的话FSMC只能从存储器中读取数据,不能写入。

  • FSMC_WaitSignal
    本成员用于设置当存储器牌突发传输模式时,是否允许通过NWAIT信号插入等待状态(FSMC_WaitSignal_Enable/Disable)。

  • FSMC_ExtendedMode:
    本成员用于设置是否使用扩展模式(FSMC_ExtendedMode_Enable/Disable),在非扩展模式下,对存储器读写的时序都只使用FSMC_BCR寄存器中的配置,即下面的FSMC_ReadWriteTimingStruct结构体成员;在扩展模式下,对存储器的读写时序可以分开配置,读时序使用FSMC_BCR寄存器,写时序使用FSMC_BWTR寄存器的配置,即下面的FSMC_WriteTimingStruct结构体。

  • FSMC_ReadWriteTimingStruct:
    本成员是一个指针,赋值时使用上面讲解的时序结构体FSMC_NORSRAMInitTypeDef设置,然后传地址给本成员,当不使用扩展模式时,读写时序都使用本成员的参数配置。

  • FSMC_WriteTimingStruct:
    同样地,本成员也是一个时序结构体的指针,只有当使用扩展模式时,本配置才有效,它是写操作使用的时序。

虽然结构体中的成员变量有很多,但是我们这里使用到的还真没有几个。

📦读写时序的结构体配置

  1. 写时序配置

图
以对SRAM进行写为例,上面的图是SRAM的写时序图,下面的图是FSMC模式A的写时序图。

SRAM时序图中的时间与FSMC需要配置的时间用相同颜色的线条连接起来。从图中可以看到,我们配置FSMC寄存器只需要配置ADDSET和DARST的值即可。

时间参数要求时间说明
tRC大于55ns读操作周期时间
tAA大于0ns地址通道时间
tDOE不超过25ns输出通道时间
tWC大于55ns写操作周期时间
tPWE大于40ns写信号时间宽度
tSA没有限制地址建立时间

按照我们上面介绍时序图时总结出的时间要求,配置FSMC中相应寄存器中的值。

  • FSMC挂载在AHB总线,时钟频率是72MHZ,也就是13.8ns
  • 这里将DATST设置为2,那么tPWE的时间是(2+1)*13.8 = 41.4ns,符合表中的大于40ns
  • 将ADDSET设置为1,那么tSA的时间是(1+1)*13.8 =27.6ns
  • 此时一个写操作所用的时间是(2+1+1+1)*13.8=69ns,符合表中的大于55ns

上代码

		//需要配置的成员变量
	readWriteTiming.FSMC_AddressSetupTime = 0x01;	 //地址建立时间(ADDSET)为2个HCLK 1/72M=27.6ns
    readWriteTiming.FSMC_DataSetupTime = 0x02;		 //数据保持时间(DATAST)为3个HCLK 3/72M=41.4ns
	readWriteTiming.FSMC_AccessMode = FSMC_AccessMode_A;	 //模式A 
		//不需要配置的成员变量
    readWriteTiming.FSMC_BusTurnAroundDuration = 0x00;
	readWriteTiming.FSMC_AddressHoldTime = 0x00;	 //地址保持时间(ADDHLD)模式A未用到	
    readWriteTiming.FSMC_CLKDivision = 0x00;
    readWriteTiming.FSMC_DataLatency = 0x00;

需要配置的变量我们按照上面的分析来配置,不需要配置的直接给0就行。

  1. 读时序配置

图
以对SRAM进行读为例,上面的图是SRAM的读时序图,下面的图是FSMC模式A的读时序图。

SRAM时序图中的时间与FSMC需要配置的时间用相同颜色的线条连接起来。从图中可以看到,我们配置FSMC寄存器只需要配置ADDSET和DARST的值即可。

同样需要对照这个时间表配置相应的时间参数

时间参数要求时间说明
tRC大于55ns读操作周期时间
tAA大于0ns地址通道时间
tDOE不超过25ns输出通道时间
tWC大于55ns写操作周期时间
tPWE大于40ns写信号时间宽度
tSA没有限制地址建立时间
  • FSMC挂载在AHB总线,时钟频率是72MHZ,也就是13.8ns
  • 将DATST的值设置为2,此时TDOE的值是(2+1)*13.8 = 41.4ns
  • 将ADDSET的值设置为1,那么tAA的值是(1+1)*13.8 = 27.6ns
  • 此时读过程的整个时间tRC的值为(2+1+1+1+2)*13.8 = 96.6ns,大于55ns,符合要求

读时序的配置和写时序的一样,所以配置代码也是一样的,就像上面那样,不过在写代码时,我们将扩展模式关闭即可,此时读和写都是用的写的时序。

📦硬件连接及引脚配置

图
SRAM硬件图。
图
SRAM控制引脚与MCU连接图。
图
SRAM地址引脚与MCU连接图。
图

这是SRAM数据引脚与MCU连接图,

  • 控制线:
    PE0 -PE1连接FSMC_NBL0和FSMC_NBL1俩根掩码信号线
    PG10连接FSMC_NE3片选信号线
    PD4 -PD5连接FSMC_NOE读信号线和FSMC_NWE写信号线
  • 地址线:
    PF0 - PF5连接地址线A0 - A5
    PF12 - PF15连接地址线A6 - A9
    PG0 - PG5连接地址线A10 - A15
    PD11 - PD13连接地址线A16 - A18
  • 数据线:
    PD14 - PD15连接数据线DO - D1
    PD0 - PD1连接数据线D2 - D3
    PE7 - PE15连接数据线D4 - D12
    PD8 - PD10连接数据线D13 - D15

由于这里的GPIO都是复用的FSMC,所以全部设置为推挽复用输出。

来看GPIO配置的代码:

GPIO_InitTypeDef  GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD|RCC_APB2Periph_GPIOE|RCC_APB2Periph_GPIOF|RCC_APB2Periph_GPIOG,ENABLE);//将用到的IO口时钟使能

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);

图
这里库函数中,结构体中的引脚实质上配置的是控制寄存器CR以及CRH。

图
这是CRL存器的位分布图,控制的是每组IO中的0~7低8位IO口

  • 以代码中配置PD口为例,我们将GPIO_InitStructure.GPIO_Pin = 0xFF33,实质上就是一次性的将用到的PD口全部配置成为推挽复用输出了。

📦代码实现

在上面我们已经将时序结构体和GPIO结构体配置好了,接下来就需要配置初始化结构体了。

初始化结构体中的成员变量所代表的意义在上面已经讲解过了,这里我们直接看代码:

FSMC_NORSRAMInitTypeDef  FSMC_NORSRAMInitStructure;
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;// 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; //读写同样时序

用到的结构体已经配置好了,接下来就是对进行初始化了。

看代码:

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,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 = 0x01;	 //地址建立时间(ADDSET)为2个HCLK 1/72M=27.6ns
    readWriteTiming.FSMC_DataSetupTime = 0x02;		 //数据保持时间(DATAST)为3个HCLK 3/72M=41.4ns
	readWriteTiming.FSMC_AccessMode = FSMC_AccessMode_A;	 //模式A 
		//不需要配置的成员变量
    readWriteTiming.FSMC_BusTurnAroundDuration = 0x00;
	readWriteTiming.FSMC_AddressHoldTime = 0x00;	 //地址保持时间(ADDHLD)模式A未用到	
    readWriteTiming.FSMC_CLKDivision = 0x00;
    readWriteTiming.FSMC_DataLatency = 0x00;
    

 
    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;// 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										  
											
}

将我们上面配置好的东西放在该函数中,并且进行初始化后再使能,这样一来,SRAM的整个初始化就完成了,我们在代码中只需要像访问内部的SRAM一样使用它就行了。

我们这里进行的实验是在SRAM中写入250000个32位大小的数字到SRAM中,正好是1M个字节的数据,再将其读出来通过串口显示到屏幕上。

这是sram.h中代码:

#ifndef __SRAM_H
#define __SRAM_H

#include "sys.h"

#define BASE_ADDRESS_FSMC_BANK1_SRAM3 ((u32)(0x68000000))
#define N 250000

void SRAM_Init(void);
void GPIO_Config(void);
void FSMC_SRAM_WriteBuffer(u8* pBuffer,u8 offset,u8 size);
void FSMC_SRAM_ReadBuffer(u8* pBuffer,u8 offset,u8 size);
#endif

main.c中代码:

u32 testsram[N] __attribute__((at(BASE_ADDRESS_FSMC_BANK1_SRAM3)));//指定测试数组的内存到Bank_SRAM3

int main()
{
	u32 i = 0;
	uart_init(115200);
	SRAM_Init();
	LED_Init();
	
	//将1M字节数据存入数组中,也就是写入SRAM中
	for(i=0;i<N;i++)
	{
		testsram[i]=i;
	}
	while(1)
	{
		printf("\r\nSRAM中的数据是:\r\n");
		for(i=0;i<N;i++)
		{
			LED0 = !LED0;
			printf("%d ",testsram[i]);
		}
	}
}

注意:
图
该语句是将数组的地址指定在从0x68000000处开始。

图
define定义的标识符常量定义的是FSMC的Ban1中的区域3的基地址。

📦效果展示

图
可以看到,数字在不停的变化,而且是递增的,这些是从SRAM中读取的数字。

📦总结

使用FSMC扩展SRAM的关键点就在于,用MCU内置的外设FSMC的不同访问模式模拟访问SRAM的时序,只需要对照时间限制表和时序图,将关键的时间通过寄存器配置好,这样就在我们使用SRAM的时候可以向访问内部的SRAM一样,不用再关注时序变化。

相关文章:

  • 猿创征文|【数据结构】二叉树相关接口的实现及对应OJ题
  • 【操作系统笔记 】进程(1):前驱图和程序执行
  • LeetCode 306周赛 (补记)
  • vue集成海康h5player实现播放
  • Endnote引用文献时期刊名称不缩写问题-论文投稿经验总结-第1期
  • CREO图文教程:三维设计案例之油缸顶物体的骨架模型设计图文教程之详细攻略
  • 表单与表格练习
  • LeetCode 70. 爬楼梯 C/C++/Python
  • 【24】冒险和预测(三):CPU里的“线程池”
  • js内置对象之String
  • 猿创征文|【C++游戏引擎Easy2D】我拿吃零食的时间,学会了在C++上添加可点击按钮
  • 【C++】类和对象(三)运算符重载 (其中有默认成员函数中的 赋值运算符重载、等等)
  • 栈和队列(C语言实现)
  • S函数介绍
  • 羡慕实时数据看板?来看看Python的交互数据分析可视化工具! ⛵
  • 时间复杂度分析经典问题——最大子序列和
  • Angular4 模板式表单用法以及验证
  • GraphQL学习过程应该是这样的
  • JavaScript 事件——“事件类型”中“HTML5事件”的注意要点
  • js对象的深浅拷贝
  • Linux下的乱码问题
  • MySQL用户中的%到底包不包括localhost?
  • Python爬虫--- 1.3 BS4库的解析器
  • redis学习笔记(三):列表、集合、有序集合
  • SAP云平台运行环境Cloud Foundry和Neo的区别
  • Spring核心 Bean的高级装配
  • SwizzleMethod 黑魔法
  • Web Storage相关
  • -- 查询加强-- 使用如何where子句进行筛选,% _ like的使用
  • 后端_MYSQL
  • 聊聊hikari连接池的leakDetectionThreshold
  • 判断客户端类型,Android,iOS,PC
  • 七牛云 DV OV EV SSL 证书上线,限时折扣低至 6.75 折!
  • 驱动程序原理
  • 使用iElevator.js模拟segmentfault的文章标题导航
  • 腾讯视频格式如何转换成mp4 将下载的qlv文件转换成mp4的方法
  • #include到底该写在哪
  • #stm32整理(一)flash读写
  • #多叉树深度遍历_结合深度学习的视频编码方法--帧内预测
  • (蓝桥杯每日一题)平方末尾及补充(常用的字符串函数功能)
  • (六) ES6 新特性 —— 迭代器(iterator)
  • (三分钟了解debug)SLAM研究方向-Debug总结
  • (转)nsfocus-绿盟科技笔试题目
  • (转)shell中括号的特殊用法 linux if多条件判断
  • .cn根服务器被攻击之后
  • .mysql secret在哪_MYSQL基本操作(上)
  • .NET Compact Framework 多线程环境下的UI异步刷新
  • .NET Core 将实体类转换为 SQL(ORM 映射)
  • .NET MAUI学习笔记——2.构建第一个程序_初级篇
  • .NET Remoting Basic(10)-创建不同宿主的客户端与服务器端
  • .net 中viewstate的原理和使用
  • .NET(C#) Internals: as a developer, .net framework in my eyes
  • .NET单元测试
  • .NET建议使用的大小写命名原则
  • .py文件应该怎样打开?