引入
随着单片机的性能提高,我们想要在单片机上实现的功能有时需要外扩彩色屏幕,但是单片机的内存太小,而屏幕需要的缓存又太大,因此扩展出了外置的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)
{
}
}
最后的结果: