一、FMC配置
cpp
SDRAM_HandleTypeDef hsdram1;
void FMC_Init(void)
{
FMC_SDRAM_TimingTypeDef SdramTiming = {0};
hsdram1.Instance = FMC_SDRAM_DEVICE;
hsdram1.Init.SDBank = FMC_SDRAM_BANK1;
hsdram1.Init.ColumnBitsNumber = FMC_SDRAM_COLUMN_BITS_NUM_9;
hsdram1.Init.RowBitsNumber = FMC_SDRAM_ROW_BITS_NUM_13;
hsdram1.Init.MemoryDataWidth = FMC_SDRAM_MEM_BUS_WIDTH_16;
hsdram1.Init.InternalBankNumber = FMC_SDRAM_INTERN_BANKS_NUM_4;
hsdram1.Init.CASLatency = FMC_SDRAM_CAS_LATENCY_3;
hsdram1.Init.WriteProtection = FMC_SDRAM_WRITE_PROTECTION_DISABLE;
hsdram1.Init.SDClockPeriod = FMC_SDRAM_CLOCK_PERIOD_2;
hsdram1.Init.ReadBurst = FMC_SDRAM_RBURST_ENABLE;
hsdram1.Init.ReadPipeDelay = FMC_SDRAM_RPIPE_DELAY_1;
/* SdramTiming 120M 约等于8.333ns*/
SdramTiming.LoadToActiveDelay = 2;
SdramTiming.ExitSelfRefreshDelay = 9; //退出自刷新延迟 > 72ns
SdramTiming.SelfRefreshTime = 6; //自动刷新延迟 > 42ns
SdramTiming.RowCycleDelay = 8; //行循环延迟 > 60ns
SdramTiming.WriteRecoveryTime = 3; //写恢复延迟 >= 2TCK
SdramTiming.RPDelay = 3; //行预充电延迟 >18ns
SdramTiming.RCDDelay = 3; //行到列的延迟 >18ns
HAL_SDRAM_Init(&hsdram1, &SdramTiming);
}
(1)hsdram1.Init.SDBank = FMC_SDRAM_BANK1;这里选择的是BANK1
这两个地址区域分别对应FMC控制器的两个独立片选信号(SDNE0/SDCKE0对应Bank1,SDNE1/SDCKE1对应Bank2)。实际使用的是哪个Bank,取决于SDRAM芯片的片选引脚连接到了哪个FMC片选信号线上,从而对应的SDRAM起始地址也不一样。
|-----------------|------------|
| FMC SDRAM Bank | 起始地址 |
| FMC_SDRAM_BANK1 | 0xC0000000 |
| FMC_SDRAM_BANK2 | 0xD0000000 |
(2)hsdram1.Init.ColumnBitsNumber = FMC_SDRAM_COLUMN_BITS_NUM_9;
、hsdram1.Init.RowBitsNumber = FMC_SDRAM_ROW_BITS_NUM_13、hsdram1.Init.MemoryDataWidth = FMC_SDRAM_MEM_BUS_WIDTH_16、hsdram1.Init.InternalBankNumber = FMC_SDRAM_INTERN_BANKS_NUM_4;
这里是SDRAM的行地址和列地址线的数量以及总线宽度和BANK的数量。

如图上所示,8192=2的13次方,512是2的9次方,16是总线宽度,BANK一共4个分别为BANK0,BANK1,BANK2,BANK3,所以就得到了上面的参数。
(3)hsdram1.Init.CASLatency = FMC_SDRAM_CAS_LATENCY_3;

CAS有2和3两个选项,不同的CAS对应不同的参数。
(4)hsdram1.Init.WriteProtection = FMC_SDRAM_WRITE_PROTECTION_DISABLE;
一般都是禁用写保护,除非用于只读,不向内存写数据。
(5)hsdram1.Init.SDClockPeriod = FMC_SDRAM_CLOCK_PERIOD_2;
2分频,因为我给fmc的时钟源是240M,分频后是120M.
(6)hsdram1.Init.ReadBurst = FMC_SDRAM_RBURST_ENABLE;
使能突发模式,以得到更高的吞吐量。
(7)hsdram1.Init.ReadPipeDelay = FMC_SDRAM_RPIPE_DELAY_1;
定义在CAS延迟之后,再额外等待多少个HCLK时钟周期才读取数据。这个数据根据实际情况微调即可在不出问题的情况下越小越好,需要结合PCB布线长度等调整。
(9)SdramTiming.LoadToActiveDelay = 2;
对应第(3)节图片中的紫色框起来的部分,最小值为2个时钟。
(10)SdramTiming.ExitSelfRefreshDelay = 9
对应第(3)节图片中的黑色框起来的部分,最小值为72ns,我配置的时钟是120M,也就是说一个时钟的时间是8.333ns,所以需要9个时钟周期。
(11)SdramTiming.SelfRefreshTime = 6
对应第(3)节图片中的绿色框起来的部分,最小值为42ns,这里我设置为6个时钟。
(12)SdramTiming.RowCycleDelay = 8
对应第(3)节图片中tRC的部分,最小值为60ns,所以设置为8个时钟。
(13)SdramTiming.WriteRecoveryTime = 3
对应第(3)节图片中红色框起来的部分,最小值为2个时钟,这里我设置为3个时钟。
(14)SdramTiming.RPDelay = 3
对应第(3)节图片中灰色框起来的部分tRP,最小值为18ns,这里我设置为3个时钟。
(15)SdramTiming.RCDDelay = 3
对应第(3)节图片中的tRCD,最小值为18ns,这里我设置为3个时钟。
二、初始化SDRAM时序
sdram程序如下
cpp
/*向SDRAM发送命令*/
bool SDRAM_SendCmd(uint8_t cmd,uint8_t refreshNum,uint16_t reg)
{
FMC_SDRAM_CommandTypeDef Command;
Command.CommandMode=cmd;
Command.CommandTarget=FMC_SDRAM_CMD_TARGET_BANK1;
Command.AutoRefreshNumber=refreshNum;
Command.ModeRegisterDefinition=reg;
if(HAL_SDRAM_SendCommand(&hsdram1,&Command,1000) == HAL_OK) //发送
{
return true;
}
else
{
return false;
}
}
//初始化SDRAM 序列
bool SDRAM_InitSequence(SDRAM_HandleTypeDef *hsdram)
{
uint32_t data = 0;
bool ret = true;
if(SDRAM_SendCmd(FMC_SDRAM_CMD_CLK_ENABLE,1,0) == false) //时钟使能
{
ret = false;
}
HAL_Delay(1);
if(SDRAM_SendCmd(FMC_SDRAM_CMD_PALL,1,0) == false) //对所有存储区预充电
{
ret = false;
}
if(SDRAM_SendCmd(FMC_SDRAM_CMD_AUTOREFRESH_MODE,8,0) == false) //设置自刷新次数
{
ret = false;
}
data = (uint32_t)SDRAM_MODEREG_BURST_LENGTH_1 | //突发访问长度为1
SDRAM_MODEREG_BURST_TYPE_SEQUENTIAL | //突发访问类型为连续
SDRAM_MODEREG_CAS_LATENCY_3 | //CAS值为3
SDRAM_MODEREG_OPERATING_MODE_STANDARD | //运行模式为标准模式
SDRAM_MODEREG_WRITEBURST_MODE_SINGLE; //写突发模式为单点访问
if(SDRAM_SendCmd(FMC_SDRAM_CMD_LOAD_MODE,1,data) == false) //设置模式寄存器
{
ret = false;
}
//COUNT = (SDRAM刷新周期/行数)/SDRAM 时钟频率 -- 20
//917 = (64ms/8192)/1000*120M - 20 /1000是将us转化为ns 向下取整,64ms为最大允许值
if(HAL_SDRAM_ProgramRefreshRate(&hsdram1,917) != HAL_OK)
{
ret = false;
}
return ret;
}
//初始化SDRAM
bool SDRAM_Init(void)
{
return SDRAMInitSequence(&hsdram1);
}
cpp
#define SDRAM_MODEREG_BURST_LENGTH_1 ((uint16_t)0x0000)
#define SDRAM_MODEREG_BURST_LENGTH_2 ((uint16_t)0x0001)
#define SDRAM_MODEREG_BURST_LENGTH_4 ((uint16_t)0x0002)
#define SDRAM_MODEREG_BURST_LENGTH_8 ((uint16_t)0x0004)
#define SDRAM_MODEREG_BURST_TYPE_SEQUENTIAL ((uint16_t)0x0000)
#define SDRAM_MODEREG_BURST_TYPE_INTERLEAVED ((uint16_t)0x0008)
#define SDRAM_MODEREG_CAS_LATENCY_2 ((uint16_t)0x0020)
#define SDRAM_MODEREG_CAS_LATENCY_3 ((uint16_t)0x0030)
#define SDRAM_MODEREG_OPERATING_MODE_STANDARD ((uint16_t)0x0000)
#define SDRAM_MODEREG_WRITEBURST_MODE_PROGRAMMED ((uint16_t)0x0000)
#define SDRAM_MODEREG_WRITEBURST_MODE_SINGLE ((uint16_t)0x0200)
//初始化SDRAM
bool SDRAM_Init(void);
(1)这里需要注意的就是这里配置的是CAS为3,FMC配置的CAS也要为3,必须相同。
(2)这里的刷新计数器的值计算出来一般都是带小数的,要选择向下取整,原因是,要求是最小64ms刷新所有行,所以不能比这个更大。
3.SDRAM导致的程序HardFault的问题
(1)情况描述
我使用SDRAM的原因是作为屏幕的显存以及LVGL的缓存,在跑UI程序的时候总是跑个几天就会死机。
(2)排查
死机后通过在线仿真,发现程序死在了LVGL的颜色渲染的一个函数中。接着就使用全局变量记录死在函数中时的各个变量的值,最终发现是里面的for循环溢出导致数组越界。再具体的排查过程就不说了,因为各种怀疑是自己程序导致这段代码出错了。最后根据自己经验定位到是SDRAM时序配置不对导致SDRAM数据出错从而导致程序跑飞了,关机是跑SDRAM的测试程序还短时间内跑不出错误。
(3)原因
之所以配置错误是因为之前使用的时钟是100M,修改为120M时没有修改这些参数。