1、FMC接口介绍
FMC 接口(即可变存储控制器)是一种用于管理外部存储器的外设接口,支持多种类型的存储器,主要分为三大类:NOR/SRAM/PSRAM设备(TFTLCD相当于SRAM)、NOR FLASH/NAND FLASH/PC卡设备和SDRAM 设备。他们共用地址数据总线等信号,他们具有不同的CS以区分不同的设备。FMC 的框图如下图所示,从图中可以看到FMC接口中那些信号接口是所有设备共享,哪些信号接口是某些类型设备独享。
FMC接口一共管理1.5GB空间,拥有6个存储块(Bank),每个存储块256MB空间,不同类型的设备占用不同的Bank,它们的地址映射如下表。
FMC地址映射表 | |||
Bank号 | 大小 | 地址映射 | 设备类型 |
Bank1 | 4*64MB | 0X6000 0000 - 0X6FFF FFFF | NOR/SRAM/PSRAM设备 |
Bank2 | 4*64MB | 0X7000 0000 - 0X7FFF FFFF | NOR FLASH设备/ NAND FLASH设备 |
Bank3 | 4*64MB | 0X8000 0000 - 0X8FFF FFFF | |
Bank4 | 4*64MB | 0X9000 0000 - 0X9FFF FFFF | PC卡设备 |
Bank5 | 4*64MB | 0XC000 0000 - 0XCFFF FFFF | SDRAM设备 |
Bank6 | 4*64MB | 0XD000 0000 - 0XDFFF FFFF |
NOR/SRAM/PSRAM设备占用的是FMC接口的Bank1,它们内部再次被分割成了4个区,每个区64M的空间,通过FMC_NE1、FMC_NE2、FMC_NE3、FMC_NE4这4个脚来确定使用的是哪个区。
SDRAM设备占用的是Bank5和Bank6,通过(SDNE0、SDCKE0)和(SDNE1、SDCKCKE1)两组引脚来确定使用的是哪个Bank。
NAND FLASH设备占用的是Bank2和Bank3,通过NCE2和NCE3两个脚来确定使用的是哪个Bank。
2、使用FMC接口驱动TFT LCD
2.1、FCM接口驱动TFT LCD原理
使用FMC解接口驱动TFT LCD时实际是把LCD当成SRAM设备使用。
SRAM设备的控制有以下接口:地址线( A0~A25最多)、数据线(如 D0~D31最多)、写信号(WE)、读信号(OE)、片选信号(CS),如果 SRAM支持字节控制,那么还有 UB/LB信号。
TFT LCD设备的控制有以下接口:RS(命令/数据标志:0,读写命令;1,读写数据)、D0~D15(16 位双向数据线)、WR(向 TFTLCD 写入数据标志)、RD(从 TFTLCD 读取数据标志)、CS(片选)、RST(硬件复位) 和 BL(背光控制)。
通过SRAM设备和TFT LCD设备控制接口对比可以发现,两者控制方式完全类似,唯一不同的是TFT LCD 有 RS 信号,但是没有地址信号。TFT LCD 通过RS信号来决定传送的数据是数据还是命令,本质上可以理解为一个地址信号,比如我们把RS接在A0上面,那么当FMC控制器写地址0的时候会使得A0变为0,对TFT LCD来说就是写命令。而FMC 写地址1的时候,A0将会变为1,对TFT LCD 来说就是写数据了。这样就把数据和命令区分开了。他们其实就是对应 SRAM 操作的两个连续地址,当然 RS 也可以接在其他地址线上,正点原子的开发板把 RS 连接在 A18 上面的。
2.2、SRAM设备的寻址和FMC访问SRAM设备时的地址偏移
SRAM设备占用的是FMC接口的Bank1,它内部再次被分割成了4个区,每个区64M的空间,通过单片机内部的AHB地址总线HADDR[27:0]进行寻址,其中AHB地址总线HADDR[25:0]对应FMC的地址线 A0~A25,而HADDR[27:26]则由片选信号决定,从FMC_NE1到FMC_NE4对应的HADDR[27:26]分别是:00 01 10 11。
由于单片机内部的AHB地址总线HADDR[27:0]是按字节寻址,而FMC外接的存储器按字寻址,所以根据存储器数据宽度的不同,单片机通过FMC访问外部存储器时发送的地址也不同,地址的偏移情况与存储器数据宽度的关系如下:
因此,FMC 内部 HADDR 与存储器寻址地址的实际对应关系就是:
当接的是 32 位宽度存储器的时候:HADDR[25:2]→ FMC_A [23:0]。
当接的是 16 位宽度存储器的时候:HADDR[25:1]→ FMC_A [24:0]。
当接的是 8 位宽度存储器的时候:HADDR[25:0]→ FMC_A [25:0]。
注意:这里的偏移只是FMC访问外部存储器时的地址偏移(软件上的偏移),但硬件连接时FMC的地址线A0到A25还是要和存储器的A0到A25对应连接。
正点原子的TFT LCD都是16位的数据宽度,所以单片机内部的AHB地址总线的HADDR[0]没有用到,只有HADDR[25:1]是有效的,对应关系变为:HADDR[25:1]→ FMC_A[24:0],相当于右移了一位。
2.3、FMC给LCD发送数据时的地址计算
在了解了SRAM设备的寻址方式和FMC访问SRAM设备时的地址偏移以后接下来就是要知道FMC给LCD发送数据时的地址计算了。
首先是确定SRAM的基地址。当我们决定了接哪一个片选信号时,那就决定了用的是Bank1的哪一个区,也就决定了SRAM的基地址。每个区的基地址都不一样,计算公式为[ 0x6000 0000 + (X-1)*0x400 0000](X表示哪一个区)。
接下来就是根据RS脚确定给LCD发送命令/数据时的地址了,RS=0是命令;RS=1是数据。正点原子F429开发板中RS接的是A18,所以当A18脚为0时发送的是命令,为1时发送的是数据。由于FMC是并行通信,所以当地址线为0时表示发送的是命令,当地址线为0x80000时表示发送的是数据。计算公式为:数据地址=[2^(y+1)](y表示RS脚接的地址线的编号,比如此时接的A18,则数据地址等于0x80000)。
经过上述计算得到FMC给LCD发送数据的地址:0x6008 0000,即&LCD_RAM = 0x6008 0000,而FMC给LCD时的地址只要不等于给LCD发命令的地址就行。为了方便,通常会使用一个 LCD 地址结构体进行管理:
typedef struct
{
volatile uint16_t LCD_REG;
volatile uint16_t LCD_RAM;
} LCD_TypeDef;
LCD_TypeDef LCD={ NULL };
通过上述计算的得知,LCD结构体中LCD_RAM的地址为0x6008 0000,所以LCD的首地址就是(0x6008 0000 - 2 = 0x6007 FFFE),所以定义时LCD结构体的地址就需要手动指定,上述语句需要写成:LCD_TypeDef LCD __attribute__((at(0x6007 FFFE))) = { NULL };
除了这种方法外,正点原子还提供了一种结构体指针的管理方式:
#define LCD ((LCD_TypeDef *) 0x6007 FFFE);
声明LCD是一个LCD_TypeDef类型的指针,并且指向0x6007 FFFE这个地址,所以结构体指针中的变量LCD->LCD_RAM的地址就是0x6008 0000,一样的效果。
2.4、NOR/SRAM/PSRAM设备的时序
NOR/SRAM/PSRAM设备主要是通过FMC_BCRx、FMC_BTRx和 FMC_BWTRx 寄存器设置访问外部存储器的时序参数(其中 x=1~4,对应 4 个区)。对于该类型的设备,FMC
控制器支持同步和异步突发两种访问方式。选用同步突发访问方式时,FMC 将 HCLK(系统时钟)分频后,发送给外部存储器作为同步时钟信号 FMC_CLK。此时需要的设置的时间参数有 2 个:
1,HCLK 与 FMC_CLK 的分频系数(CLKDIV),可以为 2~16 分频;
2,同步突发访问中获得第 1 个数据所需要的等待延迟(DATLAT)。
对于异步突发访问方式,FMC 主要设置 3 个时间参数:地址建立时间(ADDSET)、数据建立时间(DATAST)和地址保持时间(ADDHLD)。FMC综合了 SRAM、PSRAM和 NOR Flash产品的信号特点,定义了 4 种不同的异步时序模型,选用不同的时序模型时,需要设置不同的时序参数,如下表:
在驱动TFT LCD时我们采用异步模式 A(ModeA)时序,这种时序支持独立的读写时序控制,这个特点在驱动 TFTLCD时非常有用。因为TFTLCD在读的时候一般比较慢,而在写的时候可以比较快。如果读写用一样的时序,那么只能以读的时序为基准,从而导致写的速度变慢;或者在读数据的时候,重新配置 FMC 的延时,在读操作完成的时候,再配置回写的时序,这样虽然也不会降低写的速度,但是频繁配置,比较麻烦。而如果有独立的读写时序控制,那么我们只要初始化的时候配置好,之后就不用再配置,既可以满足速度要求,又不需要频繁改配置。
2.5、NOR/SRAM/PSRAM设备的FMC寄存器
2.5.1 NOR/PSRAM 片选控制寄存器 1/2/3/4(FMC_BCR1/2/3/4)
上面说过SRAM类型设备占用的是FMC的BANK1区域,但此区域内部又被分成了4个bank,每一个bank都有一个片选控制寄存器FMC_BCR,描述如下图:
该寄存器我在驱动TFTLCD时用到的设置有:EXTMOD、WREN、MWID、MTYP和 MBKEN,接下来将逐个介绍。
EXTMOD:扩展模式使能位,也就是是否允许读写不同的时序。驱动TFTLCD时读写采用不同的时序,故该位需要设置为 1。
WREN:写使能位。我们需要向 TFTLCD 写数据,故该位必须设置为 1。
MWID[1:0]:存储器数据总线宽度。00表示8位数据模式;01表示16位数据模式;10
表示 32 位数据模式;11 保留。此次使用的TFT LCD是16位数据,所以此位是01.
MTYP[1:0]:存储器类型。00表示SRAM;01表示PSRAM;10表示NOR FLASH / OneNAND FLASH; 11 保留。在驱动TFT LCD时是当成 SRAM 用,所以需要设置 MTYP[1:0]=00。
MBKEN:存储块使能位。我们需要用到该存储块控制 TFTLCD,所以要使能该存储块。
2.5.2 SRAM/NOR-Flash 闪存片选时序寄存器 1/2/3/4 (FMC_BTR1/2/3/4)
该寄存器用于设置每个存储器块的控制信息。在开启EXTMOD时此寄存器用于设置读操作时序,描述如下图:
该寄存器我在驱动TFTLCD时用到的设置有:ACCMOD、DATAST 和 ADDSET,接下来将逐个介绍。
ACCMOD[1:0]:访问模式。00 表示模式 A;01 表示模式 B;10 表示模式 C;11 表示模式 D;前面已经说过在驱动TFT LCD时采用模式 A,故设置为 00。
DATAST[7:0]:数据保持时间。0 为保留设置,其他设置则代表保持时间为: DATAST 个
HCLK时钟周期,最大为 255个 HCLK周期。对 ILI9341来说,其实就是 RD低电平持续时间,一般为 355ns。当FMC时钟为192M时一个 HCLK 时钟周期为 5.2ns 左右(1/192Mhz),为了兼容其他屏,我们这里设置 DATAST 为 70,也就是 70 个 HCLK 周期,时间大约是 364ns(略超,但不影响使用)。
ADDSET[3:0]:地址建立时间。其建立时间为:ADDSET 个 HCLK 周期,最大为 15 个
HCLK 周期。对 ILI9341 来说,这里相当于 RD 高电平持续时间,为 90ns,我们设置 ADDSET为最大 15,即 15*5.2=78ns。
2.5.3 SRAM/NOR-Flash 写入时序寄存器 1/2/3/4 (FMC_BWTR1/2/3/4)
该寄存器用于设置写操作时序,设置方式与FMC_BTR一致,描述如下图:
这个寄存器的设置方式与读操作时序寄存器的设置完全一致,不同的只是DATAST和ADDSET设置的时间不同。在进行写操作时DATAST 和 ADDSET 则对应 WR 的低电平和高电平持续时间。对 ILI9341 来说,这两个时间只需要 15ns 就够了,比读操作快得多。所以我们这里设置 DATAST 为 3,即 3 个 HCLK 周期,时间约为 15.6ns。然后 ADDSET 设置为 3,也是 15.6ns。(略超,但不影响使用)
注意:在 MDK 的寄存器定义里面,并没有定义FMC_BCRx、FMC_BTRx、FMC_BWTRx 等这个单独的寄存器,而是将他们进行了一些组合。
FMC_BCRx 和 FMC_BTRx,组合成 BTCR[8]寄存器组,他们的对应关系如下:
BTCR[0]对应 FMC_BCR1,BTCR[1]对应 FMC_BTR1
BTCR[2]对应 FMC_BCR2,BTCR[3]对应 FMC_BTR2
BTCR[4]对应 FMC_BCR3,BTCR[5]对应 FMC_BTR3
BTCR[6]对应 FMC_BCR4,BTCR[7]对应 FMC_BTR4
FMC_BWTRx 则组合成 BWTR[7],他们的对应关系如下:
BWTR[0]对应 FMC_BWTR1,
BWTR[2]对应 FMC_BWTR2,
BWTR[4]对应 FMC_BWTR3,
BWTR[6]对应 FMC_BWTR4,
BWTR[1]、BWTR[3]和 BWTR[5]保留,没有用到。
2.6、TFT LCD的驱动流程
一般TFT LCD的驱动流程如下:首先是硬件复位(LCD_RST先拉低100毫秒再拉高),然后发送LCD驱动芯片的初始化序列(厂家提供),初始化完成后就可以进行访问了。如果要写数据时先设置坐标,之后再发送写GRAM指令,发送颜色数据,LCD收到后就显示;如果要读数据同样要先设置坐标,之后再发送读GRAM指令,之后再读出颜色数据,单片机获取到数据后再进行处理。
3、使用FMC接口驱动SDRAM
3.1 SDRAM存储器相关知识介绍
SDRAM设备有以下控制接口:时钟线(CLK)、时钟使能(CKE,禁止时钟时,SDRAM 会进入自刷新模式)、片选信号(CS,低电平有效)、行地址选通信号(RAS,低电平表示选通)、列地址选通信号(CAS,低电平表示选通)、写信号(WE,低电平有效)、地址线( A0~A12)、数据线( DQ0~DQ15)、BANK地址线(BA0、BA1)、数据掩码(DQML,DQMH,表示DQ的有效部分)。
SDRAM设备通常被分成4个BANK,每个BANK都是一个表格,只需要给定行地址和列地址,就能确定其唯一的地址。所以在SDRAM内部寻址的时候,先指定BANK号和行地址,然后再指定列地址,就可以查找到目标地址。
SDRAM寻址的时候首先 RAS 信号为低电平,选通行地址,地址线 A0~A12 所表示的地址,会被传输并锁存到行地址译码器里面,作为行地址;同时 BANK 地址线上面的 BS0,BS1 所表示的 BANK 地址也会被锁存,选中对应的BANK;然后 CAS 信号为低电平,选通列地址,地址线A0~A12 所表示的地址会被传输并锁存到列地址译码器里面作为列地址,这样就完成了一次寻址。在完成寻址以后,数据线 DQ0~DQ15 上面的数据会写入(或读出)存储阵列。
注意:因为SDRAM 的位宽,可以达到32位,也就是最多有32条数据线,在实际使用的时候,我们可能会以8位、16位、24位和 32位等宽度来读写数据,这样的话,并不是每条数据线,都会被使用到,未被用到的数据线上面的数据,必须被忽略,这个时候就需要用到数据掩码( DQM)线来控制了,每一个数据掩码线,对应 8 个位的数据,低电平表示对应数据位有效,高电平表示对应数据位无效。
W9825G6KH的存储结构为:行地址8192个(2的13次方),列地址512个(2的9次方), BANK数4个,位宽:16 位。这样,整个芯片的容量为: 8192*512*4*16=32M 字节。
W9825G6KH 最快可以到 200M。
3.2 SDRAM存储器初始化流程
① 上电:此步给 SDRAM 供电,使能 CLK 时钟,并发送 NOP(No Operation 命令,空操作命令)。注意:上电后,要等待最少 200us再发送其他指令。
② 发送预充电命令(Percharge),给所有 Bank 预充电。
③ 发送自动刷新命令(Refresh),至少要发送 8 次自刷新命令,每一个自刷新命令之间的间隔时间为 tRC。
④ 设置模式寄存器(Mode Register Set):发送模式寄存器的值,配置 SDRAM 的工作参数。配置完成后,需要等待tMRD(也叫 tRSC),使模式寄存器的配置生效,才能发送其他命令。
注意:这里提到的 tRC、tMRD 和 tRSC 见 SDRAM 的芯片数据手册。
经过前面四步的操作,SDRAM 的初始化就完成了,接下来就可以发送激活命令和读/写命令,进行数据的读/写了。
3.3 SDRAM存储器的相关参数及计算方法
突发传输:指定起始列地址与突发长度,内存就会依次地自动对后面相应数量的存储单元进行读/写操作而不再需要控制器连续地提供列地址。此时除了第一个数据的传输需要若干个周期外,其后每个数据只需一个周期的即可获得。
突发长度(BL):是指在同一行中相邻的存储单元连续进行数据传输的方式,连续传输所涉及到存储单元(列)的数量就是突发长度。 BL的数值是在初始化的时候,通过模式寄存器设置命令,进行设置。 目前可用的选项是 1、 2、 4、 8、全页( Full Page),常见的设定是4和8。
非突发连续读取模式:不采用突发传输而是依次单独寻址,此时可等效于 BL=1。虽然可以让数据是连续的传输,但每次都要发送列地址与命令信息,控制资源占用极大。
突发连续读取模式:只要指定起始列地址与突发长度,寻址与数据的读取自动进行,而只要控制好两段突发读取命令的间隔周期(与 BL 相同)即可做到连续的突发传输。
CAS Latency,即列地址选通延迟(简称 CL)。在读命令(同时发送列地址)发送完之后需要等待几个时钟周期DQ数据线上的数据才会有效,这个延迟时间就叫CL,一般设置为 2或3 个时钟周期,即CAS的值通常为2或3。
SDRAM设置结构体详解:
typedef struct
{
uint32_t SDBank;//SDRAM的哪一个BANK,BANK1还是BANK2,硬件连接决定。
uint32_t ColumnBitsNumber;//SDRAM芯片的行数量
uint32_t RowBitsNumber;//SDRAM芯片的列数量
uint32_t MemoryDataWidth;//SDRAM芯片的数据宽度
uint32_t InternalBankNumber;//SDRAM芯片的BANK数
uint32_t CASLatency;//列地址选通延迟,通常为2或3
uint32_t WriteProtection;//写保护是否开启,通常是关
uint32_t SDClockPeriod;//时钟分频数,FMC时钟来源于HCLK分频(HCLK就是系统时钟)
uint32_t ReadBurst;//是否是能突发读取模式
uint32_t ReadPipeDelay;//读通道延时,即CAS延时多少个时钟周期去读取数据。
} FMC_SDRAM_InitTypeDef;
SDRAM时间参数结构体详解:
typedef struct
{
uint32_t LoadToActiveDelay;用来设置加载模式寄存器命令和激活或刷新命令之间的延迟,
对应寄存器 FMC_SDTRx 的TMRD位,其值应手册上的tRSC;
uint32_t ExitSelfRefreshDelay;用来设置从发出自刷新命令到发出激活命令之间的延迟,对应TXSR 位,其值由手册上的tXSR除以FMC时钟周期得到;
uint32_t SelfRefreshTime;用来设置自刷新周期,对应TRAS位,其值由手册上的tRC除以FMC时钟周期得到。
uint32_t RowCycleDelay;用来设置刷新命令和激活命令之间的延迟以及两个相邻刷新命令之间的延迟,对应TRC位,其值对应手册上的tRRD。
uint32_t WriteRecoveryTime;用来设置写命令和预充电命令之间的延迟,对应 TWR 位,其值对应手册上的tWR。
uint32_t RPDelay;用来设置预充电命令与其它命令之间的延迟,对应位 TRP位,其值由手册上的tRP除以FMC时钟周期得到。
uint32_t RCDDelay; 用来设置激活命令与读/写命令之间的延迟,对应位 TRCD位,其值由手册上的tRCD除以FMC时钟周期得到。
} FMC_SDRAM_TimingTypeDef;
SDRAM的刷新率计算:刷新率=(SDRAM 刷新周期[tREF]*1000000/(2^行数))/FMC时钟周期-20;(注意:这里乘1000000主要是单位不同,tREF的单位是毫秒,FMC时钟周期的单位是纳秒,相差2个量级,所以要乘1000000)
3.4 F429驱动W9825G6KH-5的参数计算
F429主频180M、SDRAM芯片接在单片机第一个 SDRAM BANK、芯片是9列13行、数据宽度为16位、芯片内有4个BANK、时钟2分频、CAS等于3:
时钟周期=1S/90M=11.1ns;
LoadToActiveDelay = tRSC = 2;
ExitSelfRefreshDelay = tXSR/FMC时钟周期=72/11.1=7;
SelfRefreshTime = tRC/FMC时钟周期=60/11.1=6;
RowCycleDelay = tRRD = 2;
WriteRecoveryTime = tWR = 2(CL等于3);
RPDelay = tRP/FMC时钟周期 = 15/11.1=2;
RCDDelay = tRCD/FMC时钟周期 = 15/11.1=2;
刷新率 =( tREF * 1000000 /( 2^行数))/ FMC时钟周期 - 20 =( 64 * 1000000 / (2^13))/ 11.1-20=683;
3.5 F429驱动W9825G6KH-5时CubeMX配置
CubeMX:6.4.0
F4 PACK包:V1.26.0
3.6 CubeMX生成代码修改
fmc.h文件:
#define BANK5_SDRAM_ADDR ((uint32_t)(0XC0000000)) /* SDRAM开始地址 */
/* SDRAM配置参数 */
/* 突发长度 */
#define SDRAM_MODEREG_BURST_LENGTH_1 ((uint16_t)0x0000)
#define SDRAM_MODEREG_BURST_LENGTH_2 ((uint16_t)0x0001)
#define SDRAM_MODEREG_BURST_LENGTH_4 ((uint16_t)0x0002)
#define SDRAM_MODEREG_BURST_LENGTH_8 ((uint16_t)0x0004)
/* 突发类型 */
#define SDRAM_MODEREG_BURST_TYPE_SEQUENTIAL ((uint16_t)0x0000) /*连续*/
#define SDRAM_MODEREG_BURST_TYPE_INTERLEAVED ((uint16_t)0x0008) /*交错*/
/* CAS值 */
#define SDRAM_MODEREG_CAS_LATENCY_2 ((uint16_t)0x0020)
#define SDRAM_MODEREG_CAS_LATENCY_3 ((uint16_t)0x0030)
/* 操作模式 */
#define SDRAM_MODEREG_OPERATING_MODE_STANDARD ((uint16_t)0x0000)
/* 突发写模式 */
#define SDRAM_MODEREG_WRITEBURST_MODE_PROGRAMMED ((uint16_t)0x0000) /*连续访问*/
#define SDRAM_MODEREG_WRITEBURST_MODE_SINGLE ((uint16_t)0x0200) /*单点访问*/
fmc.c文件:
/**
* @brief 向SDRAM发送命令
* @param bankx:0,向BANK5上面的SDRAM发送指令
* @param 1,向BANK6上面的SDRAM发送指令
* @param cmd:指令(0,正常模式/1,时钟配置使能/2,预充电所有存储区/3,自动刷新/4,加载模式寄存器/5,自刷新/6,掉电)
* @param refresh:自刷新次数
* @param 返回值:0,正常;1,失败.
* @retval 模式寄存器的定义
*/
static uint8_t sdram_send_cmd(uint8_t bankx, uint8_t cmd, uint8_t refresh, uint16_t regval)
{
uint32_t target_bank = 0;
FMC_SDRAM_CommandTypeDef command;
if (bankx == 0) target_bank = FMC_SDRAM_CMD_TARGET_BANK1;
else if (bankx == 1) target_bank = FMC_SDRAM_CMD_TARGET_BANK2;
command.CommandMode = cmd; /* 命令 */
command.CommandTarget = target_bank; /* 目标SDRAM存储区域 */
command.AutoRefreshNumber = refresh; /* 自刷新次数 */
command.ModeRegisterDefinition = regval; /* 要写入模式寄存器的值 */
/*发送命令 */
if (HAL_SDRAM_SendCommand(&hsdram1, &command, 0X1000) == HAL_OK) return 0;
else return 1;
}
void sdram_initialization_sequence(void)//发送SDRAM初始化序列
{
uint32_t temp = 0;
/* SDRAM控制器初始化完成以后还需要按照如下顺序初始化SDRAM */
sdram_send_cmd(0, FMC_SDRAM_CMD_CLK_ENABLE, 1, 0); /* 时钟配置使能 */
HAL_Delay(1); /* 至少延时500us */
sdram_send_cmd(0, FMC_SDRAM_CMD_PALL, 1, 0); /* 对所有存储区预充电 */
sdram_send_cmd(0, FMC_SDRAM_CMD_AUTOREFRESH_MODE, 8, 0); /* 设置自刷新次数 */
/* 配置模式寄存器,SDRAM的bit0~bit2为指定突发访问的长度,
* bit3为指定突发访问的类型,bit4~bit6为CAS值,bit7和bit8为运行模式
* bit9为指定的写突发模式,bit10和bit11位保留位 */
temp = (uint32_t)SDRAM_MODEREG_BURST_LENGTH_1 | /* 设置突发长度:1(可以是1/2/4/8) */
SDRAM_MODEREG_BURST_TYPE_SEQUENTIAL | /* 设置突发类型:连续(可以是连续/交错) */
SDRAM_MODEREG_CAS_LATENCY_3 | /* 设置CAS值:3(可以是2/3) */
SDRAM_MODEREG_OPERATING_MODE_STANDARD| /* 设置操作模式:0,标准模式 */
SDRAM_MODEREG_WRITEBURST_MODE_SINGLE; /* 设置突发写模式:1,单点访问 */
sdram_send_cmd(0, FMC_SDRAM_CMD_LOAD_MODE, 1, temp); /* 设置SDRAM的模式寄存器 */
}
最后在HAL_SDRAM_Init后添加以下语句:
sdram_initialization_sequence();/* 发送SDRAM初始化序列 */
HAL_SDRAM_ProgramRefreshRate(&hsdram1, 683); /* 设置刷新频率*/
至此SDRAM已经初始化完成可以使用了。
最后main.c增加一下测试代码:
uint16_t testsdram[250000] __attribute__((at(0XC0000000))); /* 测试用数组 */
void sdram_test(uint16_t x, uint16_t y)
{
uint32_t i = 0;
uint32_t temp = 0;
uint32_t sval = 0; /* 在地址0读到的数据 */
/* 每隔16K字节,写入一个数据,总共写入2048个数据,刚好是32M字节 */
for (i = 0; i < 32 * 1024 * 1024; i += 16 * 1024)
{
*(volatile uint32_t *)(BANK5_SDRAM_ADDR + i) = temp;
temp++;
}
for (i = 0; i < 32 * 1024 * 1024; i += 16 * 1024) /* 依次读出进行校验 */
{
temp =*(volatile uint32_t*)(BANK5_SDRAM_ADDR + i);
if (i == 0) sval = temp;
else if (temp <= sval) break; /* 后面读出的数据一定要比第一次读到的数据大 */
printf("SDRAM Capacity:%dKB\r\n", (uint16_t)(temp - sval + 1) * 16); /*打印容量 */
}
}
以下代码添加到main函数:
uint32_t ts = 0;
sdram_test(30, 170);
for (ts = 0; ts < 250000; ts++)
{
testsdram[ ts ] = ts; /* 预存测试数据 */
}
for (ts = 0; ts < 250000; ts+= 10)
{
if(testsdram[ts] != (uint16_t)ts) printf("第 %d 个数据测试错误\n",ts);
HAL_Delay(1);
}
printf("测试完成\n");