引入
随着单片机的性能提高,我们想要在单片机上实现的功能有时需要外扩彩色屏幕,但是单片机的内存太小,而屏幕需要的缓存又太大,因此扩展出了外置的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 ConfigurationPF0 ------> FMC_A0PF1 ------> FMC_A1PF2 ------> FMC_A2PF3 ------> FMC_A3PF4 ------> FMC_A4PF5 ------> FMC_A5PC0 ------> FMC_SDNWEPF11 ------> FMC_SDNRASPF12 ------> FMC_A6PF13 ------> FMC_A7PF14 ------> FMC_A8PF15 ------> FMC_A9PG0 ------> FMC_A10PG1 ------> FMC_A11PE7 ------> FMC_D4PE8 ------> FMC_D5PE9 ------> FMC_D6PE10 ------> FMC_D7PE11 ------> FMC_D8PE12 ------> FMC_D9PE13 ------> FMC_D10PE14 ------> FMC_D11PE15 ------> FMC_D12PH6 ------> FMC_SDNE1PH7 ------> FMC_SDCKE1PD8 ------> FMC_D13PD9 ------> FMC_D14PD10 ------> FMC_D15PD14 ------> FMC_D0PD15 ------> FMC_D1PG4 ------> FMC_BA0PG5 ------> FMC_BA1PG8 ------> FMC_SDCLKPD0 ------> FMC_D2PD1 ------> FMC_D3PG15 ------> FMC_SDNCASPE0 ------> FMC_NBL0PE1 ------> 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; // 初始化 BANK2hsdram2.Init.ColumnBitsNumber = FMC_SDRAM_COLUMN_BITS_NUM_8; // 列地址位 8hsdram2.Init.RowBitsNumber = FMC_SDRAM_ROW_BITS_NUM_12; //行地址位 12hsdram2.Init.MemoryDataWidth = FMC_SDRAM_MEM_BUS_WIDTH_16; //数据宽度 16位hsdram2.Init.InternalBankNumber = FMC_SDRAM_INTERN_BANKS_NUM_4; //SDRAM的BANK的数量 4hsdram2.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 < 143hsdram2.Init.ReadBurst = FMC_SDRAM_RBURST_ENABLE; //使能读突发hsdram2.Init.ReadPipeDelay = FMC_SDRAM_RPIPE_DELAY_0; //管道延时 0/* SdramTiming */SdramTiming.LoadToActiveDelay = 2; //退出自刷新延时 tXSRSdramTiming.SelfRefreshTime = 5; //加载模式寄存器到激活 tMRDSdramTiming.ExitSelfRefreshDelay = 7; //自刷新延时tRASSdramTiming.RowCycleDelay = 7; //行循环延时 tRCSdramTiming.WriteRecoveryTime = 3; //写恢复时间 tWRSdramTiming.RPDelay = 2; //行预充电延时 tRPSdramTiming.RCDDelay = 2; //行到列延时 tRCDHAL_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; //发送到BANK2FMC_SDRAM_CommandStruct.ModeRegisterDefinition = 0; //加载模式寄存器值(无关命令,写默认值HAL_SDRAM_SendCommand(&hsdram2,&FMC_SDRAM_CommandStruct,TIME_OUT);
//2 延时至少100usHAL_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; //发送到BANK2FMC_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; //发送到BANK2FMC_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; //发送到BANK2FMC_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();//初始化GPIOFMC_SDRAM_TimingTypeDef SdramTiming = {0};hsdram2.Instance = FMC_SDRAM_DEVICE;/* hsdram2.Init */hsdram2.Init.SDBank = FMC_SDRAM_BANK2; // 初始化 BANK2hsdram2.Init.ColumnBitsNumber = FMC_SDRAM_COLUMN_BITS_NUM_8; // 列地址位 8hsdram2.Init.RowBitsNumber = FMC_SDRAM_ROW_BITS_NUM_12; //行地址位 12hsdram2.Init.MemoryDataWidth = FMC_SDRAM_MEM_BUS_WIDTH_16; //数据宽度 16位hsdram2.Init.InternalBankNumber = FMC_SDRAM_INTERN_BANKS_NUM_4; //SDRAM的BANK的数量 4hsdram2.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 < 133hsdram2.Init.ReadBurst = FMC_SDRAM_RBURST_ENABLE; //使能读突发hsdram2.Init.ReadPipeDelay = FMC_SDRAM_RPIPE_DELAY_0; //管道延时 0/* SdramTiming */SdramTiming.LoadToActiveDelay = 2; //加载模式寄存器到激活 tMRDSdramTiming.ExitSelfRefreshDelay = 7; //退出自刷新延时 tXSRSdramTiming.SelfRefreshTime = 5; //自刷新延时tRASSdramTiming.RowCycleDelay = 7; //行循环延时 tRCSdramTiming.WriteRecoveryTime = 3; //写恢复时间 tWRSdramTiming.RPDelay = 2; //行预充电延时 tRPSdramTiming.RCDDelay = 2; //行到列延时 tRCDHAL_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){}
}
最后的结果: