【STM32】HAL库 STM32CubeMX教程十五---FMC-SDRAM(二)

前言:

本系列教程将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 delaytRSC 加载模式寄存器命令与行有效或刷新命令之间的延迟
  • 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的初始化流程
在这里插入图片描述

  1. 给 SDRAM 上电,并提供稳定的时钟,延时100-200us;
  2. 发送“空操作”(NOP) 预充电命令,给所有 Bank 预充电;
  3. 发送“预充电”(PRECHARGE) 命令,控制所有 Bank 进行预充电,并等待 tRC 时间,tRC 表
    示预充电与其它命令之间的延迟;
  4. 发送至少 2 次 “自动刷新”(AUTO REFRESH) 命令,, 每一个自刷新命令之间的间隔时间为 tRC
  5. 发送“加载模式寄存器”(LOAD MODE REGISTER) 命令,配置 SDRAM 的工作参数,并等
    待 tRSC 时间, tRSC 表示加载模式寄存器命令与行有行或刷新命令之间的延迟;
  6. 初始化流程完毕,可以开始读写数据

tRC、tRFC、tRSC 这些参数在上一节已经介绍了

SDRAM读操作

在这里插入图片描述

  1. 发送“行有效”(ACTIVE) 命令,发送命令的同时包含行地址和 Bank 地址,然后等待 tRCD
    时间,tRCD 表示行有效命令与读/写命令之间的延迟;
  2. 发送“读”(READ) 命令,在发送命令的同时发送列地址,完成对 SDRAM的寻址。

对于读操作还有一个 CL 延迟(CAS Latency),根据模式寄存器的 CL 定义,延迟 CL 个时钟周期后,SDRAM 的数据线 DQ才输出有效数据,所以需要等待给定的 CL 延迟(2 个或 3 个
CLK)后,再从 DQ 数据线上读取数据。

  1. 读/写命令都通过拉高 A10 地址线,控制自动预充电,执行“预充电”(auto precharge) 命令后,需要等待 tRP 时间,tRP 表示预充电与其它命令之间
    的延迟;
  2. 图中的标号处的 tRAS,表示自刷新周期,即在前一个“行有效”与“预充电”命令之间的时
    间;
  3. 发送第二次“行有效”(ACTIVE) 命令准备读写下一个数据,在图中的标号处的 tRC,表示
    两个行有效命令或两个刷新命令之间的延迟。

SDRAM写操作

在这里插入图片描述

  1. 发送“行有效”(ACTIVE) 命令,发送命令的同时包含行地址和 Bank 地址,然后等待 tRCD
    时间,tRCD 表示行有效命令与读/写命令之间的延迟;
  2. 发送“写”(WRITE) 命令,在发送命令的同时发送列地址,完成寻址的地址输入。写命令是没有 CL 延迟的,主机在发送写命令的同时就可以把要写入的数据用 DQ 输入到 SDRAM 中,这是读命令与写命令的时序最主要的区别。

图中的读/写命令都通过地址线 A10 控制自动预充电,而 SDRAM 接收到带预充电要求的读/写命令后,并不会立即预充电,而是等待 tWR 时间才开始,tWR 表示写命令与预充电之间的延迟;

  1. 读/写命令都通过拉高 A10 地址线,控制自动预充电,执行“预充电”(auto precharge) 命令后,需要等待 tRP 时间,tRP 表示预充电与其它命令之间的延迟;

  2. 图中的标号处的 tRAS,表示自刷新周期,即在前一个“行有效”与“预充电”命令之间的时
    间;

  3. 发送第二次“行有效”(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结构体:

/* @brief FMC SDRAM 初始化结构体类型定义 */
typedef struct
{
uint32_t Bank; /* 选择 FMC 的 SDRAM 存储区域 */
uint32_t ColumnBitsNumber; /* 定义 SDRAM 的列地址宽度 */
uint32_t RowBitsNumber; /* 定义 SDRAM 的行地址宽度 */
uint32_t MemoryDataWidth; /* 定义 SDRAM 的数据宽度 */
uint32_t InternalBankNumber; /* 定义 SDRAM 内部的 Bank 数目 */
uint32_t CASLatency; /* 定义 CASLatency 的时钟个数 */
uint32_t WriteProtection; /* 定义是否使能写保护模式 */
uint32_t SDClockPeriod; /* 配置同步时钟 SDCLK 的参数 */
uint32_t ReadBurst; /* 是否使能突发读模式 */
uint32_t ReadPipeDelay; /* 定义在 CAS 个延迟后再等待多
少个 HCLK 时钟才读取数据 */
} FMC_SDRAM_InitTypeDef;

FMC_SDRAM_TimingTypeDef结构体:

/* @brief 控制 SDRAM 的时序参数,这些参数的单位都是“周期”
* 各个参数的值可设置为 1-16 个周期。 */
typedef struct
{
uint32_t LoadToActiveDelay; /*TMRD: 加载模式寄存器命令后的延迟 */
uint32_t ExitSelfRefreshDelay; /*TXSR: 自刷新命令后的延迟 */
uint32_t SelfRefreshTime; /*TRAS: 自刷新时间 */
uint32_t RowCycleDelay; /*TRC: 行循环延迟 */
uint32_t WriteRecoveryTime; /*TWR: 恢复延迟 */
uint32_t RPDelay; /*TRP: 行预充电延迟 */
uint32_t RCDDelay; /*TRCD: 行到列延迟 */
} 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; //目标 SDRAM 存储区域 
 uint32_t AutoRefreshNumber; //自刷新次数
 uint32_t ModeRegisterDefinition; // SDRAM 模式寄存器的内容
}FMC_SDRAM_CommandTypeDef
  • CommandMode 用来设置命令类型,可用命令类型如下:
/** @defgroup FMC_SDRAM_Command_Mode FMC SDRAM Command Mode
  * @{
  */
#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 ,发送命令的时候,需要指定命令发送给哪个存储器
/** @defgroup FMC_SDRAM_Command_Target FMC SDRAM Command Target
  * @{
  */
#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 的模式寄存器

我们自己添加以下下面的宏定义就好

/**
* @brief FMC 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)
// CL等待几个时钟周期
#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

函数说完了,下面我们就是要写代码了,那么具体的流程

  1. 完成SDRAM FMC初始化 (CubeMX已经配置好)
  2. 完成SDRAM芯片初始化
  3. SDRAM读函数
  4. SDRAM写函数

我们首先来看第二步:

先定义三个宏定义:

static FMC_SDRAM_CommandTypeDef Command;   //定义SDRAM命令结构体
#define sdramHandle hsdram1  
#define SDRAM_TIMEOUT                    ((uint32_t)0xFFFF)  //定义超时时间

然后再fmc.h中添加加载模式寄存器的宏定义命令:

/**
  * @brief  FMC 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的初始化函数:

/*
 ******************************************************************************************************
 * 函 数 名: SDRAM 初始化序列
 * 功能说明: 完成 SDRAM 序列初始化
 * 形 参: hsdram: SDRAM 句柄
 * Command: 命令结构体指针
 * 返 回 值: None
 ******************************************************************************************************
 *//**
  * @brief  对SDRAM芯片进行初始化配置
  * @param  None. 
  * @retval None.
  */
static void SDRAM_InitSequence(void)
{
	uint32_t tmpr = 0;

	/* Step 1 ----------------------------------------------------------------*/
	/* 配置命令:开启提供给SDRAM的时钟 */
	Command.CommandMode = FMC_SDRAM_CMD_CLK_ENABLE; //时钟配置使能
	Command.CommandTarget = FMC_SDRAM_CMD_TARGET_BANK1;     //目标SDRAM存储区域
	Command.AutoRefreshNumber = 1;
	Command.ModeRegisterDefinition = 0;
	/* 发送配置命令 */
	HAL_SDRAM_SendCommand(&sdramHandle, &Command, SDRAM_TIMEOUT);

	/* Step 2: 延时100us */ 
	
	HAL_Delay(1);
	
	/* Step 3 ----------------------------------------------------------------*/
	/* 配置命令:对所有的bank预充电 */ 
	Command.CommandMode = FMC_SDRAM_CMD_PALL;    //预充电命令
	Command.CommandTarget = FMC_SDRAM_CMD_TARGET_BANK1;    //目标SDRAM存储区域
	Command.AutoRefreshNumber = 1;
	Command.ModeRegisterDefinition = 0;
	/* 发送配置命令 */
	HAL_SDRAM_SendCommand(&sdramHandle, &Command, SDRAM_TIMEOUT);   

	/* Step 4 ----------------------------------------------------------------*/
	/* 配置命令:自动刷新 */   
	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);

	/* Step 5 ----------------------------------------------------------------*/
	/* 设置sdram寄存器配置 */
	tmpr = (uint32_t)SDRAM_MODEREG_BURST_LENGTH_1          |  //设置突发长度:1(可以是1/2/4/8)
				   SDRAM_MODEREG_BURST_TYPE_SEQUENTIAL   |  //设置突发类型:连续(可以是连续/间隔)
				   SDRAM_MODEREG_CAS_LATENCY_2           |   //设置CAS值:3(可以是2/3)
				   SDRAM_MODEREG_OPERATING_MODE_STANDARD |   //设置操作模式:0,标准模式
				   SDRAM_MODEREG_WRITEBURST_MODE_SINGLE;    //设置突发写模式:1,单点访问

	/* 配置命令:设置SDRAM寄存器 */
	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);

	/* Step 6 ----------------------------------------------------------------*/

	/* 设置自刷新速率 */
	
	    //刷新频率计数器(以SDCLK频率计数),计算方法:
	//COUNT=SDRAM刷新周期/行数-20=SDRAM刷新周期(us)*SDCLK频率(Mhz)/行数
    //我们使用的SDRAM刷新周期为64ms,SDCLK=200/2=100Mhz,行数为8192(2^13).
	//所以,COUNT=64*1000*100/8192-20=761
	HAL_SDRAM_ProgramRefreshRate(&sdramHandle, 761); 
}

然后在fmc.h中添加SDRAM的基地址:

/**
  * @brief  FMC SDRAM 数据基地址
  */   
#define SDRAM_BANK_ADDR     ((uint32_t)0xC0000000)

然后我们使能下串口:

就直接配置为异步模式,然后IO口复用成PA9跟PA10
在这里插入图片描述
在USART.C里面添加

#include<stdio.h>


//#pragma import(__use_no_semihosting)             
//标准库需要的支持函数                 
struct __FILE 
{ 
	int handle; 
}; 

FILE __stdout;       
//定义_sys_exit()以避免使用半主机模式    
void _sys_exit(int x) 
{ 
	x = x; 
} 
//重定义fputc函数 
int fputc(int ch, FILE *f)
{ 	
	while((USART1->ISR&0X40)==0);//循环发送,直到发送完毕   
	USART1->TDR=(uint8_t)ch;      
	return ch;
}

可以在FMC.C中添加SDRAM读写函数:

SDRAM写函数:


/**
  * @brief  向sdram写入数据 ,在指定地址(WriteAddr+Bank5_SDRAM_ADDR)开始,连续写入n个字节
  * @param  pBuffer: 指向数据的指针 
  * @param  WriteAddr: 要写入的SDRAM内部地址
  * @param  n:要写入的字节数
  * @retval None.
  */

void FMC_SDRAM_WriteBuffer(uint8_t *pBuffer,uint32_t WriteAddr,uint32_t n)
{

  /* 检查SDRAM标志,等待至SDRAM空闲 */  
  while ( HAL_SDRAM_GetState(&hsdram1) != RESET)
  {
  }
	for(;n!=0;n--)
	{
		*(__IO uint8_t*)(SDRAM_BANK_ADDR+WriteAddr)=*pBuffer;
		WriteAddr++;
		pBuffer++;
	}
}

SDRAM读函数:

/**
  * @brief  从sdram读数据 ,在指定地址((WriteAddr+Bank5_SDRAM_ADDR))开始,连续读出n个字节.
  * @param  pBuffer: 指向数据的指针 
  * @param  ReadAddr: 要读出的起始地址
  * @param  n:要读出的字节数
  * @retval None.
  */
void FMC_SDRAM_ReadBuffer(uint8_t *pBuffer,uint32_t ReadAddr,uint32_t n)
{

  /* 检查SDRAM标志,等待至SDRAM空闲 */  
  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测试函数:


//SDRAM内存测试	    
void fsmc_sdram_test()
{  
	__IO uint32_t i=0;  	  
	__IO uint32_t temp=0;	   
	__IO uint32_t sval=0;	//在地址0读到的数据	  				   
  
	//每隔16K字节,写入一个数据,总共写入2048个数据,刚好是32M字节
	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);//打印SDRAM容量
 	}					 
}

在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]);  //打印SDRAM数据
	}


	while(1)
	{

	}

打开串口,然后查看测试数据:
在这里插入图片描述
下载地址:
链接:https://pan.baidu.com/s/1A8pY0Oz7Bba15CfPPD8fMQ
提取:zzzz

请添加图片描述
请添加图片描述

  • 38
    点赞
  • 97
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 8
    评论
### 回答1: STM32是一款非常流行的微控制器,而HAL库STM32CubeMX则是STM32开发中非常重要的工具。HAL库STM32官方提供的一套硬件抽象层库,可以方便地进行底层硬件操作。而STM32CubeMX则是一款图形化的STM32配置工具,可以帮助开发者快速配置STM32的各种参数和外设。 如果你想学习如何使用HAL库STM32CubeMX进行STM32开发,可以参考以下教程: 1. STM32CubeMX教程:https://www.elecfreaks.com/13511.html 2. HAL库教程:https://www.elecfreaks.com/13512.html 这两篇教程都是非常详细的,包含了从安装配置到具体使用的所有步骤和注意事项。如果你是初学者,建议先从STM32CubeMX教程开始学习,然后再学习HAL库的使用。祝你学习愉快! ### 回答2: STM32是一种微控制器芯片,HAL库STM32芯片上运行的一个软件库,STM32CubeMX是一款开发STM32的集成开发环境(IDE),三者相结合可以帮助开发者更加方便快捷地开发STM32芯片应用。 HAL库简介: HAL即Hardware Abstraction Layer,是一种硬件抽象层,主要作用是将设备特定的功能与应用程序隔离,为应用程序提供一种通用、一致的编程接口。HAL库针对不同的硬件平台,提供了简化的 API 接口,方便开发者编写控制程序。HAL库不仅包含了GPIO、USART、SPI、I2C等通信协议的驱动程序,还提供了RTT(实时跟踪),USB,FSMC等高级驱动程序。 STM32CubeMX简介: STM32CubeMX是STMicroelectronics公司推出的集成开发环境(IDE),它为STM32的软硬件设计提供了全面支持。它能够自动生成C代码,用于配置MCU的参数和外围设备,从而简化STM32的开发流程。STM32CubeMX具有直观的图形界面和标准的配置菜单,可方便地初始化各种复杂的设置与参数。更值得一提的是,STM32CubeMX还能根据用户的设备选型,自动生成基于HAL库的样板代码。 STM32CubeMX使用教程: 1.打开STM32CubeMX并在打开文件夹中打开相应的工程文件。 2.选择相应的芯片型号,并根据需要进行配置。 3.选择芯片的外设,设置其参数,然后生成代码。 4.在vscode或IAR等IDE中导入所生成的代码,然后开始开发。 总的来说,HAL库STM32CubeMX是一对黄金搭档,使得STM32芯片的开发更加快捷、便利。对于初学者而言,使用STM32CubeMX辅助开发可降低学习和开发难度,加快开发效率;对于有经验的工程师而言,它能够提高代码的可读性,使得代码重用和维护更加方便。 ### 回答3: STM32是指意法半导体推出的一款基于ARM Cortex®-M内核的32位微控制器,具有高性能、低功耗和丰富的外设特性。HAL库STM32微控制器的软件库,在软件开发方面提供了便利,简化了开发人员的工作。 STM32CubeMX是一个集成开发环境(IDE),可用于创建STM32项目。它允许开发人员使用图形界面创建STM32代码,并可选择所需的外设并初始化代码。STM32CubeMX可以帮助您快速掌握STM32系列微控制器的各种特性,并帮助您生成初始化代码,这样您就可以在不浪费任何时间的情况下开始编程了。 本教程将介绍如何使用STM32CubeMXHAL库,在STM32微控制器上构建基本的程序。本教程包括以下内容: 1. STM32CubeMX的介绍和安装; 2. 创建STM32项目; 3. 添加外设和初始化代码; 4. 编写应用程序; 5. 调试代码。 首先,我们需要安装STM32CubeMX。您可以在ST的官方网站上下载它。安装后,您可以启动STM32CubeMX并创建新的STM32项目。创建新项目后,您应该选择您所使用的MCU型号并开始配置: 建议您使用图形界面设置芯片的外设。该界面非常友好,您可以轻松地选择所需外设,创建初始化代码。接下来,您可以为您的项目添加所需的外设模块。一旦您添加了特定的模块,您就可以配置这些外设的参数。例如,您可以配置GPIO模块的I/O设置或串口模块的波特率。您还可以定义中断和时钟设置来精确定义您的系统。 一旦完成了外设选择和配置,STM32CubeMX会自动生成您需要的初始化代码并集成到您的主要工程文件中。您可以开始编写您的应用程序,例如传感器读取、控制舵机、以及各种反应器等。您也可以使用STM32CubeMX的代码生成器快速找到并编辑适合您的应用程序的代码。 最后,您可以使用STM32CubeIDE作为IDE环境,帮助您调试代码,分析程序性能和查找错误。STM32CubeIDE具有强大的开发功能,包括源代码编辑器、代码自动完成、调试器和性能分析器等。 综上所述,STM32CubeMXHAL库STM32微控制器的强大工具,可以极大地简化开发人员的工作。使用这些工具,您可以快速创建适用于您的应用程序的代码,并快速调试和分析代码。这提高了开发效率,缩短了产品的上市时间,从而使您的产品更具竞争力。
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Z小旋

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值