STM32+FMC驱动W9825G6 SDRAM程序以及遇到的问题讲解

一、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时没有修改这些参数。

相关推荐
少年潜行1 小时前
【开源】STM32驱动BH1750(附开源代码)
单片机
0南城逆流01 小时前
【STM32】知识点介绍八:UART/USART串口功能
stm32·单片机·嵌入式硬件
国家一级保护废物...2 小时前
51单片机day1
单片机·嵌入式硬件·51单片机
小白学电子_2 小时前
STM32常用HAL常见库函数快速运用和讲解
stm32·单片机·嵌入式硬件
woshihonghonga2 小时前
解决Eclipse的Copilot终端依赖问题
stm32·mcu·eclipse·copilot·ai编程
busideyang2 小时前
STM32中__weak(弱定义)函数核心总结
c语言·stm32·单片机·嵌入式硬件·嵌入式
可乐鸡翅好好吃2 小时前
RTC时钟源及其低功耗应用
单片机·嵌入式硬件·实时音视频
senijusene2 小时前
51单片机:硬件基础、开发工具与核心外设详解
单片机·嵌入式硬件·51单片机
forAllforMe2 小时前
用STM32+LAN9252的etherCAT 从站实现传感器数据采集
stm32·单片机·嵌入式硬件