【SDRAM】STM32外扩SDRAM学习总结 + CubeMX配置教程

一、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 时钟周期读取数据)

扫描二维码关注公众号,回复: 14612983 查看本文章

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 */

编译完成后下载,可以看到串口中输出了测试成功的消息

猜你喜欢

转载自blog.csdn.net/lrqblack/article/details/124829360