通过上面的讲解,通过对 FSMC 相关的寄存器的描述,大家对 FSMC 的原理有了一个初步的认识,如果还不熟悉的朋友,请一定要搜索网络资料理解FSMC的原理。只有理解了原理,使用库函数才可以得心应手。那么,在库函数中是怎么实现FSMC的配置的呢?FSMC_BCRx,FSMC_BTRx 寄存器在库函数是通过什么函数来配置的呢?
下面,我们来讲解一下 FSMC 相关的库函数。
要使用FSMC,当然首先得开启其时钟。然后需要把FSMC_D0—15,FSMCA0—18等相关IO 口,全部配置为复用输出,并使能各 IO 组的时钟。
RCC_AHB3PeriphClockCmd(RCC_AHB3Periph_FSMC,ENABLE);//使能 FSMC 时钟
配置 IO 口为复用输出的关键行代码为:
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;//复用输出
关于引脚复用映射配置,这在 LCD 实验章节也讲解非常详细,调用函数为:
void GPIO_PinAFConfig(GPIO_TypeDef* GPIOx, uint16_t GPIO_PinSource, uint8_t GPIO_AF);
针对每个复用引脚调用这个函数即可,例如 GPIOD.0
引脚复用映射配置方法为:
GPIO_PinAFConfig(GPIOD,GPIO_PinSource0,GPIO_AF_FSMC);//PD0,AF12
根据前面的讲解,初始化 FSMC 主要是初始化三个寄存器FSMC_BCRx
,FSMC_BTRx
,FSMC_BWTRx
,那么在固件库中是怎么初始化这三个参数的呢?
固件库提供了 3 个FSMC
初始化函数分别为:
FSMC_NORSRAMInit();
FSMC_NANDInit();
FSMC_PCCARDInit();
这三个函数分别用来初始化 4 种类型存储器。这里根据名字就很好判断对应关系。用来初始化NOR和 SRAM 使用同一个函数 FSMC_NORSRAMInit()
。所以我们之后使用的 FSMC 初始化函数为FSMC_NORSRAMInit()
。下面我们看看函数定义:
void FSMC_NORSRAMInit(FSMC_NORSRAMInitTypeDef* FSMC_NORSRAMInitStruct);
这个函数只有一个入口参数,也就是FSMC_NORSRAMInitTypeDef
类型指针变量,这个结构体的成员变量非常多,因为FSMC
相关的配置项很多,但是对于SRAM我们只需要配置对应的成员就可以了,并不是所有结构体成员都需要配置。
typedef struct
{
uint32_t FSMC_Bank;/*设置要控制的 Bank 区域 */
uint32_t FSMC_DataAddressMux;/*设置地址总线与数据总线是否复用 */
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_NORSRAMTimingInitTypeDef* FSMC_WriteTimingStruct;
}FSMC_NORSRAMInitTypeDef;
从这个初始化结构体我们可以看出,前面有13个基本类型(unit32_t)的成员变量,这 13 个参数是用来配置片选控制寄存器FSMC_BCRx
。最后面还有两个FSMC_NORSRAMTimingInitTypeDef
指针类型的成员变量。前面我们讲到,FSMC
有读时序和写时序之分,所以这里就是用来设置读时序和写时序的参数了。也就是说,这两个参数是用来配置寄存器SMC_BTRx
和FSMC_BWTRx
,后面我们会讲解到。
下面,我们主要来看看模式A下的相关配置参数:
FSMC_Bank1_NORSRAM3
。FSMC_MemoryType_SRAM
。 FSMC_MemoryDataWidth_16b
。FSMC_WriteOperation_Enable
。FSMC_ExtendedMode_Disable
。上面的这些参数是与模式A相关的,下面我们也来稍微了解一下其他几个参数的意义吧:
FSMC_DataAddressMux_Disable
。读写时序参数的两个变量FSMC_ReadWriteTimingStruct 和FSMC_WriteTimingStruct,它们都是 FSMC_NORSRAMTimingInitTypeDef 结构体指针类型,这两个参数在初始化的时候分别用来初始化片选控制寄存器FSMC_BTRx和写操作时序控制寄存器 FSMC_BWTRx。下面我们看看 FSMC_NORSRAMTimingInitTypeDef
类型的定义:
控制 FSMC使用 SRAM 存储器时主要是配置时序寄存器以及控制寄存器,利用ST 标准库的 SRAM 时序结构体,以及初始化结构体可以很方便地写入参数。
typedef struct
{
uint32_t FSMC_AddressSetupTime;/*地址建立时间,0-0xF个HCLK 周期*/
uint32_t FSMC_AddressHoldTime;/*地址保持时间,0-0xF个HCLK 周期*/
uint32_t FSMC_DataSetupTime;/*数据保持时间,0-0xF个HCLK 周期*/
uint32_t FSMC_BusTurnAroundDuration;/*总线转换周期,0-0xF个HCLK 周期,在NOR FLASH才用到,对于SRAM无效 */
uint32_t FSMC_CLKDivision;/*时钟分频因子,1-0xF,若控制异步存储器,本参数无效 */
uint32_t FSMC_DataLatency;/*数据延迟时间,若控制异步存储器,本参数无效 */
uint32_t FSMC_AccessMode;/*设置访问模式 */
}FSMC_NORSRAMTimingInitTypeDef;
这个结构体成员定义的都是 SRAM 读写时序中的各项时间参数,这些成员的的参数都与 FSMC_BRT 及 FSMC_BWTR 寄存器配置对应,各个成员介绍如下:
FSMC_AddressSetupTime 本成员设置地址建立时间,即FSMC读写时序图中的 ADDSET 值,它可以被设置为 0-0xF个HCLK周期数,按STM32标准库的默认配置,HCLK的时钟频率为168MHz,即一个 HCLK周期为 1/168微秒。
FSMC_AddressHoldTime 本成员设置地址保持时间,它可以被设置为 0-0xF个HCLK周期数。地址保持时间(ADDHLD)模式A未用到,配置为0x00即可。
FSMC_DataSetupTime 本成员设置数据建立时间,即FSMC读写时序图中的 DATAST值,它可以被设置为0-0xF个HCLK周期数。
FSMC_BusTurnAroundDuration 本成员设置总线转换周期,在NOR FLASH存储器中,地址线与数据线可以分时复用,总线转换周期就是指总线在这两种状态间切换需要的延时,防止冲突。但是在控制其它存储器时(如SRAM)这个参数无效,配置为0即可。
FSMC_CLKDivision 本成员用于设置时钟分频,它以 HCLK时钟作为输入,经过 FSMC_CLKDivision分频后输出到 FSMC_CLK引脚作为通讯使用的同步时钟。控制其它异步通讯的存储器时这个参数无效,我们的SRAM属于异步通讯的存储器,用不到这个参数,所以配置为 0即可。
FSMC_DataLatency 本成员设置数据保持时间,它表示在读取第一个数据之前要等待的周期数,该周期指同步时钟的周期,本参数仅用于同步 NOR FLASH类型的存储器,控制其它类型的存储器时这个参数无效,我们的SRAM属于异步通讯的存储器,用不到这个参数,所以配置为0即可。
FSMC_AccessMode 本成员设置存储器访问模式,不同的模式下 FSMC访问存储器地址时引脚输出的时序不一样,可选 FSMC_AccessMode_A/B/C/D 模式。一般来说控制 SRAM 时使用A模式,其实就是模式A,在控制SRAM是也没有见到过用其他模式的。
综上述所,我们这7个成员变量,我们的SRAM使用到了其中的3个,其他的四个是NOR FLASH需要配置的,其他的4个配置为0就可以了。
具体的配置如下:
//地址建立时间(ADDSET)为1个HCLK,1/168M = 6ns
readWriteTiming.FSMC_AddressSetupTime = 0x00;//(0+1)个HCLK周期=6ns >0ns 满足要求
//数据保持时间(DATAST)+ 1个HCLK = 9/168M=54ns(对EM的SRAM芯片)
readWriteTiming.FSMC_DataSetupTime = 0x08;//(8+1)个HCLK周期=54ns >25ns 满足要求
//以上的设置满足6>0 54>25 54+6>55三个条件,所以设置是合理的。
//以下配置跟异步SRAM无关,默认设置为0就可以了
//地址保持时间(ADDHLD)模式A未用到
readWriteTiming.FSMC_AddressHoldTime = 0x00;
//设置总线转换周期,仅用于复用模式的NOR操作(这里是SRAM,不用管)
readWriteTiming.FSMC_BusTurnAroundDuration = 0x00;
//设置时钟分频,仅用于同步类型的存储器(这里是异步型的SRAM,不用管)
readWriteTiming.FSMC_CLKDivision = 0x00;
//数据保持时间,仅用于同步型的NOR(这里是异步型的SRAM,不用管)
readWriteTiming.FSMC_DataLatency = 0x00;
//选择匹配SRAM的模式
readWriteTiming.FSMC_AccessMode = FSMC_AccessMode_A;
上面我们设置了FSMC_AddressSetupTime 地址建立时间为0,FSMC_DataSetupTime 数据保持时间为8,为什么要这样设置呢?
这就需要看SRAM的时序图和FSMC模式A的时序图了,只有在时序图中才能找到答案,同时我们在操作别的存储器时也是需要通过时序图来配置具体的参数的。
下面,我们就来看看时序图吧!
使用模式A,读写共用时序(共用时序时,以写时序为准),配置代码如下:
//地址建立时间(ADDSET)为1个HCLK,1/168M = 6ns
readWriteTiming.FSMC_AddressSetupTime = 0x00;//(0+1)个HCLK周期=6ns >0ns 满足要求
//地址保持时间(ADDHLD)模式A未用到
readWriteTiming.FSMC_AddressHoldTime = 0x00;
//数据保持时间(DATAST)+ 1个HCLK = 9/168M=54ns(对EM的SRAM芯片)
readWriteTiming.FSMC_DataSetupTime = 0x08;//(8+1)个HCLK周期=54ns >25ns 满足要求
为什么地址建立时间是0ns呢?因为从IS62WV51216写时序关键特性中可以看到,Address Setup Time为0,所以配置时,地址建立时间要等于0。
地址保持时间模式A未用到,所以配置为0。
数据建立时间为什么是54ns呢?因为从IS62WV51216写时序关键特性中可以看到,Dataup to Write End最小值为30ns,所以配置时,数据保持时间要大于30ns。至于选择的54ns并不是严格按照手册时间来的,这需要实际调试才能确定具体值。
那为什么数据建立时间配置为8,但却是9个HCLK周期时间呢?从STM32中文参考手册的时序图中可以看到,模式A写入时序的数据建立时间为:数据建立时间=HCLK周期+1,初始化程序时,如果设置DATAST=0,则实际数据建立周期为1,即实际数据建立周期比DATAST值多1个HCLK周期。
经过上面的设置后写一次操作的时间就是(0+1)*6ns+(8+1)*6ns=60ns>55ns,是满足要求的。
参数确定大体就是这样的思路,其他模式及不同的芯片确定参数的思路是一样的。
最后是在帖子中看到的关于时序的讲解,挺有趣,一并放在文章中。
“先搞清逻辑关系。这是CPU读存储器的时序。CPU索取,存储器付出。我有很多单元,你要读我,至少要把那个单元的门牌号告诉我吧?——这就是地址线,由CPU填写(输出);地址线上非1即0,你放不放地址都这样的,所以,你要告诉我什么时候是“地址”,什么时候是“垃圾”吧?——这就是OE线下降沿的作用,也要CPU填写。(一般来说,要在地址稳定以后,OE才发出下降沿。上面图没有给出这个差别。) 好了,CPU该办的手续已经完了,球到了存储器一边。我要去这个单元找东西,总需要点时间吧?——这就是OE下降沿以后,RAM发出数据的时间间隔。艾玛,累死我了,你要的东西找到了,我放上去了!按说,数据放到总线上了,可是CPU如何才能知道数据来了呢?因为不论是数据还是垃圾,那电线上面总有1、0这些东西。这时出现不同解决方法了。一种方法,由存储器给CPU发出一个通知信号:好了,快来拿!这个信号通常叫做 Data Available,数据可得,简写 DAV,可能有人见过。CPU接到这个信号,自然会做。另一种方法,有老大强势规定:OE下降沿之后,你丫必须在1秒钟之内把东西交出来!我只管时间到就来拿!这是多数存储器遵守的规定。这两种方法,由于是不同的公司提出的,经常以公司名字来命名。Motorola总线,intel总线,听说过吧?OK,明白这些意思,稍微想一想,应当就可以理解各种时序的时间了。以后再不用问人了。另外,手册里经常可以看到,这些时间一般会有最大值、最小值,有些只给出一项。比如上面提到,从OE下降沿到存储器交出数据的时间,在存储器手册里面,这个时间只会给出最大值。因为用户必须按“最长时间”操作,不能有侥幸心理。给出最小值没有实际意义。
”
FSMC 对不同的存储器类型同样提供了不同的使能函数:
void FSMC_NORSRAMCmd(uint32_t FSMC_Bank, FunctionalState NewState);
void FSMC_NANDCmd(uint32_t FSMC_Bank, FunctionalState NewState);
void FSMC_PCCARDCmd(FunctionalState NewState);
这个就比较好理解,我们这里不讲解,我们是SRAM,所以使用的是第一个函数。
FSMC_NORSRAMCmd(FSMC_Bank1_NORSRAM3, ENABLE); // 使能 BANK3
通过以上几个步骤,我们就完成了FSMC的配置,可以访问IS62WV51216有了,这里还需要注意,因为我们使用的是BANK1的区域3,所以 HADDR[27:26]=10,故外部内存的首地址为0X68000000。
下面来说一下在STM32F407中SRAM的硬件连接:对于FSMC来说,它已经集成到了单片机内部,它的提供给的管脚已经确定了,是不改动的,这个可以参考STM32对应芯片的 Datasheet。唯具有灵活性的就是FSMC_NE,具体用哪个 FSMC_NE管脚来和你的SRAM相连,当然是你的自由,但是不要忘了,你要找到你选的 FSMC_NE所对应的地址范围,不然写程序的时候就搞不清喏!
我们需要将SRAM芯片和STM32单片机连接起来,就要来看看他们之间的连线和接口。
STM32FSMC外设 | SRAM内存芯片 | 引脚说明 |
---|---|---|
FSMC_NE3 | 片选信号,低电平有效 | |
FSMC_OE | 输出使能信号,低电平有效 | |
NWE | 写使能信号,低电平有效 | |
FSMC_NBL0 | 低字节控制信号 | |
FSMC_NBL1 | 高字节控制信号 | |
FMSC_A[0:18] | 地址信号线 | |
SMC_D[0:15] | 数据信号线 |
搜索并选中芯片STM32F407ZGT6:
配置时钟源:
如果选择使用外部高速时钟(HSE),则需要在System Core中配置RCC;如果使用默认内部时钟(HSI),这一步可以略过;
这里我都使用外部时钟:
调试选项配置:
默认没有配置下载引脚,烧录之后下载器将无法再检测到,这里我使用JLink,所以配置为SW选项:
配置串口:
开发板板载了一个CH340换串口,连接到USART1,但是引脚不是默认引脚,需要手动修改。
接下来开始配置USART1:
配置FSMC外设:
(1)FSMC配置
我们板子上面的双口SRAM原理图如下:
通过原理图可以看出:
根据这些信息,在STM32CubeMX中先配置SRAM1的基本设置:
(2)SRAM基本参数配置
这部分信息直接配置即可:
配置情况如下:
(3)配置时钟树
STM32F407ZGT6的最高主频到168M,使HCLK=168Mhz即可:
生成工程设置:
生成代码:
点击GENERATE CODE即可生成MDK-V5工程:
END
→点关注,不迷路←
文章引用微信公众号"嵌入式微处理器",如有侵权,请联系管理员删除!