前言:
本系列教程将HAL库与STM32CubeMX结合在一起讲解,使您可以更快速的学会各个模块的使用
上一讲我们说了CubeMX配置SDRAM的一些基本配置,还有FMC跟SDRAM的讲解,这一讲我们来说下SDRAM的初始化流程跟HAL库的SDRAM函数使用
【STM32】HAL库 STM32CubeMX教程十五—FMC-SDRAM(一)
所用工具:
1、芯片: STM32H743II
2、STM32CubeMx软件V6.4.0
3、IDE: MDK-Keil5软件
4、STM3H7xxHAL库
5、W9825G6KH
知识概括:
通过本篇博客您将学到:
SDRAM+FMC的基本原理
STM32CubeMX创建FMC-SDRAM例程
HAL库FMC函数库
SDRAM初始化
SDRAM的初始化,是由其内部的一个加载模式寄存 控制的,该寄存器框图如下:
-
A0~A2: Burst Length,即突发长度(简称 BL) 也就是在同一行中相邻的存储单元连续传输多少数据 -
A3: Addressing Mode 突发访问的模式,Sequential(顺序)或 Interleave(间隔)我们读数据都是连续读,一般不需要跳着读,所以用顺序模式 -
A4~A6: CAS Latency,即列地址选通延迟(简称 CL)在发出读命令 (命令同时包含列地址) 后,需要等待几个时钟周期数据线 Data才会输出有效数据 -
A7~A8: SDRAM 的工作模式。当它被配置为“00”的时候表示工作在正常模式,其它值是测试模式或被保留的设定。实际使用时必须配置成正常模式。 -
A9: Write Mode,:设置写操作的模式,可以选择突发模式或者单次写入模式
SDRAM指令
我们把SDRAM的信号线和控制指令再写一下
我们列一些常用的控制指令 还有上一节的CubeMX配置:
然后我们再来下面那些参数的配置:
- Load mode register to active delay :tRSC 加载模式寄存器命令与行有效或刷新命令之间的延迟
- Exit self-refresh delay:tXSR 退出自我刷新到行有效命令之间的延迟
- Self-refresh time : tRAS 自刷新周期
- SDRAM common row cycle delay : tRC 刷新命令和激活命令之间的延迟
- Write recovery time : tWR 写命令和预充电命令之间的延迟
- SDRAM common row precharge delay: tRP 义预充电命令与其它命令之间的延迟
- Row to column delay tRCD 定义行有效命令与读/写命令之间的延迟
我们查看《W9825G6KH》数据手册,即可看到对应的参数:
在CubeMX中点击对应参数,下面会出来对应的介绍: 这些参数在手册里都有详细的说明,我们看MIN最小时间就好
以tXSR,举个例子 我们SDRAM时钟为HCLK 2分频得到,为100MHZ 那么时钟周期就是 T=1/F =1/100MHz=10ns纳秒 那么tXSR最少为72ns,也就是最小要延迟8个时钟周期 所以我们设置为8
tRSC,最小为两个时钟周期tCK 所以我们设置为2
SDRAM初始化流程
SDRAM 上电后,必须进行初始化,才可以正常使用。
我们来看下SDRAM的初始化流程
- 给 SDRAM 上电,并提供稳定的时钟,延时100-200us;
- 发送“空操作”(NOP) 预充电命令,给所有 Bank 预充电;
- 发送“预充电”(PRECHARGE) 命令,控制所有 Bank 进行预充电,并等待 tRC 时间,tRC 表
示预充电与其它命令之间的延迟; - 发送至少 2 次 “自动刷新”(AUTO REFRESH) 命令,, 每一个自刷新命令之间的间隔时间为 tRC
- 发送“加载模式寄存器”(LOAD MODE REGISTER) 命令,配置 SDRAM 的工作参数,并等
待 tRSC 时间, tRSC 表示加载模式寄存器命令与行有行或刷新命令之间的延迟; - 初始化流程完毕,可以开始读写数据
tRC、tRFC、tRSC 这些参数在上一节已经介绍了
SDRAM读操作
- 发送“行有效”(ACTIVE) 命令,发送命令的同时包含行地址和 Bank 地址,然后等待 tRCD
时间,tRCD 表示行有效命令与读/写命令之间的延迟; - 发送“读”(READ) 命令,在发送命令的同时发送列地址,完成对 SDRAM的寻址。
对于读操作还有一个 CL 延迟(CAS Latency),根据模式寄存器的 CL 定义,延迟 CL 个时钟周期后,SDRAM 的数据线 DQ才输出有效数据,所以需要等待给定的 CL 延迟(2 个或 3 个 CLK)后,再从 DQ 数据线上读取数据。
- 读/写命令都通过拉高 A10 地址线,控制自动预充电,执行“预充电”(auto precharge) 命令后,需要等待 tRP 时间,tRP 表示预充电与其它命令之间
的延迟; - 图中的标号处的 tRAS,表示自刷新周期,即在前一个“行有效”与“预充电”命令之间的时
间; - 发送第二次“行有效”(ACTIVE) 命令准备读写下一个数据,在图中的标号处的 tRC,表示
两个行有效命令或两个刷新命令之间的延迟。
SDRAM写操作
- 发送“行有效”(ACTIVE) 命令,发送命令的同时包含行地址和 Bank 地址,然后等待 tRCD
时间,tRCD 表示行有效命令与读/写命令之间的延迟; - 发送“写”(WRITE) 命令,在发送命令的同时发送列地址,完成寻址的地址输入。写命令是没有 CL 延迟的,主机在发送写命令的同时就可以把要写入的数据用 DQ 输入到 SDRAM 中,这是读命令与写命令的时序最主要的区别。
图中的读/写命令都通过地址线 A10 控制自动预充电,而 SDRAM 接收到带预充电要求的读/写命令后,并不会立即预充电,而是等待 tWR 时间才开始,tWR 表示写命令与预充电之间的延迟;
-
读/写命令都通过拉高 A10 地址线,控制自动预充电,执行“预充电”(auto precharge) 命令后,需要等待 tRP 时间,tRP 表示预充电与其它命令之间的延迟; -
图中的标号处的 tRAS,表示自刷新周期,即在前一个“行有效”与“预充电”命令之间的时 间; -
发送第二次“行有效”(ACTIVE) 命令准备读写下一个数据,在图中的标号处的 tRC,表示 两个行有效命令或两个刷新命令之间的延迟。
关于上面的初始化操作和读写顺序请仔细阅读,在上一节我们已经建好了工程,那么现在就来看下FMC SDRAM的Hal库函数吧
首先我们来看下SDRAM的初始化
首先我们看下三个初始化函数:
FMC_SDRAM_Init(FMC_SDRAM_TypeDef *Device,FMC_SDRAM_InitTypeDef *Init);
设置 SDRAM 的相关控制参数,比如地址线宽度、CAS 延迟、SDRAM 时钟等 对应上图的上半部
FMC_SDRAM_Timing_Init(FMC_SDRAM_TypeDef *Device,FMC_SDRAM_TimingTypeDef *Timing, uint32_t Bank);
SDRAM 时间相关参数,比如自刷新时间、恢复延迟、预充电延迟等,对应上图的下半部 ‘ 最后的初始化函数是:HAL_SDRAM_Init,把上面两个函数合到一起,我们来看下这两个结构体参数:
HAL_SDRAM_Init(SDRAM_HandleTypeDef *hsdram,FMC_SDRAM_TimingTypeDef *Timing);
参数:
- *hsdram: 设置 SDRAM 的相关控制参数
- *Timing :SDRAM 时间相关参数
调用了两个结构体,对应我们CubeMx初始化的上下两个部分。
FMC_SDRAM_InitTypeDef结构体:
typedef struct
{
uint32_t Bank;
uint32_t ColumnBitsNumber;
uint32_t RowBitsNumber;
uint32_t MemoryDataWidth;
uint32_t InternalBankNumber;
uint32_t CASLatency;
uint32_t WriteProtection;
uint32_t SDClockPeriod;
uint32_t ReadBurst;
uint32_t ReadPipeDelay;
} FMC_SDRAM_InitTypeDef;
FMC_SDRAM_TimingTypeDef结构体:
typedef struct
{
uint32_t LoadToActiveDelay;
uint32_t ExitSelfRefreshDelay;
uint32_t SelfRefreshTime;
uint32_t RowCycleDelay;
uint32_t WriteRecoveryTime;
uint32_t RPDelay;
uint32_t RCDDelay;
} FMC_SDRAM_TimingTypeDef
关于FMC IO口的一些初始化在
HAL_FMC_MspInit();
函数里面,感兴趣的可自行查看,这里我们不做过多描述
在看完了初始化之后,我们来看下FMC关于SDRAM有哪些函数
注意,在HAL库里面 有stm32h7xx_ll_fmc.c, stm32h7xx_hal_sdram.c,stm32h7xx_hal_dram.c等等文件,其中stm32h7xx_ll_fmc.c是关于FMC的一些函数及初始化,因为FMC支持很多存储器,所以有很多内容,我们这里用的是SDRAM,所以只看SDRAM的函数,也就是只看stm32h7xx_hal_sdram.c跟stm32h7xx_ll_fmc.c这两个文件
基本上SDRAM.C里的函数都是调用fmc.c中的
初始化函数:
首先是四个初始化函数,这几个函数在上面都介绍过了HAL_SDRAM_MspInit调用的是 HAL_FMC_MspInit(); 也就是硬件IO跟时钟初始化。
HAL_StatusTypeDef HAL_SDRAM_Init(SDRAM_HandleTypeDef *hsdram, FMC_SDRAM_TimingTypeDef *Timing);
HAL_StatusTypeDef HAL_SDRAM_DeInit(SDRAM_HandleTypeDef *hsdram);
void HAL_SDRAM_MspInit(SDRAM_HandleTypeDef *hsdram);
void HAL_SDRAM_MspDeInit(SDRAM_HandleTypeDef *hsdram);
SDRAM中断函数
void HAL_SDRAM_IRQHandler(SDRAM_HandleTypeDef *hsdram);
SDRAM自刷新错误回调函数:
void HAL_SDRAM_RefreshErrorCallback(SDRAM_HandleTypeDef *hsdram);
SDRAM DMA函数:
void HAL_SDRAM_DMA_XferCpltCallback(MDMA_HandleTypeDef *hmdma);
void HAL_SDRAM_DMA_XferErrorCallback(MDMA_HandleTypeDef *hmdma);
这些函数我们基本用不到,有个了解即可
SDRAM控制函数
HAL_StatusTypeDef HAL_SDRAM_WriteProtection_Enable(SDRAM_HandleTypeDef *hsdram);
参数:
- *hsdram: 选择是那个sdram,比如&hsdram1,&hsdram2
功能:SDRAM写保护使能,使能写保护之后就不能往SDRAM中写数据了,只能读数据
HAL_SDRAM_WriteProtection_Disable(SDRAM_HandleTypeDef *hsdram);
参数:
- *hsdram: 选择是那个sdram,比如&hsdram1,&hsdram2
功能:SDRAM写保护关闭,关闭写保护
HAL_SDRAM_SendCommand(SDRAM_HandleTypeDef *hsdram, FMC_SDRAM_CommandTypeDef *Command,
uint32_t Timeout);
参数:
- *hsdram: 选择是那个sdram,比如&hsdram1,&hsdram2
- *Command SDRAM命令的结构体函数,下面对这个结构体做了介绍
- Timeout : 超时时间,就是执行函数最长的时间,超过该时间自动退出函数
FMC_SDRAM_CommandTypeDef 结构体,SDRAM命令设置,参数如下
typedef struct
{
uint32_t CommandMode;
uint32_t CommandTarget;
uint32_t AutoRefreshNumber;
uint32_t ModeRegisterDefinition;
}FMC_SDRAM_CommandTypeDef
- CommandMode 用来设置命令类型,可用命令类型如下:
#define FMC_SDRAM_CMD_NORMAL_MODE (0x00000000U)
#define FMC_SDRAM_CMD_CLK_ENABLE (0x00000001U)
#define FMC_SDRAM_CMD_PALL (0x00000002U)
#define FMC_SDRAM_CMD_AUTOREFRESH_MODE (0x00000003U)
#define FMC_SDRAM_CMD_LOAD_MODE (0x00000004U)
#define FMC_SDRAM_CMD_SELFREFRESH_MODE (0x00000005U)
#define FMC_SDRAM_CMD_POWERDOWN_MODE (0x00000006U)
- CommandTarget 设置目标 SDRAM 存储区域,因为FMC可以外挂 2 个SDRAM ,发送命令的时候,需要指定命令发送给哪个存储器
#define FMC_SDRAM_CMD_TARGET_BANK2 FMC_SDCMR_CTB2
#define FMC_SDRAM_CMD_TARGET_BANK1 FMC_SDCMR_CTB1
#define FMC_SDRAM_CMD_TARGET_BANK1_2 (0x00000018U)
- AutoRefreshNumber 自刷新次数,我们知道SDRAM每次初始化的时候都需要多次自刷新,
需要CommandMode 被配置为 FMC_SDRAM_CMD_AUTOREFRESH_MODE 自刷新命令时才有效
可输入参数值为 1-16
- ModeRegisterDefinition 用来设置 SDRAM 模式寄存器,这个成员值长度为 13 位,各个位一 一对应 SDRAM 的模式寄存器
我们自己添加以下下面的宏定义就好
#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 的模式寄存器”的位定义的,例如突发长度、突发模式、CL 长度、SDRAM 工作模式以及突发写模式
功能:设置SDRAM刷新频率
HAL_SDRAM_ProgramRefreshRate(SDRAM_HandleTypeDef *hsdram, uint32_t RefreshRate);
参数:
- *hsdram: 选择是那个sdram,比如&hsdram1,&hsdram2
- *RefreshRate: 自动刷新周期
SDRAM的自动刷新,在上一节我们也提到了
“自动刷新”,SDRAM 内部有一个行地址生成器(也称刷新计数器)用来自动地依次生成 行地址,每收到一次命令刷新一行。在刷新过程中,所有 Bank 都停止工作,而每次刷新所占用 的时间为 N 个时钟周期 (视 SDRAM 型号而定,通常为 N=9),刷新结束之后才可进入正常的工 作状态,也就是说在这 N 个时钟期间内,所有工作指令只能等待而无法执行。一次次地按行刷 新,刷新完所有行后,将再次对第一行重新进行刷新操作,刷新所有行一遍所需要的时间, 称为 SDRAM 的刷新周期,通常为 64ms。
这个函数会向刷新定时寄存器 FMC_SDRTR 写入计数值,这个计数值每个 SDCLK周期自动减 1,减至 0 时 FMC 会自动向 SDRAM 发出自动刷新命令,控制 SDRAM 刷新,SDRAM每次收到刷新命令后,刷新一行,对同一行进行刷新操作的时间间隔称为 SDRAM 的刷新周期。
计算公式为:
刷新速率=(COUNT+1)*SDRAM 频率时钟 ? 刷新速率=(SDRAM 刷新周期/行数) ? (COUNT+1)=刷新速率/SDRAM 频率时钟 ? 但是实际的COUNT计数值需要减少20个SDRAM时钟周期,最终公式如下: ? COUNT = (SDRAM 刷新周期/行数)/SDRAM 时钟频率 – 20
以 W9825G6KH 为例讲解计算过程,W9825G6KH 的SDRAM刷新周期为 64ms,行数为 8192行,则每行的刷新速率为:
刷新速率=64ms/8192=7.813us
即每隔 7.813us 需要收到一次自动刷新命令
而 SDRAM 时钟频率=200Mhz/2=100Mhz(10ns)
COUNT 的值为:
COUNT=(7.81us/10ns)-20≈761
这里正点原子跟野火给的公式都是错的,看他们的📕的时候请务必注意,两家不知道谁炒谁,错的一模一样
HAL_SDRAM_GetModeStatus(SDRAM_HandleTypeDef *hsdram);
功能: 获取SDRAM的状态 返回SET表示SDRAM空闲,RESET表示SDRAM忙 参数:
- *hsdram: 选择是那个sdram,比如&hsdram1,&hsdram2
函数说完了,下面我们就是要写代码了,那么具体的流程
- 完成SDRAM FMC初始化 (CubeMX已经配置好)
- 完成SDRAM芯片初始化
- SDRAM读函数
- SDRAM写函数
我们首先来看第二步:
先定义三个宏定义:
static FMC_SDRAM_CommandTypeDef Command;
#define sdramHandle hsdram1
#define SDRAM_TIMEOUT ((uint32_t)0xFFFF)
然后再fmc.h中添加加载模式寄存器的宏定义命令:
#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的初始化函数:
static void SDRAM_InitSequence(void)
{
uint32_t tmpr = 0;
Command.CommandMode = FMC_SDRAM_CMD_CLK_ENABLE;
Command.CommandTarget = FMC_SDRAM_CMD_TARGET_BANK1;
Command.AutoRefreshNumber = 1;
Command.ModeRegisterDefinition = 0;
HAL_SDRAM_SendCommand(&sdramHandle, &Command, SDRAM_TIMEOUT);
HAL_Delay(1);
Command.CommandMode = FMC_SDRAM_CMD_PALL;
Command.CommandTarget = FMC_SDRAM_CMD_TARGET_BANK1;
Command.AutoRefreshNumber = 1;
Command.ModeRegisterDefinition = 0;
HAL_SDRAM_SendCommand(&sdramHandle, &Command, SDRAM_TIMEOUT);
Command.CommandMode = FMC_SDRAM_CMD_AUTOREFRESH_MODE;
Command.CommandTarget = FMC_SDRAM_CMD_TARGET_BANK1;
Command.AutoRefreshNumber = 4;
Command.ModeRegisterDefinition = 0;
HAL_SDRAM_SendCommand(&sdramHandle, &Command, SDRAM_TIMEOUT);
tmpr = (uint32_t)SDRAM_MODEREG_BURST_LENGTH_1 |
SDRAM_MODEREG_BURST_TYPE_SEQUENTIAL |
SDRAM_MODEREG_CAS_LATENCY_2 |
SDRAM_MODEREG_OPERATING_MODE_STANDARD |
SDRAM_MODEREG_WRITEBURST_MODE_SINGLE;
Command.CommandMode = FMC_SDRAM_CMD_LOAD_MODE;
Command.CommandTarget = FMC_SDRAM_CMD_TARGET_BANK1;
Command.AutoRefreshNumber = 1;
Command.ModeRegisterDefinition = tmpr;
HAL_SDRAM_SendCommand(&sdramHandle, &Command, SDRAM_TIMEOUT);
HAL_SDRAM_ProgramRefreshRate(&sdramHandle, 761);
}
然后在fmc.h中添加SDRAM的基地址:
#define SDRAM_BANK_ADDR ((uint32_t)0xC0000000)
然后我们使能下串口:
就直接配置为异步模式,然后IO口复用成PA9跟PA10 在USART.C里面添加
#include<stdio.h>
struct __FILE
{
int handle;
};
FILE __stdout;
void _sys_exit(int x)
{
x = x;
}
int fputc(int ch, FILE *f)
{
while((USART1->ISR&0X40)==0);
USART1->TDR=(uint8_t)ch;
return ch;
}
可以在FMC.C中添加SDRAM读写函数:
SDRAM写函数:
void FMC_SDRAM_WriteBuffer(uint8_t *pBuffer,uint32_t WriteAddr,uint32_t n)
{
while ( HAL_SDRAM_GetState(&hsdram1) != RESET)
{
}
for(;n!=0;n--)
{
*(__IO uint8_t*)(SDRAM_BANK_ADDR+WriteAddr)=*pBuffer;
WriteAddr++;
pBuffer++;
}
}
SDRAM读函数:
void FMC_SDRAM_ReadBuffer(uint8_t *pBuffer,uint32_t ReadAddr,uint32_t n)
{
while ( HAL_SDRAM_GetState(&hsdram1) != RESET)
{
}
for(;n!=0;n--)
{
*pBuffer++=*(__IO uint8_t*)(SDRAM_BANK_ADDR+ReadAddr);
ReadAddr++;
}
}
例程测试
在main.c中主函数外添加宏定义:
uint16_t testsram[250000] __attribute__((at(0XC0000000)));
添加SDRAM测试函数:
void fsmc_sdram_test()
{
__IO uint32_t i=0;
__IO uint32_t temp=0;
__IO uint32_t sval=0;
for(i=0;i<32*1024*1024;i+=16*1024)
{
*(__IO uint32_t*)(SDRAM_BANK_ADDR+i)=temp;
temp++;
}
for(i=0;i<32*1024*1024;i+=16*1024)
{
temp=*(__IO uint32_t*)(SDRAM_BANK_ADDR+i);
if(i==0)sval=temp;
else if(temp<=sval)break;
printf("SDRAM Capacity:%dKB\r\n",(uint16_t)(temp-sval+1)*16);
}
}
在main主函数里添加:
uint32_t ts=0;
for(ts=0;ts<250000;ts++)
{
testsram[ts]=ts;
}
HAL_Delay(2000);
fsmc_sdram_test();
HAL_Delay(2000);
for(ts=0;ts<250000;ts++)
{
printf("testsram[%d]:%d\r\n",ts,testsram[ts]);
}
while(1)
{
}
打开串口,然后查看测试数据: 下载地址: 链接:https://pan.baidu.com/s/1A8pY0Oz7Bba15CfPPD8fMQ 提取:zzzz
|