STM32——“扩展动态随机存储器SDRAM”

发布于:2025-06-20 ⋅ 阅读:(21) ⋅ 点赞:(0)

引入

        随着单片机的性能提高,我们想要在单片机上实现的功能有时需要外扩彩色屏幕,但是单片机的内存太小,而屏幕需要的缓存又太大,因此扩展出了外置的RAM,RAM又分为静态随机存储器和动态随机存储器,在这里我介绍动态随机存储器SDRAM在STM32上的配置。

一、SDRAM简介

        SDRAM是动态随机存储器(Synchronous Dynamic Random-Access Memory)的简称,相比较SRAM,他的读写速率可能没有SRAM那么快,因为需要留时间给刷新,但是他的造价低,容量一般可以做的很大。本章节用的是基于野火的STM32F429-V1开发板。

二、SDRAM在STM32上的配置

        SDRAM的使用需要用到FMC,即“可变存储控制器”,本章节以STM32F429IGT6为例(FMC在F4系列中,支持的型号仅仅只有“STM32F42xxx 和 STM32F43xxx”),且在STM32F4的中文草考手册中没有,反而在另外一个F4的参考手册中提及。、

        本章节代码采用HAL库,要用FMC驱动SDRAM就得进行初始化。

        初始化代码大概分为以下步骤:

        1.初始化GPIO,开启时钟,并且复用为FMC

        GPIO的初始化可以由CubeMX配置,因为引脚很多,这里配置BANK2。

static void SDRAM_GPIO_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStruct = {0};

    /* Peripheral clock enable */
    __HAL_RCC_FMC_CLK_ENABLE();
    __HAL_RCC_GPIOF_CLK_ENABLE();
    __HAL_RCC_GPIOH_CLK_ENABLE();
    __HAL_RCC_GPIOC_CLK_ENABLE();
    __HAL_RCC_GPIOG_CLK_ENABLE();
    __HAL_RCC_GPIOE_CLK_ENABLE();
    __HAL_RCC_GPIOD_CLK_ENABLE();
    __HAL_RCC_GPIOA_CLK_ENABLE();
    /* GPIO_InitStruct */
    
  /** FMC GPIO Configuration
  PF0   ------> FMC_A0
  PF1   ------> FMC_A1
  PF2   ------> FMC_A2
  PF3   ------> FMC_A3
  PF4   ------> FMC_A4
  PF5   ------> FMC_A5
  PC0   ------> FMC_SDNWE
  PF11   ------> FMC_SDNRAS
  PF12   ------> FMC_A6
  PF13   ------> FMC_A7
  PF14   ------> FMC_A8
  PF15   ------> FMC_A9
  PG0   ------> FMC_A10
  PG1   ------> FMC_A11
  PE7   ------> FMC_D4
  PE8   ------> FMC_D5
  PE9   ------> FMC_D6
  PE10   ------> FMC_D7
  PE11   ------> FMC_D8
  PE12   ------> FMC_D9
  PE13   ------> FMC_D10
  PE14   ------> FMC_D11
  PE15   ------> FMC_D12
  PH6   ------> FMC_SDNE1
  PH7   ------> FMC_SDCKE1
  PD8   ------> FMC_D13
  PD9   ------> FMC_D14
  PD10   ------> FMC_D15
  PD14   ------> FMC_D0
  PD15   ------> FMC_D1
  PG4   ------> FMC_BA0
  PG5   ------> FMC_BA1
  PG8   ------> FMC_SDCLK
  PD0   ------> FMC_D2
  PD1   ------> FMC_D3
  PG15   ------> FMC_SDNCAS
  PE0   ------> FMC_NBL0
  PE1   ------> FMC_NBL1
  */
    GPIO_InitStruct.Pin = GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_3
                          |GPIO_PIN_4|GPIO_PIN_5|GPIO_PIN_11|GPIO_PIN_12
                          |GPIO_PIN_13|GPIO_PIN_14|GPIO_PIN_15;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
    GPIO_InitStruct.Alternate = GPIO_AF12_FMC;

    HAL_GPIO_Init(GPIOF, &GPIO_InitStruct);

    /* GPIO_InitStruct */
    GPIO_InitStruct.Pin = GPIO_PIN_0;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
    GPIO_InitStruct.Alternate = GPIO_AF12_FMC;

    HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);

    /* GPIO_InitStruct */
    GPIO_InitStruct.Pin = GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_4|GPIO_PIN_5
                          |GPIO_PIN_8|GPIO_PIN_15;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
    GPIO_InitStruct.Alternate = GPIO_AF12_FMC;

    HAL_GPIO_Init(GPIOG, &GPIO_InitStruct);

    /* GPIO_InitStruct */
    GPIO_InitStruct.Pin = GPIO_PIN_7|GPIO_PIN_8|GPIO_PIN_9|GPIO_PIN_10
                          |GPIO_PIN_11|GPIO_PIN_12|GPIO_PIN_13|GPIO_PIN_14
                          |GPIO_PIN_15|GPIO_PIN_0|GPIO_PIN_1;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
    GPIO_InitStruct.Alternate = GPIO_AF12_FMC;

    HAL_GPIO_Init(GPIOE, &GPIO_InitStruct);

    /* GPIO_InitStruct */
    GPIO_InitStruct.Pin = GPIO_PIN_6|GPIO_PIN_7;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
    GPIO_InitStruct.Alternate = GPIO_AF12_FMC;

    HAL_GPIO_Init(GPIOH, &GPIO_InitStruct);

    /* GPIO_InitStruct */
    GPIO_InitStruct.Pin = GPIO_PIN_8|GPIO_PIN_9|GPIO_PIN_10|GPIO_PIN_14
                          |GPIO_PIN_15|GPIO_PIN_0|GPIO_PIN_1;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
    GPIO_InitStruct.Alternate = GPIO_AF12_FMC;

    HAL_GPIO_Init(GPIOD, &GPIO_InitStruct);

}

        2.初始化SDRAM控制器

        SDRAM控制器的相关参数可以在手册中找到:

    SDRAM_HandleTypeDef hsdram2;

    FMC_SDRAM_TimingTypeDef SdramTiming = {0};
    
    hsdram2.Instance = FMC_SDRAM_DEVICE;
    /* hsdram2.Init */
    hsdram2.Init.SDBank = FMC_SDRAM_BANK2;									// 初始化 BANK2
    hsdram2.Init.ColumnBitsNumber = FMC_SDRAM_COLUMN_BITS_NUM_8;			// 列地址位 8
    hsdram2.Init.RowBitsNumber = FMC_SDRAM_ROW_BITS_NUM_12;					//行地址位   12
    hsdram2.Init.MemoryDataWidth = FMC_SDRAM_MEM_BUS_WIDTH_16;				//数据宽度 16位
    hsdram2.Init.InternalBankNumber = FMC_SDRAM_INTERN_BANKS_NUM_4;			//SDRAM的BANK的数量 4
    hsdram2.Init.CASLatency = FMC_SDRAM_CAS_LATENCY_3;						//CAS延时 3 个周期
    hsdram2.Init.WriteProtection = FMC_SDRAM_WRITE_PROTECTION_DISABLE;		//失能写保护
    hsdram2.Init.SDClockPeriod = FMC_SDRAM_CLOCK_PERIOD_2;					//SDRAM时钟分频 2 分频 180/2 < 143
    hsdram2.Init.ReadBurst = FMC_SDRAM_RBURST_ENABLE;						//使能读突发
    hsdram2.Init.ReadPipeDelay = FMC_SDRAM_RPIPE_DELAY_0;					//管道延时 0
    /* SdramTiming */
    SdramTiming.LoadToActiveDelay = 2;											//退出自刷新延时 tXSR
    SdramTiming.SelfRefreshTime = 5;									//加载模式寄存器到激活 tMRD
    SdramTiming.ExitSelfRefreshDelay = 7;									//自刷新延时tRAS
    SdramTiming.RowCycleDelay = 7;											//行循环延时 tRC
    SdramTiming.WriteRecoveryTime = 3;										//写恢复时间 tWR
    SdramTiming.RPDelay = 2;												//行预充电延时 tRP
    SdramTiming.RCDDelay = 2;												//行到列延时 tRCD

    HAL_SDRAM_Init(&hsdram2, &SdramTiming);//初始化SDRAM外设

        此图的Row Address 代表行地址引脚,一共12个,Column Address代表列地址的引脚,一共8个他们是分时复用的,第一个参数页提示了这个SDRAM有四个bank,以及数据宽度。

        图一显示的是不同芯片的最大时钟以及其他配置选项,429的始终是180MHz,而核心板的芯片是-7,因此最大频率不超过143(CAS为3,这里配置为3),因此配置 :

hsdram2.Init.SDClockPeriod = FMC_SDRAM_CLOCK_PERIOD_2;   //SDRAM时钟分频 2 分频 180/2 < 143 

       90MHz对应的时间大致为11ns

        此外的其他配置,如写保护给失能,管道延时(跟在CAS延时后边)为0,并且使能突发读。

此外还需配置“7个时间”,用来配合SDRAM控制器和SDRAM的通信。

        1.退出自刷新时间 tXSR

他在手册中定义如下(从左到右依次是-5 -6 -7):

        这里为不小于70ns,那就配置为7

SdramTiming.ExitSelfRefreshDelay = 7;

        2.加载模式寄存器到激活时间 tMRD

这里为两个时钟周期,那么就为2

SdramTiming.LoadToActiveDelay = 2;

        3.自刷新时间tRAS

这里为不小于42ns,就填为5

SdramTiming.SelfRefreshTime = 5;

        4.行循环延时 tRC

        这里最小为63ns,则配置为7

SdramTiming.RowCycleDelay = 7;	

        5.写恢复时间 tWR

        这里最小为两个时钟,则配置为3(满足某个特定条件: tWR >= tRAS - tRCD)

SdramTiming.WriteRecoveryTime = 3;

        6.行预充电延时 tRP

这里不低于15ns,则配置为2

SdramTiming.RPDelay = 2;

        7.行到列延时 tRCD

这里不低于15ns,则配置为2

SdramTiming.RCDDelay = 2;

最后初始化SDRAM控制器

        3.发送控制命令到SDRAM

发送命令由函数HAL_SDRAM_SendCommand完成

                a.给 SDRAM 提供时钟

                b.延时至少100us

                c.给 SDRAM 的所有 BANK 预充电

                d.插入8个自动刷新周期

                e.配置加载模式寄存器

                f.配置 SDRAM 自动刷新周期

发送命令代码如下:


static void SDRAM_Cmmand(void)
{
    FMC_SDRAM_CommandTypeDef FMC_SDRAM_CommandStruct = {0};
    
//1 给 SDRAM 提供时钟
    FMC_SDRAM_CommandStruct.CommandMode = FMC_SDRAM_CMD_CLK_ENABLE;			//启用时钟
    FMC_SDRAM_CommandStruct.AutoRefreshNumber = 1;							//自动刷新数(无关命令,写默认值
    FMC_SDRAM_CommandStruct.CommandTarget = FMC_SDRAM_CMD_TARGET_BANK2;		//发送到BANK2
    FMC_SDRAM_CommandStruct.ModeRegisterDefinition = 0;						//加载模式寄存器值(无关命令,写默认值
    
    HAL_SDRAM_SendCommand(&hsdram2,&FMC_SDRAM_CommandStruct,TIME_OUT);
//2 延时至少100us
    
    HAL_Delay(1);

//3 给 SDRAM 的所有 BANK 预充电
    FMC_SDRAM_CommandStruct.CommandMode = FMC_SDRAM_CMD_PALL;				//预充电
    FMC_SDRAM_CommandStruct.AutoRefreshNumber = 1;							//自动刷新数(无关命令,写默认值
    FMC_SDRAM_CommandStruct.CommandTarget = FMC_SDRAM_CMD_TARGET_BANK2;		//发送到BANK2
    FMC_SDRAM_CommandStruct.ModeRegisterDefinition = 0;						//加载模式寄存器值(无关命令,写默认值
    
    HAL_SDRAM_SendCommand(&hsdram2,&FMC_SDRAM_CommandStruct,TIME_OUT);
    
//4 插入8个自动刷新周期
    FMC_SDRAM_CommandStruct.CommandMode = FMC_SDRAM_CMD_AUTOREFRESH_MODE;	//自动刷新
    FMC_SDRAM_CommandStruct.AutoRefreshNumber = 4;							//自动刷新数
    FMC_SDRAM_CommandStruct.CommandTarget = FMC_SDRAM_CMD_TARGET_BANK2;		//发送到BANK2
    FMC_SDRAM_CommandStruct.ModeRegisterDefinition = 0;						//加载模式寄存器值(无关命令,写默认值
    
    HAL_SDRAM_SendCommand(&hsdram2,&FMC_SDRAM_CommandStruct,TIME_OUT);
//5 配置加载模式寄存器

    uint32_t ModeRegister = SDRAM_MODEREG_BURST_LENGTH_4 \
                          | SDRAM_MODEREG_BURST_TYPE_SEQUENTIAL\
                          | SDRAM_MODEREG_CAS_LATENCY_3\
                          | SDRAM_MODEREG_OPERATING_MODE_STANDARD\
                          | SDRAM_MODEREG_WRITEBURST_MODE_SINGLE;
                          
                           
    FMC_SDRAM_CommandStruct.CommandMode = FMC_SDRAM_CMD_LOAD_MODE;			//加载模式寄存器
    FMC_SDRAM_CommandStruct.AutoRefreshNumber = 1;							//自动刷新数(无关命令,写默认值
    FMC_SDRAM_CommandStruct.CommandTarget = FMC_SDRAM_CMD_TARGET_BANK2;		//发送到BANK2
    FMC_SDRAM_CommandStruct.ModeRegisterDefinition = ModeRegister;			//加载模式寄存器值
    
    HAL_SDRAM_SendCommand(&hsdram2,&FMC_SDRAM_CommandStruct,TIME_OUT);
    
    
//6 配置 SDRAM 自动刷新周期  
    HAL_SDRAM_ProgramRefreshRate(&hsdram2,1386); 							//(64x10^-6 s /4096) x 90x10^6Hz - 20

}

                其中,初始阿虎过程可以在手册中找到:

翻译大致如下(可能不准确,以英文原文为主)

初始化(Initialization)

SDRAM 必须按照预定义的方式上电并初始化。

        当电源同时加到 VDD 和 VDDQ上,并且时钟稳定(DQM 为高电平,CKE 为高电平)之后,64Mb 的 SDRAM 才会开始初始化。

        在发送除 COMMAND INHIBIT 或 NOP 命令之外的任何命令之前,需要等待 100 微秒。在这 100 微秒期间,可以持续发送 COMMAND INHIBIT 或 NOP 命令,并应至少持续到 100 微秒结束。

        在至少发送一次 COMMAND INHIBIT 或 NOP 命令之后,一旦 100 微秒延时完成,就应发送 PRECHARGE 命令,所有的 bank 必须被预充电(Precharged)。这将使所有 bank 进入空闲状态。

        随后,至少执行两次自动刷新(AUTO REFRESH)命令。自动刷新命令完成之后,SDRAM 就可以准备好进行模式寄存器(Mode Register)的设置。

        在执行任何操作命令之前,应先加载模式寄存器,因为上电后 SDRAM 处于未知状态。执行完 Load Mode Register 命令之后,至少要执行一次 NOP 命令,然后才可以执行其他命令。

        总结起来和以上配置并无二异,不过最后还得配置 SDRAM 自动刷新周期 ,他的计算公式如下图(手册中):

由SDRAM手册可知:

        行数为4096行,SDRAM的时钟已经被设置为90MHz,刷新4096次需要64ms

那么最后的结果就是:

(64x10^-6 s /4096) x 90x10^6Hz - 20 = 1386

整个初始化代码就是:

void SDRAM_Init(void)
{

    SDRAM_GPIO_Init();//初始化GPIO

    FMC_SDRAM_TimingTypeDef SdramTiming = {0};
    
    hsdram2.Instance = FMC_SDRAM_DEVICE;
    /* hsdram2.Init */
    hsdram2.Init.SDBank = FMC_SDRAM_BANK2;									// 初始化 BANK2
    hsdram2.Init.ColumnBitsNumber = FMC_SDRAM_COLUMN_BITS_NUM_8;			// 列地址位 8
    hsdram2.Init.RowBitsNumber = FMC_SDRAM_ROW_BITS_NUM_12;					//行地址位   12
    hsdram2.Init.MemoryDataWidth = FMC_SDRAM_MEM_BUS_WIDTH_16;				//数据宽度 16位
    hsdram2.Init.InternalBankNumber = FMC_SDRAM_INTERN_BANKS_NUM_4;			//SDRAM的BANK的数量 4
    hsdram2.Init.CASLatency = FMC_SDRAM_CAS_LATENCY_3;						//CAS延时 3 个周期
    hsdram2.Init.WriteProtection = FMC_SDRAM_WRITE_PROTECTION_DISABLE;		//失能写保护
    hsdram2.Init.SDClockPeriod = FMC_SDRAM_CLOCK_PERIOD_2;					//SDRAM时钟分频 2 分频 180/2 < 133
    hsdram2.Init.ReadBurst = FMC_SDRAM_RBURST_ENABLE;						//使能读突发
    hsdram2.Init.ReadPipeDelay = FMC_SDRAM_RPIPE_DELAY_0;					//管道延时 0
    /* SdramTiming */
    SdramTiming.LoadToActiveDelay = 2;										//加载模式寄存器到激活 tMRD
    SdramTiming.ExitSelfRefreshDelay = 7;									//退出自刷新延时 tXSR
    SdramTiming.SelfRefreshTime = 5;										//自刷新延时tRAS
    SdramTiming.RowCycleDelay = 7;											//行循环延时 tRC
    SdramTiming.WriteRecoveryTime = 3;										//写恢复时间 tWR
    SdramTiming.RPDelay = 2;												//行预充电延时 tRP
    SdramTiming.RCDDelay = 2;												//行到列延时 tRCD

    HAL_SDRAM_Init(&hsdram2, &SdramTiming);//初始化SDRAM外设
    
    SDRAM_Cmmand();//初始化SDRAM
}

        在主函数调用,即可通过指针访问SDRAM,由于这里是用的BANK2,因此它的起始地址是0xD0000000 。

        在主函数中将一张150kb的图片复制到SDRAM,然后读出来显示在屏幕中:

int main()
{
	HAL_Init();
	SystemClock_Config();
	UsartInit(115200);
    SDRAM_Init();
	SPI_LCD_Init();
	
	LCD_SetColor(COLOR_LIME);
	LCD_SetBackColor(COLOR_BLACK);
	LCD_Clear();
	LCD_SetDirection(Direction_H_Flip);
	LCD_SetTextFont(&CH_Font32);
    
    uint32_t* add1 = (uint32_t*)0xD0000000;
    memcpy(add1,gImage_cat,153600);

    LCD_DrawColorImage(0,0,320,240,(uint8_t*)add1);
	while(1)
	{


	}
}

        最后的结果:


网站公告

今日签到

点亮在社区的每一天
去签到