目录
前言
1. SRAM控制原理
1.1 SRAM功能框图
1.2 SRAM读写时序
2. FSMC简介
2.1 FSMC架构
2.2 FSMC地址映射
2.3 FSMC控制SRAM时序
3. FSMC结构体
4. 库函数配置FSMC
5. 实验程序
5.1 main.c
5.2 SRAM.c
5.3 SRAM.h
前言
STM32F4自带了192K字节的SRAM;对于一般的应用来说,192K字节已经够用了,但是对于那些对内存要求比较高的,192K字节是远远不够用的;为此,STM32F4板载了一个1M字节的SRAM芯片:IS62WV51216,以满足大内存的需求。
IS62WV51216是ISSI(Integrated Silicon Solution Inc)公司生产的一颗16位宽512K(512*16,即1M字节(下面会做精确讲解计算,为什么是512*16,又为什么是1M字节))容量的CMOS静态内存(SRAM)芯片。
1. SRAM控制原理
STM32控制器芯片内部有一定大小的SRAM及FLASH作为内存和程序存储空间,但是当程序比较大时,内存和程序空间不足时,就需要STM32芯片的外部扩展存储器了。
实际生活中,不管是我们使用手机还是电脑,我们常常说的运行内存(8+128、12+256中的8G和12G)实际上是执行内存,而我们下载APP或者游戏所消耗的内存是存储内存;这是截然不同的。
给STM32芯片扩展内存与给PC电脑扩展内存的原理是一样的,只不过我们平时给电脑扩展内存一般以内存条的形式扩展,内存条实质上是由多个内存颗粒(即SDRAM芯片)组成的通用标准模块,而STM32扩展时,直接与SRAM芯片连接即可。
扩展内存的本质是:利用STM32的FSMC灵活静态存储器去驱动IS62WV51216芯片,进而达到扩展内存的目的;(STM32芯片追其根本还是起控制作用,真正其作用的是IS62WV51216芯片)
1.1 SRAM功能框图
SRAM芯片内部功能框图:
A0-A18是地址线,会连接到内部地址译码器,然后会发送给存储器矩阵(SRAM、FLASH和EEPROM在内部都是通过矩阵的形式进行存储的,一方面有利于指针的读写,另一方面也有利于擦除)
其中存储器矩阵是512K*16,单位是Bit,1个字节对应于8位;512*16=8192Bit;8192/8=1024字节,所以外部扩展Flash的大小是1M;
- A0-A18:输入引脚;地址输入 (设置指针寻址获取存储器矩阵中对应的数据)
- I/O0-I/O7:输入输出引脚;数据输入输出信号,低字节
- I/O8-I/O15:输入输出引脚;数据输入输出信号,高字节
- CS2和CS1#:输入引脚;片选信号,CS2高电平有效,CS1#低电平有效,部分芯片只要其中一个引脚
- OE#:输入引脚;输出使能信号,低电平有效
- WE#:输入引脚;写入使能,低电平有效
- UB#:输入引脚;数据掩码信号Upper Byte,高位字节允许访问,低电平有效
- LB#:输入引脚;数据掩码信号Lower Byte,低位字节允许访问,低电平有效
拓展:
数据掩码信号:将真正的客户数据转换成其他人都不能使用的完全伪造的数据,但是那些数据仍可以被用于应用程序测试。
简单来说就是:一种读写保护机制,因为写入芯片的程序对使用者来说都非常重要,为防止被他人窃取,所以引入数据掩码信号。
地址译码器的作用:
地址译码器是把 N 根地址线转换成 根信号线。每根信号线对应一行或者一列存储单元,通过地址线找到具体的存储单元,实现寻址。
美中不足的是:STM32F4的开发板中拓展的SRAM还比较小,只有1M,是没有列地址线的;它的数据宽度(因为存储器矩阵类似于我们使用的Excel,矩阵是由行和列组成的,那么每一行的长度称为数据宽度)是16位(一行能放2个字节,每个字节占8位);(其中:因为数据宽度是16位的,所以一行可以存放2个字节,每次写入一个字节,那么就需要分别写入这16位的高8位和低8位,那么就需要借助I/O0-I/O7、I/O8-I/O15数据线分别写入这16位的高字节和低字节)
A0-A18是地址信号线,18根地址线一共可以表示=*1024=512K行存储单元,所以总共可以访问 512K*16Bit 大小的空间。访问时,使用UB#或LB#线控制数据宽度。
UB#和LB#的作用:
因为存储器矩阵是通过地址线寻址进而得到对应的数据的;那么如果我们想要访问第二行的高8位,并且不想改变第二行的低8位,此时就需要借助于UB#和LB#,UB#是高位字节允许访问,低电平有效;LB#是低位字节允许访问,低电平有效;设置LB#等于1,那么低位字节就不允许访问,此时就无法访问第二行的低8位;在此基础上设置UB#=0,高位字节允许访问,此时就可以改变第二行高8位的数据。
控制电路:
控制电路主要包括片选信号CS、读写使能OE、WE以及宽度控制信号UB#和LB#。利用CS2和CS1#片选信号,可以把多个SRAM芯片组成一个大容量的内存条。OE#和WE#控制读写使能,防止误操作。
1.2 SRAM读写时序
SRAM读数据时序:
读时序的过程是:
首先通过A0-A18地址线发送一个地址,这个地址代表我们想要访问的SRAM对应地址上的数据,因为SRAM是通过寻址的方式找到对应数据的;紧接着读使能OE置低电平,因为读使能引脚OE低电平有效;接下来片选信号线CS1置低电平,片选信号线CS2置高电平,因为CS1低电平有效,CS2高电平有效;然后设置掩码信号LB#和UB#,如果我们想要访问一行16位,那么LB和UB均设置为低电平即可;如果只是想访问16位中高8位和低8位其中之1,那么按照需要对应设置掩码信号即可;最后通过数据线 I/O 将对应地址上的数据发送给 STM32 即可;这样就可以实现在SRAM上读取数据。
注: 图中橙色区域和黄色区域的含义是:
因为SRAM读取数据是异步通讯,也就是没有时钟总线,也可以说是双方没有规定好时间,那么我将要读取的地址发送以后,就需要给出一定的反应时间(因为没有时钟,不是同步通信),也就是图中程序区域的长度,整个程序区域长度表示整个读数据的时间,黄色区域表示在发送地址一定时间后,SRAM才会将对应地址上的数据返回给STM32(也就是说对应时间过去以后,我才会在黄色区域时间内把对应数据返回回去)
SRAM写数据时序:
写时序的过程是:
首先在整个黄色区域,也就是写时序周期内发送一个地址,该地址表示STM32想要往SRAM的哪个地址上写;确定写的地方以后,紧接着拉低片选信号线CS1,置高片选信号线CS2;接下来写使能WE置低,表示开始写数据到SRAM中;然后根据自己想要写的数据宽度是16位还是16位中的高8位还是低8位设置掩码信号;最后通过数据线将所要写入的数据写到对应的地址上;
和的作用:
因为是异步通信,没有时钟总线,所以我们默认在整个写时序的过程中,在把写地址发送给SRAM以后,等待一段时间(这段时间是黄色区域的前半部分),然后默认再等待+时间,就认为数据一定是写入了发送的地址上。
2. FSMC简介
首先我们需要明确STM32单片机只是起到了控制作用,也就是说MCU控制外设去实现单片机内部集成的某些功能。这里学习的FSMC就是其中一个外设,通过STM32控制FSMC就可以管理扩展的存储器SRAM。
FSMC:Flexible Static Memory Controller的缩写,译为灵活的静态存储器。FSMC可以用来驱动SRAM、NOR FLASH以及NAND FLASH类型的存储器,但是不可以驱动动态存储器SDRAM(动态存储器位于STM32F429中)。
2.1 FSMC架构
①:通讯引脚
通讯引脚和原理图上的SRAM模块的引脚是一一对应的:
- FSMC_NBL[1:0]:对应SRAM引脚为LB#和UB#;表示数据掩码信号
- FSMC_A[18:0]:对应SRAM引脚为地址线A[18:0];表示行地址线
- FSMC_D[15:0]:对应SRAM引脚为数据线I/0[15:0];表示数据线
- FSMC_NWE:对应SRAM引脚为WE#;表示写入使能
- FSMC_NOE:对应SRAM引脚为OE#;表示输出使能(读使能)
- FSMC_NE[1:4]:对应SRAM引脚为CE#;表示片选信号
②:存储器控制器
原理图中对应的所有引脚都是连接到对应的存储控制器中的。
NOR/PSRAM/SRAM设备使用相同的控制器,NAND/PC卡设备使用相同的控制器,不同的控制器有专用的寄存器用于配置其工作模式;
其中控制SRAM的由有FSMC_BCR1/2/3/4控制寄存器、FSMC_BTR1/2/3/4片选时序寄存器以及FSMC_BWTR1/2/3/4写时序寄存器;每种寄存器都有4个,对应于4个不同的存储区域;
FSMC_BCR控制寄存器可配置要控制的存储器类型、数据线宽度以及信号有效极性能参数。
FSMC_BTR时序寄存器用于配置SRAM访问时的各种时间延迟,如数据保持时间、地址保持时间等。
FSMC_BWTR写时序寄存器与FSMC_BTR控制的参数类似,专门用于控制写时序的时间参数。
③:时钟控制逻辑
FSMC外设挂载在AHB总线上,时钟信号来自于HCLK(默认168MHz),存储器控制器的同步时钟输出就是由它分频得到的。
2.2 FSMC地址映射
FSMC连接好外部的存储器(SRAM拓展芯片IS62WV51216)并初始化后,就可以直接通过访问地址来读写数据。
FSMC访问存储的方式不同于IIC EEPROM、SPI Flash,IIC和SPI都需要控制总线给存储器发送地址,然后获取数据;在程序里,地址和数据是需要定义不同的变量来存储的(我们的IIC、SPI程序中通常写字节函数都会定义第一个参数Address、第二个参数Data,表示往地址Address上写入数据Data),并且访问时还需要使用代码控制发送读写命令。
而FSMC外接存储器时,其存储单元是映射到STM32的内部寻址空间的;在程序里,定义一个指向这些地址的指针,就可以通过指针直接修改这些存储单元的内容。并且,FSMC外设会自动完成数据访问过程,读写命令之类的操作不需要程序控制。
2.3 FSMC控制SRAM时序
FSMC外设支持输出多种不同的时序以便于控制不同的存储器,它具有ABCD四种模式:
其中ADDSET表示地址设置阶段的持续时间:
- 0000:地址设置阶段的持续时间=0*HCLK时钟周期(168MHz)
- 0001:地址设置阶段的持续时间=1*HCLK时钟周期(168MHz)
- ……
- 1111:地址设置阶段的持续时间=1615*HCLK时钟周期(168MHz)(复位后的默认值)
DATAST表示数据阶段的持续时间:
- 00000000:Reserved
- 00000001:DATAST阶段的持续时间=1*HCLK时钟周期
- 00000010:DATAST阶段的持续时间=2*HCLK时钟周期
- ……
- 11111111:DATAST阶段的持续时间=255*HCLK时钟周期(复位后的默认值)
以上的两个时间均通过FSMC片选时序寄存器的相应位进行设置;
3. FSMC结构体
//FSMC时序结构体
//一个HCLK周期为1/168微秒
typedef struct
{
uint32_t FSMC_AddressSetupTime; //地址建立时间,0-0xF个HCLK周期
//通过时序寄存器进行设置
//0000:地址建立持续时间=0*HCLK时钟周期
//1111:地址建立持续时间=15*HCLK时钟周期
uint32_t FSMC_AddressHoldTime; //地址保持时间,0-0xF个HCLK周期
//通过时序寄存器设置
//0000:保留
//0001:保持时间=1*HCLK时钟周期
//1111:保持时间=15*HCLK时钟周期
uint32_t FSMC_DataSetupTime; //数据建立时间,0-0xF个HCLK周期
//通过时序寄存器设置
//0000 0000:保留
//0000 0001:持续时间=1*HCLK时钟周期
//1111 1111:持续时间=255*HCLK时钟周期
uint32_t FSMC_BusTurnAroundDuration; //总线转换周期,0-0xF个HCLK周期
//通过时序寄存器BTR/BWTR配置
//0000:增加0个HCLK时钟周期
//1111:增加15个HCLK时钟周期
uint32_t FSMC_CLKDivision; //时钟分频因子,1-0xF
//时序寄存器BTR位[23:20]设置
//0000:Reserved
//0001:CLK周期=2*HCLK周期
//0010:CLK周期=3*HCLK周期
//1111:CLK周期=16*HCLK周期
uint32_t FSMC_DataLatency; //数据延迟时间
//时序寄存器BTR/BWTR位[18:15]设置数据延迟时间
//0000 0000:Reserved
//0000 0001:延迟时间=1*HCLK(168MHz)
//0000 0010:延迟时间=2*HCLK(168MHz)以此类推
//1111 1111:延迟时间=255*HCLK(168MHz)
uint32_t FSMC_AccessMode; //设置访问模式
//时序寄存器BTR/BWTR位[29:28]设置访问模式
//00:访问模式A
//01:访问模式B
//10:访问模式C
//11:访问模式D
}FSMC_NORSRAMTimingInitTypeDef;
//FSMC初始化结构体
//除了最后两个成员是时序寄存器配置以外,其余的成员都是FSMC_BCR控制寄存器配置的
typedef struct
{
uint32_t FSMC_Bank; //设置要控制的Bank区域 选择FSMC映射的存储区域,SRAM不需要通过寻址,STM32内部是有其映射地址的,只需要通过指针即可访问内部存储区域
//FSMC_Bank1_NORSRAM1 0x6000 0000~0x63FF FFFF
//FSMC_Bank1_NORSRAM2 0x6400 0000~0x67FF FFFF
//FSMC_Bank1_NORSRAM3 0x6800 0000~0x6BFF FFFF
//FSMC_Bank1_NORSRAM4 0x6C00 0000~0x6FFF FFFF
//原理图上对应的引脚标明:FSMC_NE3 所以在此选择FSMC_Bank1_NORSRAM3 对应的地址
uint32_t FSMC_DataAddressMux; //设置地址总线和数据总线是否复用
//FSMC_DataAddressMux_Enable/Disable
//在控制NOR FLASH时,可以地址总线和数据总线分时复用,减少STM32信号线的数量
uint32_t FSMC_MemoryType; //设置存储器的类型
//它支持控制的存储器类型为SRAM、PSRAM、NOR
//FSMC_MemoryType_SRAM/PSRAM/NOR
uint32_t FSMC_MemoryDataWidth; //设置存储器的数据宽度 可以选择设置为8或者16位
//FSMC_MemoryDataWidth_8b/16b
uint32_t FSMC_BurstAccessMode; //设置是否支持突发访问模式
//FSMC_BurstAccessMode_Enable/Disable
//突发访问模式是指发送一个地址后连续访问多个数据
//非突发模式下每访问一个数据都需要输入一个地址
//控制同步类型的存储器时才能使用突发模式(SRAM是异步类型的存储器)
uint32_t FSMC_AsynchronousWait; //设置是否使能在同步传输时的等待信号
//FSMC_AsynchronousWait_Enable/Disable
//在控制同步类型的NOR或PSRAM时,存储器可以使用FSMC_NWAIT引脚通知STM32需要等待
uint32_t FSMC_WaitSignalPolarity; //设置等待信号的极性 要求等待时,使用高电平还是低电平
//FSMC_WaitSignalPolarity_High/Low;
uint32_t FSMC_WrapMode; //设置是否支持对齐的突发模式
//是否支持把非对齐的AHB突发模式分割成2次线性操作
//FSMC_WrapMode_Enable/Disable
uint32_t FSMC_WaitSignalActive; //配置等待信号是在等待前有效还是等待期间有效
//决定存储器是在等待状态之前的一个数据周期有效还是在等待状态期间有效
//FSMC_WaitSignalActive_BeforeWaitState/DuringWaitState
uint32_t FSMC_WriteOperation; //设置是否写使能
//FSMC_WriteOperation_Enable/Disable
uint32_t FSMC_WaitSignal; //设置是否使能等待状态插入
//设置当存储器突发传输模式时,是否允许通过NWAIT信号插入等待状态
//FSMC_WaitSignal_Enable/Disable
uint32_t FSMC_ExtendedMode; //设置是否使能扩展模式
//FSMC_ExtendedMode_Enable/Disable
uint32_t FSMC_WriteBurst; //设置是否使能写突发操作
//FSMC_WriteBurst_Enable/Disable
//当不使用扩展模式时,本参数用于配置读写时序,否则用于配置读时序
FSMC_NORSRAMTimingInitTypeDef* FSMC_ReadWriteTimingStruct;
//当使用扩展模式时,本参数用于配置写时序
//FSMC_NORSRAMTimingInitTypeDef* FSMC_WriteTimingStruct;
}FSMC_NORSRAMInitTypeDef;
4. 库函数配置FSMC
1. 使能FSMC时钟,配置FSMC相关的IO及其时钟使能
要使用FSMC,首先要开启其时钟。然后需要把FSMC_D0~D15,FSMC_A0~A18相关的IO口全部配置为复用功能,使能各个IO组的时钟
RCC_AHB3PeriphClockCmd(RCC_AHB3Periph_FSMC,ENABLE); //使能 FSMC 时钟
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; //复用输出
void GPIO_PinAFConfig(GPIO_TypeDef* GPIOx, uint16_t GPIO_PinSource, uint8_t GPIO_AF); //设置引脚映射
2. 设置FSMC BANK1 区域3的相关寄存器
void FSMC_NORSRAMInit(FSMC_NORSRAMInitTypeDef* FSMC_NORSRAMInitStruct); //调用结构体设置FSMC控制寄存器的工作模式、位宽和读写时序
3. 使能BANK1 区域3
FSMC_NORSRAMCmd(FSMC_Bank1_NORSRAM3, ENABLE); // 使能 BANK1 区域3
5. 实验程序
实验现象:
开机后,显示提示信息,然后按下KEY0按键,测试外部SRAM容量大小并显示在LCD上。
按下KEY1按键,显示预存在外部的SRAM数据。
5.1 main.c
#include "stm32f4xx.h"
#include "delay.h"
#include "usart.h"
#include "LED.h"
#include "lcd.h"
#include "Key.h"
#include "usmart.h"
#include "SRAM.h"
//LCD状态设置函数
void led_set(u8 sta)//只要工程目录下有usmart调试函数,主函数就必须调用这两个函数
{
LED1=sta;
}
//函数参数调用测试函数
void test_fun(void(*ledset)(u8),u8 sta)
{
led_set(sta);
}
u32 TestSRAM[250000] __attribute__((at(0x68000000))); //测试用数组
//定义一个局部变量 该数组是用来测试的数组
//__attribute__((at(0x68000000)))这个是用来指定变量或结构位域的特殊属性,该关键字后的双括弧中的内容是属性说明
//at是关键字,该关键字用来设置变量的绝对地址,也就是说通过该关键字可以指定某个变量处于内存里面的某个给定的地址
//综合起来就是:设置变量处于0x68000000这个地址
//外部内存测试,最大支持1M字节内存测试
void fsmc_sram_test(u16 x,u16 y)
{
u32 i=0;
u8 temp=0;
u8 sval=0; //在地址0读到的数据
LCD_ShowString(x,y,239,y+16,16,"Ex Memory Test: 0KB"); //提示信息 239,y+16是指区域大小
//每隔4K字节,写入一个数据,总共写入256个数据,正好是1M字节,用来测试SRAM内存
for(i=0;i<1024*1024;i=i+4096) //1M=1024KB,1KB=1024字节,4096字节就是4KB i=i+4096表示for每循环一次,就跳过4K字节
{
//i是在1024*1024的存储器矩阵中进行循环的
FSMC_SRAM_WriteBuffer(&temp,i,1);//调用在指定地址上写入n个字节函数
//&temp,取地址变量temp,表示拿到了temp变量的存储首地址;指针指向temp首地址,表示从这个地址开始写
//i表示要写入的值,i在存储器矩阵中递增,SRAM写是不需要软件参与的,只要指针能指向这个i对应的地址,就能对该地址进行写操作
//每次写入1个字节
temp++;
}
//依次读出之前写入的数据,进行校验
for(i=0;i<1024*1024;i=i+4096)
{
FSMC_SRAM_ReadBuffer(&temp,i,1);
if(i==0) //表示在地址0上读出的数据
{
sval=temp; //把地址0上读到的数据给到sval
}
else if(temp<=sval) //else if 此时i不再是0,也就表示之前已经写入数据了,因为i在存储器矩阵中是递增的,所以只要后面读取的值小于地址0上的值,就break结束
{
break; //后面读出的数据一定要比第一次读到的数据大
}
LCD_ShowxNum(x+15*8,y,(u16)(temp-sval+1)*4,4,16,0); //显示内容容量
//第三个参数表示要写入的数值 第四个参数表示长度
//u16 表示unsigned int 表示强制类型转换为int型
//temp是最后一次读取到值;sval是位置0上读取的值,temp-sval+1表示在整个存储器矩阵读到了多少个数,每个4K写一次,那么读到的数也是每隔4K的
//(temp-sval+1)*4 表示总的数据长度,也就是内存容量
}
}
int main(void)
{
u8 key;
u8 i=0;
u32 j=0;
delay_init(168);
uart_init(115200);
LED_Init();
LCD_Init();
Key_Init();
FSMC_SRAM_Init();
POINT_COLOR=RED;
LCD_ShowString(30,50,200,16,16,"Explorer STM32F4");
LCD_ShowString(30,70,200,16,16,"SRAM Test");
LCD_ShowString(30,90,200,16,16,"ATOM@ALIENTEK");
LCD_ShowString(30,110,200,16,16,"2023/20/23");
LCD_ShowString(30,130,200,16,16,"KEY0:Test Sram");
LCD_ShowString(30,150,200,16,16,"KEY1:Test Data");
POINT_COLOR=BLUE; //设置字体为蓝色
for(j=0;j<250000;j++)
{
TestSRAM[j]=j; //预存测试数据
}
// 可以借助该程序在串口查看数据
// while(1)
// {
// printf("\r\nSRAM中的数据是:\r\n");
// for(j=0;j<250000;j++)
// {
// LED0=!LED0;
// printf("%d ",TestSRAM[j]);
// }
// }
while(1)
{
key=KEY_Scan(0);
if(key==1) //KEY0按下
{
fsmc_sram_test(60,170); //测试SRAM容量
}
else if(key==2) //KEY1按下 打印预存测试数据
{
for(j=0;j<250000;j++)
{
LCD_ShowxNum(60,190,TestSRAM[j],6,16,0); //显示测试数据
}
}
else
{
delay_ms(10);
}
i++;
if(i==20)
{
i=0;
LED0=!LED0;
}
}
}
5.2 SRAM.c
#include "stm32f4xx.h"
#include "SRAM.h"
//使用NOR/SRAM的 Bank1.sector3,地址位HADDR[27,26]=10
//00:sector1
//01:sector2
//10:sector3
//11:sector4
//对IS62WV51216来说,地址线范围为A0~A18
#define Bank1_SRAM3_ADDRESS ((u32)(0x68000000)) //Bank1_SRAM3的起始地址为0x68000000
//初始化外部SRAM
void FSMC_SRAM_Init(void)
{
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB|RCC_AHB1Periph_GPIOD|RCC_AHB1Periph_GPIOE|RCC_AHB1Periph_GPIOF|RCC_AHB1Periph_GPIOG,ENABLE); //使能B D E F G时钟
RCC_AHB3PeriphClockCmd(RCC_AHB3Periph_FSMC,ENABLE); //使能FSMC时钟
//PB15
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_OUT;
GPIO_InitStructure.GPIO_OType=GPIO_OType_PP; //推挽输出
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_15; //PB15 推挽输出 控制背光
GPIO_InitStructure.GPIO_PuPd=GPIO_PuPd_UP;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOB,&GPIO_InitStructure);
//PD0 PD1 PD4 PD5 PD8~15
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF;
GPIO_InitStructure.GPIO_OType=GPIO_OType_PP; //推挽输出
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_4|GPIO_Pin_5|GPIO_Pin_8|GPIO_Pin_9|GPIO_Pin_10|GPIO_Pin_11|GPIO_Pin_12|GPIO_Pin_13|GPIO_Pin_14|GPIO_Pin_15;
GPIO_InitStructure.GPIO_PuPd=GPIO_PuPd_UP;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_100MHz;
GPIO_Init(GPIOD,&GPIO_InitStructure);
//PE0 PE1 PE7~15
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF; //引脚模式设置为复用,后续将引脚复用为FSMC
GPIO_InitStructure.GPIO_OType=GPIO_OType_PP; //推挽输出
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_7|GPIO_Pin_8|GPIO_Pin_9|GPIO_Pin_10|GPIO_Pin_11|GPIO_Pin_12|GPIO_Pin_13|GPIO_Pin_14|GPIO_Pin_15;
GPIO_InitStructure.GPIO_PuPd=GPIO_PuPd_UP;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_100MHz;
GPIO_Init(GPIOE,&GPIO_InitStructure);
//PF0~5 PF12~15
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF;
GPIO_InitStructure.GPIO_OType=GPIO_OType_PP; //推挽输出
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3|GPIO_Pin_4|GPIO_Pin_5|GPIO_Pin_12|GPIO_Pin_13|GPIO_Pin_14|GPIO_Pin_15;
GPIO_InitStructure.GPIO_PuPd=GPIO_PuPd_UP;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_100MHz;
GPIO_Init(GPIOF,&GPIO_InitStructure);
//PG0~5 PG10
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF;
GPIO_InitStructure.GPIO_OType=GPIO_OType_PP; //推挽输出
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3|GPIO_Pin_4|GPIO_Pin_5|GPIO_Pin_10;
GPIO_InitStructure.GPIO_PuPd=GPIO_PuPd_UP;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_100MHz;
GPIO_Init(GPIOG,&GPIO_InitStructure);
//将所有复用功能的引脚设置为 复用FSMC
GPIO_PinAFConfig(GPIOD,GPIO_PinSource0,GPIO_AF_FSMC); //PD0复用为FSMC
GPIO_PinAFConfig(GPIOD,GPIO_PinSource1,GPIO_AF_FSMC); //PD1复用为FSMC
GPIO_PinAFConfig(GPIOD,GPIO_PinSource4,GPIO_AF_FSMC); //PD4复用为FSMC
GPIO_PinAFConfig(GPIOD,GPIO_PinSource5,GPIO_AF_FSMC); //PD5复用为FSMC
GPIO_PinAFConfig(GPIOD,GPIO_PinSource8,GPIO_AF_FSMC); //PD8复用为FSMC
GPIO_PinAFConfig(GPIOD,GPIO_PinSource9,GPIO_AF_FSMC); //PD9复用为FSMC
GPIO_PinAFConfig(GPIOD,GPIO_PinSource10,GPIO_AF_FSMC); //PD10复用为FSMC
GPIO_PinAFConfig(GPIOD,GPIO_PinSource11,GPIO_AF_FSMC); //PD11复用为FSMC
GPIO_PinAFConfig(GPIOD,GPIO_PinSource12,GPIO_AF_FSMC); //PD12复用为FSMC
GPIO_PinAFConfig(GPIOD,GPIO_PinSource13,GPIO_AF_FSMC); //PD13复用为FSMC
GPIO_PinAFConfig(GPIOD,GPIO_PinSource14,GPIO_AF_FSMC); //PD14复用为FSMC
GPIO_PinAFConfig(GPIOD,GPIO_PinSource15,GPIO_AF_FSMC); //PD15复用为FSMC
GPIO_PinAFConfig(GPIOE,GPIO_PinSource0,GPIO_AF_FSMC); //PE0复用为FSMC
GPIO_PinAFConfig(GPIOE,GPIO_PinSource1,GPIO_AF_FSMC); //PE1复用为FSMC
GPIO_PinAFConfig(GPIOE,GPIO_PinSource7,GPIO_AF_FSMC); //PE7复用为FSMC
GPIO_PinAFConfig(GPIOE,GPIO_PinSource8,GPIO_AF_FSMC); //PE8复用为FSMC
GPIO_PinAFConfig(GPIOE,GPIO_PinSource9,GPIO_AF_FSMC); //PE9复用为FSMC
GPIO_PinAFConfig(GPIOE,GPIO_PinSource10,GPIO_AF_FSMC); //PE10复用为FSMC
GPIO_PinAFConfig(GPIOE,GPIO_PinSource11,GPIO_AF_FSMC); //PE11复用为FSMC
GPIO_PinAFConfig(GPIOE,GPIO_PinSource12,GPIO_AF_FSMC); //PE12复用为FSMC
GPIO_PinAFConfig(GPIOE,GPIO_PinSource13,GPIO_AF_FSMC); //PE13复用为FSMC
GPIO_PinAFConfig(GPIOE,GPIO_PinSource14,GPIO_AF_FSMC); //PE14复用为FSMC
GPIO_PinAFConfig(GPIOE,GPIO_PinSource15,GPIO_AF_FSMC); //PE15复用为FSMC
GPIO_PinAFConfig(GPIOF,GPIO_PinSource0,GPIO_AF_FSMC); //PF0复用为FSMC
GPIO_PinAFConfig(GPIOF,GPIO_PinSource1,GPIO_AF_FSMC); //PF1复用为FSMC
GPIO_PinAFConfig(GPIOF,GPIO_PinSource2,GPIO_AF_FSMC); //PF2复用为FSMC
GPIO_PinAFConfig(GPIOF,GPIO_PinSource3,GPIO_AF_FSMC); //PF3复用为FSMC
GPIO_PinAFConfig(GPIOF,GPIO_PinSource4,GPIO_AF_FSMC); //PF4复用为FSMC
GPIO_PinAFConfig(GPIOF,GPIO_PinSource5,GPIO_AF_FSMC); //PF5复用为FSMC
GPIO_PinAFConfig(GPIOF,GPIO_PinSource12,GPIO_AF_FSMC); //PF12复用为FSMC
GPIO_PinAFConfig(GPIOF,GPIO_PinSource13,GPIO_AF_FSMC); //PF13复用为FSMC
GPIO_PinAFConfig(GPIOF,GPIO_PinSource14,GPIO_AF_FSMC); //PF14复用为FSMC
GPIO_PinAFConfig(GPIOF,GPIO_PinSource15,GPIO_AF_FSMC); //PF15复用为FSMC
GPIO_PinAFConfig(GPIOG,GPIO_PinSource0,GPIO_AF_FSMC);
GPIO_PinAFConfig(GPIOG,GPIO_PinSource1,GPIO_AF_FSMC);
GPIO_PinAFConfig(GPIOG,GPIO_PinSource2,GPIO_AF_FSMC);
GPIO_PinAFConfig(GPIOG,GPIO_PinSource3,GPIO_AF_FSMC);
GPIO_PinAFConfig(GPIOG,GPIO_PinSource4,GPIO_AF_FSMC);
GPIO_PinAFConfig(GPIOG,GPIO_PinSource5,GPIO_AF_FSMC);
GPIO_PinAFConfig(GPIOG,GPIO_PinSource10,GPIO_AF_FSMC);
FSMC_NORSRAMTimingInitTypeDef FSMC_NORSRAMTimingInitStructure; //调用STM32库函数配置的结构体配置时序寄存器
FSMC_NORSRAMTimingInitStructure.FSMC_AccessMode=FSMC_AccessMode_A; //设置访问模式为模式A
FSMC_NORSRAMTimingInitStructure.FSMC_AddressHoldTime=0x00; //地址保持时间,模式A未用到
FSMC_NORSRAMTimingInitStructure.FSMC_AddressSetupTime=0x00; //地址建立时间(ADDSET)为1个HCLK 1/36M=27ns
FSMC_NORSRAMTimingInitStructure.FSMC_BusTurnAroundDuration=0x00; //总线转换周期 0000:增加0个HCLK时钟周期
FSMC_NORSRAMTimingInitStructure.FSMC_CLKDivision=0x00; //时钟分频因子 0000 0000:保留
FSMC_NORSRAMTimingInitStructure.FSMC_DataLatency=0x00; //数据延迟时间 0000 0000:保留
FSMC_NORSRAMTimingInitStructure.FSMC_DataSetupTime=0x08; //数据保持时间(DATAST)为9个HCLK 6*9=54ns
//调用STM32库函数配置的结构体配置FSMC_BCR控制寄存器
FSMC_NORSRAMInitTypeDef FSMC_NORSRAMInitStructure;
FSMC_NORSRAMInitStructure.FSMC_AsynchronousWait=
FSMC_NORSRAMInitStructure.FSMC_Bank=FSMC_Bank1_NORSRAM3; //Bank1区域3
FSMC_NORSRAMInitStructure.FSMC_BurstAccessMode=FSMC_BurstAccessMode_Disable; //不支持突发访问模式
FSMC_NORSRAMInitStructure.FSMC_DataAddressMux=FSMC_DataAddressMux_Disable; //地址总线和数据总线不复用
FSMC_NORSRAMInitStructure.FSMC_ExtendedMode=FSMC_ExtendedMode_Disable; //不使能扩展模式,读写使用相同的时序
FSMC_NORSRAMInitStructure.FSMC_MemoryDataWidth=FSMC_MemoryDataWidth_16b; //存储器的数据宽度为16位
FSMC_NORSRAMInitStructure.FSMC_MemoryType=FSMC_MemoryType_SRAM; //选择存储器种类为SRAM
FSMC_NORSRAMInitStructure.FSMC_ReadWriteTimingStruct=&FSMC_NORSRAMTimingInitStructure; //不使用扩展模式时,配置读写时序
FSMC_NORSRAMInitStructure.FSMC_WaitSignal=FSMC_WaitSignal_Disable; //不使能等待状态插入
FSMC_NORSRAMInitStructure.FSMC_WaitSignalActive=FSMC_WaitSignalActive_BeforeWaitState; //配置等待信号在等待前有效
FSMC_NORSRAMInitStructure.FSMC_WaitSignalPolarity=FSMC_WaitSignalPolarity_Low; //要求等待时,设置为低电平
FSMC_NORSRAMInitStructure.FSMC_WrapMode=FSMC_WrapMode_Disable; //不支持对齐的突发模式
FSMC_NORSRAMInitStructure.FSMC_WriteBurst=FSMC_WriteBurst_Disable; //不使能写突发操作
FSMC_NORSRAMInitStructure.FSMC_WriteOperation=FSMC_WriteOperation_Enable; //存储器写使能
FSMC_NORSRAMInitStructure.FSMC_WriteTimingStruct=&FSMC_NORSRAMTimingInitStructure; //扩展模式时,本参数用于配置写时序
FSMC_NORSRAMInit(&FSMC_NORSRAMInitStructure);
FSMC_NORSRAMCmd(FSMC_Bank1_NORSRAM3,ENABLE); //使能Bank1 区域3
}
//在指定地址(WriteAddress+Bank1_SRAM3_ADDRESS)上开始,连续写入n个字节
//pBuffer:字节指针
//WriteAddress:要写入的地址
//n:要写入的字节数
void FSMC_SRAM_WriteBuffer(u8* pBuffer,u32 WriteAddress,u32 n) //在SRAM上写字节是不需要软件来操作的,因为地址是在STM32上映射的
//所有只需要给出地址,通过指针就可以实现读写操作,读写操作通过硬件来完成,无需软件操作来参与
{
for(;n!=0;n--) //通过for循环将n个字节写到SRAM,每循环一次,要写入的字节就少一个
{
*(vu8*)(Bank1_SRAM3_ADDRESS+WriteAddress)=*pBuffer;
//该代码的含义是:Bank1_SRAM3_ADDRESS是Bank1区域3的起始地址+WriteAddress要写入的地址,表示首先要将指针指向我们要写入的地址上
//(vu8*)(Bank1_SRAM3_ADDRESS+WriteAddress)表示强制类型转换成vu8的指针,然后解引用拿到该指针指向地址上的数据
//将*pBuffer指针指向这个数据,硬件自动写入数据
WriteAddress++;//要写入的地址加1
pBuffer++;//指针指向下一个地址
}
//这里注意:
//假设WriteAddress要写入的地址是0,也就是说从SRAM起始地址开始写,起始地址是0x68000000;写入的地址是偶数,那么地址线最低位A0一定是0,因为SRAM是通过地址线发送要访问的地址的
//此时LB#和UB#掩码信号中LB有效,所以LB=0,写入数据D0~D7有效;D8~D15无效,UB=1;
//继续写地址1的时候,写入的地址就变成了0x68000001,此时LB=1,无效;UB=0,有效;因此数据写入高8位地址
//UB和LB与要写入地址的最低位有关
//如果要写入的数据是16位的,那么UB和LB都要有效,所以UB和LB都是0;
//如果地址是奇数,那么1个16位的数据需要分两次去写,因为强制类型转换把要写入的地址转换成了8位
//所以需要分别去写地址的高8位和低8位;
//这样写两次相对于写一次,速度就会减半;
}
//在指定地址(ReadAddress+Bank1_SRAM3_ADDRESS)上开始,连续读出n个字节
//pBuffer:字节指针
//ReadAddress:要读出的起始地址
//n:要写入的字节数
void FSMC_SRAM_ReadBuffer(u8* pBuffer,u32 ReadAddress,u32 n)
{
for(;n!=0;n--) //循环n次,从SRAM中读出n个字节 同理,读字节也是硬件来完成的,无需软件操作
{
*pBuffer++=*(vu8*)(Bank1_SRAM3_ADDRESS+ReadAddress); //解引用拿到指针指向地址上的数据,字节指针指向这个数据
//通过硬件循环读取该数据,指针后置++
ReadAddress++;//对应地址++
}
}
//测试函数
//在指定地址写入1个字节
//Address:地址
//Data:要写入的数据
void fsmc_sram_test_write(u32 Address,u8 Data)
{
FSMC_SRAM_WriteBuffer(&Data,Address,1); //第一个参数字节指针,指向要写入数据的首元素地址;第二个参数是要写入的地址;第三个参数是要写入的字节个数
//写入一个字节
}
//读取一个字节
//Address:要读取的地址
//返回值:读取到的数据
u8 fsmc_sram_test_read(u32 Address)
{
u8 Data;
FSMC_SRAM_ReadBuffer(&Data,Address,1); //读取一个字节
return Data;
}
5.3 SRAM.h
#ifndef _SRAM__H_
#define _SRAM__H_
#include "sys.h"
void FSMC_SRAM_Init(void);
void FSMC_SRAM_WriteBuffer(u8* pBuffer,u32 WriteAddress,u32 n);
void FSMC_SRAM_ReadBuffer(u8* pBuffer,u32 ReadAddress,u32 n);
void fsmc_sram_test_write(u32 addr,u8 data);
u8 fsmc_sram_test_read(u32 addr);
#endif