STM32F429等系列的芯片增加了SDRAM接口,扩展的内存在做人机界面等应用时是非常有用的。


1. SDRAM硬件接口

    SDRAM(Synchronous Dynamic Random Access Memory)即同步动态随机存取存储器,同步是指存储器工作需要同步时钟,内部命令的发送与数据传输都以它为基准;动态是指存储阵列需要不断的刷新来保证数据不丢失;随机存取是指存储器的内容可以以任意顺序访问,而不管前一次访问的是哪一个位置。


    本篇文章使用的SDRAM芯片为W9825G6KH-6,该芯片是一个32M字节的芯片(4 M × 4 BANKS × 16 BITS  。芯片与单片机接口电路如下:

STM32CubeMX之SDRAM接口_数据

    芯片引脚定义如下:

  • A0~A12:时分复用地址总线(发送地址时,先行后列,13行,9列)
  • DQ0~DQ15:双向数据总线
  • BA0/BA1:Bank地址(两条总线,可以选通4个Bank)
  • CS#:片选信号(低电平有效)
  • WE#:写使能信号(低电平有效)
  • RAS#:行地址信号(低电平有效)
  • CAS#:列地址信号(低电平有效)
  • CLK:同步时钟
  • CKE:时钟使能信号
  • VDD/VDDQ:工作电压/DQ电压
  • VSS/VSSQQ:相应电压接地



2. STM32CubeMX配置

    基础的配置这里不再赘述,直接介绍SDRAM配置:

STM32CubeMX之SDRAM接口_时钟周期_02

    选择FMC接口,配置SDRAM1,

  • Clock and chip enable选择SDCKE0+SDNE0。FMC_SDCKE0 和FMC_SDCLK0对应的存储区域1 的地址范围是0xC000 0000-0xCFFF FFFF;而FMC_SDCKE1 和FMC_SDCLK1 对应的存储区域2 的地址范围是0xD000 0000- 0xDFFF FFFF
  • bank numbers为 4banks
  • Address为13bits
  • Data为16bits

    后三项配置都与所选芯片相关,W9825G6KH-6有4个banks,地址总线为13位,数据总线为16位。

    详细的配置如下:

STM32CubeMX之SDRAM接口_数据_03

    配置参数说明:

  • Number of column address bits表示列地址总线数9bits
  • Number of row address bits表示行地址总线数13bits

    这两项通过查看数据手册可知:

STM32CubeMX之SDRAM接口_#define_04

  • CAS latency表示CAS潜伏期,这里配置为3 memory clock cycles
  •    Write protection 表示写保护,一般配置为Disabled
  • SDRAM common clock为SDRAM 时钟配置,可选HCLK的2分频\3分频\不使能SDCLK时钟前面主频配置为168MHz,SDRAM common clock设置为2分频,那SDCLK时钟为84MHz,每个时钟周期为11.9ns
  • SDRAM common burst read 表示突发读,这里选择使能
  • SDRAM common read pipe delay 表示CAS潜伏期后延迟多少个时钟在进行读数据,这里选择1 HCLK clock cycle
  • Load mode register to active delay 表示加载模式寄存器命令和激活或刷新命令之间的延迟,查看数据手册,这里设置为2。
    STM32CubeMX之SDRAM接口_#define_05
  • Exit self-refresh delay 从发出自刷新命令到发出激活命令之间的延迟,按存储器时钟周期数计。查数据手册知道其最小值为72ns,由于我们每个时钟周期为11.9ns,这里设为8 (8*11.9=95.2>72ns)满足要求。
    STM32CubeMX之SDRAM接口_#define_06
  • Self refresh time 最短的自刷新周期,按存储器时钟周期数计。查数据手册知道其最小值为42ns,最大值为100000ns,由于我们每个时钟周期为11.9ns,这里设为6(6*11.9=71.4>42ns)满足要求。
    STM32CubeMX之SDRAM接口_数据_07
  • SDRAM common row cycle delay 刷新命令和激活命令之间的延迟,以及两个相邻刷新命令之间的延迟, 以存储器时钟周期。查数据手册知道其最小值为60ns,由于我们每个时钟周期为11.9ns,这里设为6 (6*11.9=71.4>60ns)满足要求。STM32CubeMX之SDRAM接口_时钟周期_08
  • Write recovery time 写命令和预充电命令之间的延迟,查数据手册,这里设为4,即.

STM32CubeMX之SDRAM接口_数据_09

  • SDRAM common row precharge delay预充电命令与其它命令之间的延迟,按存储器时钟周期数计。查数据手册知道其最小值为15ns,由于我们每个时钟周期为11.9ns,所以设为2 (2*11.9=23.8>15ns)满足要求。
    STM32CubeMX之SDRAM接口_#define_10
  • Row to column delay 表示激活命令与读/写命令之间的延迟,按存储器时钟周期数计。查数据手册知道其最小值为15ns,由于我们每个时钟周期为11.9ns,所以设为2 (2*11.9=23.8>15ns)
    STM32CubeMX之SDRAM接口_#define_11

需要注意,时序必须满足以下式子:

Write recovery time  ≥ TRAS - TRCD

Write recovery time ≥ TRC - TRCD - TRP


3. 代码编写

    配置完成后点击生成代码。需要自己编写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)
#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)

uint8_t SDRAM_SendCommand(uint32_t CommandMode, uint32_t Bank, uint32_t RefreshNum, uint32_t RegVal)
{
uint32_t CommandTarget;
FMC_SDRAM_CommandTypeDef Command;

if(Bank == 1)
CommandTarget = FMC_SDRAM_CMD_TARGET_BANK1;
else if(Bank == 2)
CommandTarget = FMC_SDRAM_CMD_TARGET_BANK2;

Command.CommandMode = CommandMode;
Command.CommandTarget = CommandTarget;
Command.AutoRefreshNumber = RefreshNum;
Command.ModeRegisterDefinition = RegVal;
if(HAL_SDRAM_SendCommand(&hsdram1, &Command, 0x1000) == HAL_OK)
return 1;
else
return 0;
}

void SDRAM_Init(void)
{
uint32_t temp;

SDRAM_SendCommand(FMC_SDRAM_CMD_CLK_ENABLE, 1, 1, 0); //步骤3:使能时钟信号,SDCKE0 = 1
HAL_Delay(1); //步骤4:至少延时200us
SDRAM_SendCommand(FMC_SDRAM_CMD_PALL, 1, 1, 0); //步骤5:发送全部预充电命令
SDRAM_SendCommand(FMC_SDRAM_CMD_AUTOREFRESH_MODE, 1, 8, 0); //步骤6:设置自动刷新次数

temp = SDRAM_MODEREG_BURST_LENGTH_1 | //设置突发长度:1
SDRAM_MODEREG_BURST_TYPE_SEQUENTIAL | //设置突发类型:连续
SDRAM_MODEREG_CAS_LATENCY_3 | //设置CAS值:3
SDRAM_MODEREG_OPERATING_MODE_STANDARD | //设置操作模式:标准
SDRAM_MODEREG_WRITEBURST_MODE_SINGLE; //设置突发写模式:单点访问
SDRAM_SendCommand(FMC_SDRAM_CMD_LOAD_MODE, 1, 1, temp); //步骤7:装载模式寄存器的值

//SDRAM刷新周期是64ms,行数是8192行,时钟频率是168MHz/2=84MHz
//所有COUNT = (64ms/8192)/(1/84us)-20 = 64000*84/8192-20 = 636
HAL_SDRAM_ProgramRefreshRate(&hsdram1, 636); //步骤8:设置刷新速率
}

    在主函数中调用SDRAM_Init()函数后即可使用SDRAM。编写SDRAM测试函数:


#define SDRAM_Size 0x02000000 //32M字节
#define SDRAM_BANK_ADDR ((uint32_t)0xC0000000) // FMC SDRAM 数据基地址

uint8_t SDRAM_Test(void)
{
uint32_t i = 0; // 计数变量
uint32_t ReadData = 0; // 读取到的数据
uint8_t ReadData_8b;

UART2_Send((uint8_t*)"STM32F429 SDRAM测试\r\n");
UART2_Send((uint8_t*)"测试开始,以32位数据宽度写入数据...\r\n");
for (i = 0; i < SDRAM_Size/4; i++)
{
*(__IO uint32_t*) (SDRAM_BANK_ADDR + 4*i) = i; // 写入数据
}

UART2_Send((uint8_t*)"写入完毕,读取数据并比较...\r\n");
for(i = 0; i < SDRAM_Size/4;i++ )
{
ReadData = *(__IO uint32_t*)(SDRAM_BANK_ADDR + 4 * i ); // 从SDRAM读出数据
if( ReadData != i ) //检测数据,若不相等,跳出函数,返回检测失败结果。
{
UART2_Send((uint8_t*)"SDRAM测试失败!!\r\n");
return ERROR; // 返回失败标志
}
}

UART2_Send((uint8_t*)"32位数据宽度读写通过,以8位数据宽度写入数据\r\n");
for (i = 0; i < 255; i++)
{
*(__IO uint8_t*) (SDRAM_BANK_ADDR + i) = i;
}
UART2_Send((uint8_t*)"写入完毕,读取数据并比较...\r\n");
for (i = 0; i < 255; i++)
{
ReadData_8b = *(__IO uint8_t*) (SDRAM_BANK_ADDR + i);
if( ReadData_8b != (uint8_t)i ) //检测数据,若不相等,跳出函数,返回检测失败结果。
{
UART2_Send((uint8_t*)"8位数据宽度读写测试失败!!\r\n");
UART2_Send((uint8_t*)"请检查NBL0和NBL1的连接\r\n");
return ERROR; // 返回失败标志
}
}
UART2_Send((uint8_t*)"8位数据宽度读写通过\r\n");
UART2_Send((uint8_t*)"SDRAM读写测试通过,系统正常\r\n");
return SUCCESS; // 返回成功标志
}

    查看串口打印的数据,读写正常。

STM32CubeMX之SDRAM接口_时钟周期_12


4. 总结

    本篇文章介绍了SDRAM的读写操作,可以作为GUI的显存,将在后续文章中介绍。


欢迎关注公众号"嵌入式技术开发",大家可以后台给我留言沟通交流。如果觉得该公众号对你有所帮助,也欢迎推荐分享给其他人。

STM32CubeMX之SDRAM接口_数据_13