一、SDRAM概述
(内容参考正点原子 + 个人经验总结)
STM32H743自带1M左右的RAM,虽然已经很大了,但总有不够的时候。RAM不够用,长久之计当然是增加外部RAM。
外部RAM也分两大类,第一类是DRAM,另一类是SRAM。
DRAM:动态随机存储器,以电容的电荷来表示数据(有电荷代表1,无电荷代表零)。因为是电容,会失电,所以要频繁的刷新以保存数据。
SRAM:静态随机存储器,以锁存的形式存储数据,只要不掉电,数据就一直存在,不需要刷新来保持数据。
DRAM中也有一个种类叫SDRAM,S代表同步,同步的传输速度要比异步快很多,SDRAM这个速度可以说是很快了,大部分要求都能满足(DDR SDRAM可以看作SDRAM的加速版,一个时钟周期的上升沿和下降沿都能传输一个数据,而DDRII和DDRIII,只是提高了同步时钟的频率)。
我用到的实验平台是正点原子阿波罗STM32H743+W9825G6KH-6(行地址8192(2的13次方),列地址512(2^9),BANK4个,位宽16,芯片容量81925124*16=32M字节)
由于是并口同步RAM,所以对引脚口要求数量大,使用口线:
SDRAM的储存单元叫BANK(一般有4个),每个BANK中以阵列形式排列,先指定BANK号和行地址,然后指定列地址,则能确定唯一地址。
SDRAM 控制命令(数据在时钟信号上升沿有效)
NO-Operation:空操作,用于选中SDRAM,为发送做准备
Active:激活命令(非读写),选择行地址和BANK
Read/Write:读/写命令(WE高读低写),由于Active选择了行地址和BANK,于是我们还需要发送列地址才能确定一个存储体,读写命令的时候,数据会和列地址一起发送过来。
Precharge:预充电,用于关闭BANK现有行,准备打开新行
Refresh:用于刷新操作。
有两种刷新模式:自动刷新和自我刷新。(在发送 Refresh 命令时,CKE为高电平有效,则启用自动刷新模式。在发送 Refresh 命令时,CKE为低电平无效,则启用自我刷新模式。)
主要强调自动刷新: SDRAM内部有一个行地址生成器(也称刷新计数器)用来自动的依次生成要刷新的行地址。由于刷新是针对一行中的所有存储体进行,所以无需列寻址。刷新操作是让所有BANK刷新同一行,刷新过程中BANK不可用,这意味着刷新操作会使所有bank都失去作用,这个刷新过程持续9个时钟周期(PC133标准下),所有指令都必须等待,之后可进入正常工作状态。SDRAM的刷新周期是64MS(因为公认SDRAM在64毫秒之后电容数据可能失效),所以一个有8192行的SDRAM必须要在64毫秒里至少完成8192次刷新。
Mode Register Set:模式设置寄存器,每次SDRAM初始化都必须进行设置。它的设置包括:突发长度、突发访问的地址模式、列地址选通延迟、写模式单点还是突发。可以根据自己的需要在这里进行有限的优化。
Burst Stop:若传输长度小于突发长度,则需要发送 Burst Stop(停止突发)命令,结束突发传输。
STM32H743使用FMC接口驱动SDRAM。
FMC涉及到四个关键寄存器:
控制寄存器: FMC_SDCRx
时序寄存器: FMC_SDTRx
命令模式寄存器: FMC_SDCMR
刷新定时器寄存器: FMC_SDRTR(注意COUNT位,刷新速率=( COUNT+1) *SDRAM 频率时钟)
如果FMC上的时钟是200Mhz的情况下,SDRAM吃不消这么高的频率,所以在配置的时候会设置 二分频 或者 三分频。200Mhz的情况下,二分频就能满足要求,所以落在SDRAM上的时钟是100MHz的
∴ 刷新周期 = 64ms/8192行 = 7.81us
SDRAM频率时钟 = 1秒/(200/2)MHz = 10ns
COUNT = 7.81us/10ns = 781
但781是理想情况下刷新完所有行,但实际读写过程中,一定会存在刷新等待的情况,所以我们给出一定裕度,COUNT = COUNT - 20 = 761
二、CubeMX配置
MDK(KEIL5)的配置和串口的配置我就省略了,可以看我的SRAM配置的博客
现在默认大家都配置好了串口和MDK(keil5)
由于我这边无法上传高清图片,所以各位讲究看看吧~
数值来源:
1、我们用的是SDRAM1控制器
2、拉低CKE使能时钟,拉低NE使能片选信号,正常模式
3、这个SdDRAM有4个BANK
4、A0-A12,一共13根地址位
5、D0-D15,位宽16位,同时也是16根数据线
6、允许16位数据宽度读写
上半部分是SDRAM control的配置:
1、所以我们用SDRAM bank1(此BANK非彼BANK,是STM32的BANK1)
2、这片SDRAM是13行9列的,所以9bit 13bit
3、CAS latency(突发读写长度为4or8时CL都为3)
4、关闭写保护
5、SDRAM Common clock是SDRAM的时钟周期,是相对于主频而言的,(如果480M主频SDRAM会扛不住,所以要分频)
6、使能突发读取
7、SDRAM Common read pipe delay(CAS 延迟后延后多少个 HCLK 时钟周期读取数据)
2、下半部分是SDRAM timeing in memory clock cyscles配置(以下单位均为SDRAM时钟周期, SDRAM周期就按照器件手册的数值填写即可,因为HAL库的SDRAM时序配置函数会将数值-1进行寄存器赋值。)
如果我们的主频选择的是480M,那么FMC的频率可能被分到240M,但是SDRAM吃不住这么高频率,就要配置分频,如果配置了二分频,那么一个周期1/120M=8.3ns
这些数据要根据SDRAM芯片手册去填写
这些数据分别是手册中的
tRSC
tXSR
tRC
tRRD
tWR
tRP
tRCD
根据手册中的数值去配置就没问题!注意单位和时钟周期的计算,这个要基于你自己配置的时钟!
由于我这边无法上传高清图片,所以各位讲究看看吧~
三、程序配置
先在fmc.c上方添加三个函数:
函数一:头文件、定义、一个延时函数
#include "stdio.h"
FMC_SDRAM_CommandTypeDef command; // 控制指令
void SDRAM_delay(__IO uint32_t nCount)
{
__IO uint32_t index = 0;
for(index = (200000 * nCount); index != 0; index--);
}
函数二:和寄存器配置有关的函数,可以在这里更改寄存器配置
void SDRAM_Initialization_Sequence(SDRAM_HandleTypeDef *hsdram, FMC_SDRAM_CommandTypeDef *Command)
{
__IO uint32_t tmpmrd = 0;
/* Configure a clock configuration enable command */
Command->CommandMode = FMC_SDRAM_CMD_CLK_ENABLE; // 开启SDRAM时钟
Command->CommandTarget = FMC_COMMAND_TARGET_BANK; // 选择要控制的区域
Command->AutoRefreshNumber = 1;
Command->ModeRegisterDefinition = 0;
HAL_SDRAM_SendCommand(hsdram, Command, SDRAM_TIMEOUT); // 发送控制指令
SDRAM_delay(5); // 延时等待
/* Configure a PALL (precharge all) command */
Command->CommandMode = FMC_SDRAM_CMD_PALL; // 预充电命令
Command->CommandTarget = FMC_COMMAND_TARGET_BANK; // 选择要控制的区域
Command->AutoRefreshNumber = 1;
Command->ModeRegisterDefinition = 0;
HAL_SDRAM_SendCommand(hsdram, Command, SDRAM_TIMEOUT); // 发送控制指令
/* Configure a Auto-Refresh command */
Command->CommandMode = FMC_SDRAM_CMD_AUTOREFRESH_MODE; // 使用自动刷新
Command->CommandTarget = FMC_COMMAND_TARGET_BANK; // 选择要控制的区域
Command->AutoRefreshNumber = 8; // 自动刷新次数
Command->ModeRegisterDefinition = 0;
HAL_SDRAM_SendCommand(hsdram, Command, SDRAM_TIMEOUT); // 发送控制指令
/* Program the external memory mode register */
tmpmrd = (uint32_t)SDRAM_MODEREG_BURST_LENGTH_2 |
SDRAM_MODEREG_BURST_TYPE_SEQUENTIAL |
SDRAM_MODEREG_CAS_LATENCY_3 |
SDRAM_MODEREG_OPERATING_MODE_STANDARD |
SDRAM_MODEREG_WRITEBURST_MODE_SINGLE;
Command->CommandMode = FMC_SDRAM_CMD_LOAD_MODE; // 加载模式寄存器命令
Command->CommandTarget = FMC_COMMAND_TARGET_BANK; // 选择要控制的区域
Command->AutoRefreshNumber = 1;
Command->ModeRegisterDefinition = tmpmrd;
HAL_SDRAM_SendCommand(hsdram, Command, 0x1000); // 发送控制指令
HAL_SDRAM_ProgramRefreshRate(hsdram, 761); // 设置刷新计数器
}
void SDRAM_Device_Init(void)
{
SDRAM_Initialization_Sequence(&hsdram1, &command);//因为用的是SDRAM1,然后前面也定义了一个FMC_SDRAM_CommandTypeDef类型的command
}
函数三:SDRAM测试程序
uint8_t SDRAM_Test(void)
{
#if SDRAM_TEST_FUNC_1
uint32_t i = 0; // 计数变量
uint16_t ReadData = 0; // 读取到的数据
uint8_t ReadData_8b;
ii = 0;
printf("%d\n",ii);
for (i = 0; i < SDRAM_Size/2; i++)
{
*(__IO uint16_t*) (SDRAM_BANK_ADDR + 2*i) = (uint16_t)i; // 写入数据
}
printf("%d\n",ii);
for(i = 0; i < SDRAM_Size/2;i++ )
{
ReadData = *(__IO uint16_t*)(SDRAM_BANK_ADDR + 2 * i ); // 从SDRAM读出数据
if( ReadData != (uint16_t)i ) //检测数据,若不相等,跳出函数,返回检测失败结果。
{
printf("SDRAM测试失败!!\r\n");
return ERROR; // 返回失败标志
}
}
ii = 0;
printf("%d\n",ii);
for (i = 0; i < SDRAM_Size; i++)
{
*(__IO uint8_t*) (SDRAM_BANK_ADDR + i) = (uint8_t)i;
}
printf("%d\n",ii);
for (i = 0; i < SDRAM_Size; i++)
{
ReadData_8b = *(__IO uint8_t*) (SDRAM_BANK_ADDR + i);
if( ReadData_8b != (uint8_t)i ) //检测数据,若不相等,跳出函数,返回检测失败结果。
{
printf("8位数据宽度读写测试失败!!\r\n");
printf("请检查NBL0和NBL1的连接\r\n");
return ERROR; // 返回失败标志
}
}
#endif
return SUCCESS; // 返回成功标志
}
然后在fmc.h中的下端添加一段代码
这些代码和模式、SDRAM大小、基地址等,不能不看!
extern FMC_SDRAM_CommandTypeDef command; // 控制指令
#define SDRAM_Size 0x02000000 //32M字节
#define SDRAM_Half_Size 0x01000000
#define SDRAM_BANK_ADDR ((uint32_t)0xC0000000) // FMC SDRAM 数据基地址(已测试D开头的SDRAM2地址无法写入,所以没问题)
#define FMC_COMMAND_TARGET_BANK FMC_SDRAM_CMD_TARGET_BANK1 // SDRAM 的bank选择
#define SDRAM_TIMEOUT ((uint32_t)0x1000) // 超时判断时间
#define SRAM_Size 0x00020000 //128K字节
#define SRAM_Half_Size 0x00010000
#define SRAM_ADDR ((uint32_t)0x30000000)
#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)
#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)
void SDRAM_Initialization_Sequence(SDRAM_HandleTypeDef *hsdram, FMC_SDRAM_CommandTypeDef *Command);
void SDRAM_Device_Init(void);
uint8_t SDRAM_Test(void);
最后在主循环的上面引用测试代码,测一测SDRAM好不好用
/* USER CODE BEGIN 2 */
printf("test");
int retval;
SDRAM_Device_Init(); // SDRAM设备初始化
retval = SDRAM_Test(); // SDRAM设备测试
if(retval != SUCCESS)
{
printf("SDRAM测试错误!\r\n");
}
/* USER CODE END 2 */
编译完成后下载,可以看到串口中输出了测试成功的消息