|  **银杏科技有限公司旗下技术文档发布平台**  ||||
|技术支持电话|**0379-69926675-801**|||
|技术支持邮件|Gingko@vip.163.com|||
^  版本  ^  日期  ^  作者  ^  修改内容  ^
|  V1.0  |  2020-07-12  |  gingko  |  初次建立  | 
===== 实验三十七:SDRAM实验——读写SDRAM =====
==== 一、 实验目的与意义 ====
  - 了解STM32 SDRAM结构。
  - 了解STM32 SDRAM特征。
  - 掌握SDRAM的使用方法。
  - 掌握STM32 HAL库中SDRAM属性的配置方法。
  - 掌握KEILMDK 集成开发环境使用方法。
==== 二、 实验设备及平台 ====
  - iCore4 双核心板[[https://item.taobao.com/item.htm?spm=a1z10.1-c-s.w4004-22598974120.15.5923532fsFrHiE&id=551864196684|点击购买]]。
  - JLINK(或相同功能)仿真器[[https://item.taobao.com/item.htm?id=554869837940|点击购买]]。
  - 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器件的引脚分为以下几类。
    * (1)控制信号:包括片选、时钟、时钟使能、行列地址选择、读写有效及数据有效。
    * (2)地址信号:时分复用引脚,根据行列地址选择引脚,控制输入的地址为行地址或列地址。
    * (3)数据信号:双向引脚,受数据有效控制。
  * SDRAM的所有操作都同步于时钟。根据时钟上升沿控制管脚和地址输入的状态,可以产生多种输入命令:
    * 模式寄存器设置命令
    * 激活命令
    * 预充命令
    * 读命令
    * 写命令
    * 带预充的读命令
    * 带预充的写命令
    * 自动刷新命令
    * 自我刷新命令
    * 突发停命令
    * 空操作命令
  * 根据输入命令,SDRAM状态在内部状态间转移。内部状态包括模式寄存器设置状态、激活状态、预充状态、写状态、读状态、预充读状态、预充写状态、自动刷新状态及自我刷新状态。
  * SDRAM支持的操作命令有初始化配置、预充电、行激活、读操作、写操作、自动刷新、自刷新等。所有的操作命令通过控制线CS#、RAS#、CAS#、WE#和地址线、体选地址BA输入。
=== 2、SDRAM信号线 ===
  * SDRAM的信号线如图所示:
{{ :icore4:icore4_arm_hal_37_1.png?direct&750 |}} 
=== 3、SDRAM存储单元 ===
  * SDRAM的存储单元(称之为:BANK)是以阵列的形式排列的,每个存储单元的结构示意图,如图所示:
{{ :icore4:icore4_arm_hal_37_2.png?direct&600 |}}  
  * 对于这个存储阵列,我们可以将其看成是一个表格,只需要给定行地址和列地址,就可以确定其唯一位置,这就是SDRAM寻址的基本原理。而一个SDRAM芯片内部,一般又有4个这样的存储单元(BANK),所以,在SDRAM内部寻址的时候,先指定BANK号和行地址,然后再指定列地址,就可以查找到目标地址。
  * SDRAM的存储结构示意图,如图下所示,寻址的时候,首先RAS信号为低电平,选通行地址,地址线A0~A12所表示的地址,会被传输并锁存到行地址译码器里面,最为行地址,同时BANK地址线上面的BS0,BS1所表示的BANK地址,也会被锁存,选中对应的BANK,然后,CAS信号为低电平,选通列地址,地址线A0~A12所表示的地址,会被传输并锁存到列地址译码器里面,作为列地址,这样,就完成了一次寻址。
{{ :icore4:icore4_arm_hal_37_3.png?direct |}}  
=== 4、数据传输 ===
  * 在完成寻址以后,数据线DQ0~DQ15上面的数据会通过数据控制逻辑写入(或读出)存储阵列。特别注意:因为SDRAM的位宽,可以达到32位,也就是最多有32条数据线,在实际使用的时候,我们可能会以:8位、16位、24位和32位等宽度来读写数据,这样的话,并不是每条数据线,都会被使用到,未被用到的数据线上面的数据,必须被忽略,这个时候就需要用到数据掩码(DQM)线来控制了,每一个数据掩码线,对应8个位的数据,低电平表示对应数据位有效,高电平表示对应数据位无效。
  * SDRAM的驱动需要用到一些命令,常用的命令如图所示:
{{ :icore4:icore4_arm_hal_37_4.png?direct |}} 
=== 5、原理图 ===
{{ :icore4:icore4_arm_hal_37_5.png?direct |}} 
==== 四、 实验程序 ====
=== 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模式,单步运行或设置断点验证程序逻辑。
==== 六、 实验现象 ====
  * 上电即开始读写SDRAM测试,测试过程中,蓝色LED点亮,如果出现错误,红色LED闪烁,测试成功,绿色LED点亮。