| 银杏科技有限公司旗下技术文档发布平台 | 
	
		| 技术支持电话 | 0379-69926675-801 | 
	
		| 技术支持邮件 | Gingko@vip.163.com | 
	
		| 版本 | 日期 | 作者 | 修改内容 | 
	
		| V1.0 | 2020-07-12 | gingko | 初次建立 | 
实验三十七:SDRAM实验——读写SDRAM
一、 实验目的与意义
-  了解STM32 SDRAM结构。 
-  了解STM32 SDRAM特征。 
-  掌握SDRAM的使用方法。 
-  掌握STM32 HAL库中SDRAM属性的配置方法。 
-  掌握KEILMDK 集成开发环境使用方法。 
 
二、 实验设备及平台
- 
- 
-  Micro USB线缆。 
-  Keil MDK 开发平台。 
-  STM32CubeMX开发平台。 
-  装有WIN XP(及更高版本)系统的计算机。 
 
三、 实验原理
1、SDRAM简介
-  SDRAM,英文名是:Synchronous Dynamic Random Access Memory,即同步动态随机存储器,相较于SRAM(静态存储器),SDRAM具有:容量大和价格便宜的特点。STM32F767支持SDRAM,因此,我们可以外挂SDRAM,从而大大降低外扩内存的成本。 
-  同步动态随机存取内存(SDRAM)是有一个同步接口的动态随机存取内存(DRAM)。通常DRAM是有一个异步接口的,这样它可以随时响应控制输入的变化。而SDRAM有一个同步接口,在响应控制输入前会等待一个时钟信号,这样就能和计算机的系统总线同步。时钟被用来驱动一个有限状态机,对进入的指令进行管线(Pipeline)操作。这使得SDRAM与没有同步接口的异DRAM(asynchronous DRAM)相比,可以有一个更复杂的操作模式。管线意味着芯片可以在处理完之前的指令前,接受一个新的指令。在一个写入的管线中,写入命令在另一个指令执行完之后可以立刻执行,而不需要等待数据写入存储队列的时间。在一个读取的流水线中,需要的数据在读取指令发出之后固定数量的时钟频率后到达,而这个等待的过程可以发出其它附加指令。 
-  SDRAM是多Bank结构,例如在一个具有两个Bank的SDRAM的模组中,其中一个Bank在进行预充电期间,另一个Bank却马上可以被读取,这样当进行一次读取后,又马上去读取已经预充电Bank的数据时,就无需等待而是可以直接读取了,这也就大大提高了存储器的访问速度。为了实现这个功能,SDRAM需要增加对多个Bank的管理,实现控制其中的Bank进行预充电。在一个具有2个以上Bank的SDRAM中,一般会多一根叫做BAn的引脚,用来实现在多个Bank之间的选择。 
-  SDRAM具有多种工作模式,内部操作是一个复杂的状态机。SDRAM器件的引脚分为以下几类。 
-  SDRAM的所有操作都同步于时钟。根据时钟上升沿控制管脚和地址输入的状态,可以产生多种输入命令: - 
-  模式寄存器设置命令 
-  激活命令 
-  预充命令 
-  读命令 
-  写命令 
-  带预充的读命令 
-  带预充的写命令 
-  自动刷新命令 
-  自我刷新命令 
-  突发停命令 
-  空操作命令 
 
-  根据输入命令,SDRAM状态在内部状态间转移。内部状态包括模式寄存器设置状态、激活状态、预充状态、写状态、读状态、预充读状态、预充写状态、自动刷新状态及自我刷新状态。 
-  SDRAM支持的操作命令有初始化配置、预充电、行激活、读操作、写操作、自动刷新、自刷新等。所有的操作命令通过控制线CS#、RAS#、CAS#、WE#和地址线、体选地址BA输入。 
 
2、SDRAM信号线
3、SDRAM存储单元
 
-  对于这个存储阵列,我们可以将其看成是一个表格,只需要给定行地址和列地址,就可以确定其唯一位置,这就是SDRAM寻址的基本原理。而一个SDRAM芯片内部,一般又有4个这样的存储单元(BANK),所以,在SDRAM内部寻址的时候,先指定BANK号和行地址,然后再指定列地址,就可以查找到目标地址。 
-  SDRAM的存储结构示意图,如图下所示,寻址的时候,首先RAS信号为低电平,选通行地址,地址线A0~A12所表示的地址,会被传输并锁存到行地址译码器里面,最为行地址,同时BANK地址线上面的BS0,BS1所表示的BANK地址,也会被锁存,选中对应的BANK,然后,CAS信号为低电平,选通列地址,地址线A0~A12所表示的地址,会被传输并锁存到列地址译码器里面,作为列地址,这样,就完成了一次寻址。 
 
 
4、数据传输
-  在完成寻址以后,数据线DQ0~DQ15上面的数据会通过数据控制逻辑写入(或读出)存储阵列。特别注意:因为SDRAM的位宽,可以达到32位,也就是最多有32条数据线,在实际使用的时候,我们可能会以:8位、16位、24位和32位等宽度来读写数据,这样的话,并不是每条数据线,都会被使用到,未被用到的数据线上面的数据,必须被忽略,这个时候就需要用到数据掩码(DQM)线来控制了,每一个数据掩码线,对应8个位的数据,低电平表示对应数据位有效,高电平表示对应数据位无效。 
-  SDRAM的驱动需要用到一些命令,常用的命令如图所示: 
 
 
5、原理图
四、 实验程序
1、主函数
int main(void)
{
  int i,j;
  /* MCU 配置*/
  /* 重置所有外设, 初始化Flash 接口和Systick. */
  HAL_Init();
  /* 系统时钟配置 */
  SystemClock_Config();
  /* 初始化所有已配置的外设 */
  MX_GPIO_Init();
    //SDRAM初始化
  BSP_SDRAM_Init();       
  LED_BLUE_ON;
  //向SDRAM中写入0x5555并读取校验
  for(i = 0;i < SDRAM_SIZE;i++){ 
      write_sdram(i,0x5555);
    }
    for(i = 0;i < SDRAM_SIZE;i++){
        if(0x5555 != read_sdram(i)){
            while(1){
                LED_RED_ON;
                HAL_Delay(500);
                LED_RED_OFF;
                HAL_Delay(500);
            }
        }
    }
    //向SDRAM中写入0xAAAA并读取校验
    for(i = 0;i < SDRAM_SIZE;i++){ 
        write_sdram(i,0xAAAA);
    }
    for(i = 0;i < SDRAM_SIZE;i++){
        if(0xAAAA != read_sdram(i)){
            while(1){
                LED_RED_ON;
                HAL_Delay(500);
                LED_RED_OFF;
                HAL_Delay(500);
            }
        }
    }   
    //向SDRAM中写入0x0000~0xFFFF并读取校验
    for(j = 0; j < 256; j++){
        for(i = 0;i < 65536;i++){
            write_sdram((65536 * j + i),i);
        }
    }
    for(j = 0; j < 256; j ++){
        for(i = 0;i < 65536;i++){
            if(i != read_sdram((65536 * j + i))){
                while(1){
                    LED_RED_ON;
                    HAL_Delay(500);
                    LED_RED_OFF;
                    HAL_Delay(500);
                }
            }
        }
    }   
 
    //测试成功
    LED_BLUE_OFF;
    LED_GREEN_ON;
  while (1)
  {
  }
}
 
2、SDRAM初始化
uint8_t BSP_SDRAM_Init(void)
{ 
  static uint8_t sdramstatus = SDRAM_ERROR;
  /* SDRAM设备配置 */
  /* SDRAM时钟频率为100Mhz的定时配置(系统时钟最高为200Mhz) */
  Timing.LoadToActiveDelay    = 2;
  Timing.ExitSelfRefreshDelay = 7;
  Timing.SelfRefreshTime      = 4;
  Timing.RowCycleDelay        = 7;
  Timing.WriteRecoveryTime    = 2;
  Timing.RPDelay              = 2;
  Timing.RCDDelay             = 2;
 
  sdramHandle.Init.SDBank             = FMC_SDRAM_BANK1;
  sdramHandle.Init.ColumnBitsNumber   = FMC_SDRAM_COLUMN_BITS_NUM_9;
  sdramHandle.Init.RowBitsNumber      = FMC_SDRAM_ROW_BITS_NUM_13;
  sdramHandle.Init.MemoryDataWidth    = SDRAM_MEMORY_WIDTH;
  sdramHandle.Init.InternalBankNumber = FMC_SDRAM_INTERN_BANKS_NUM_4;
  sdramHandle.Init.CASLatency         = FMC_SDRAM_CAS_LATENCY_3;
  sdramHandle.Init.WriteProtection    = FMC_SDRAM_WRITE_PROTECTION_DISABLE;
  sdramHandle.Init.SDClockPeriod      = SDCLOCK_PERIOD;
  sdramHandle.Init.ReadBurst          = FMC_SDRAM_RBURST_ENABLE;
  sdramHandle.Init.ReadPipeDelay      = FMC_SDRAM_RPIPE_DELAY_1;
  /* SDRAM控制器初始化 */
  BSP_SDRAM_MspInit(&sdramHandle, NULL); /* __weak函数可以被应用程序重写*/
  if(HAL_SDRAM_Init(&sdramHandle, &Timing) != HAL_OK)
  {
    sdramstatus = SDRAM_ERROR;
  }
  else
  {
    sdramstatus = SDRAM_OK;
  }
  /* SDRAM初始化顺序 */
  BSP_SDRAM_Initialization_sequence(REFRESH_COUNT);
  return sdramstatus;
}
 
 
 
3、SDRAM读写函数
#define SDRAM_SIZE SDRAM_DEVICE_SIZE
#define write_sdram(offset,data) *(volatile unsigned short int *)(SDRAM_DEVICE_ADDR + (offset << 1)) = data
#define read_sdram(offset) *(volatile unsigned short int *)(SDRAM_DEVICE_ADDR + (offset << 1))
 
五、 实验步骤
-  把仿真器与iCore4的SWD调试口相连(直接相连或者通过转接器相连); 
-  把iCore4通过Micro USB线与计算机相连,为iCore4供电; 
-  打开Keil MDK 开发环境,并打开本实验工程; 
-  烧写程序到iCore4上; 
-  也可以进入Debug模式,单步运行或设置断点验证程序逻辑。 
 
六、 实验现象