本文记录了如何使用stm32cubeMX配置stm32h743iit6的硬件FMC外设与与 SDRAM 通信(W9825G6KH)。具体比较深的内容不作介绍。
1.准备工作
硬件准备
这里使用的正点原子的stm32h743开发板,板载的是一片SDRAM,型号为w9825G6KH,大小为32MB。
软件准备
需要准备一份 W9825G6KH-6 的数据手册。
2. 使用STM32CubeMX生成工程
选择芯片型号
打开STM32CubeMX,打开MCU选择器,选择芯片型号。
配置时钟源
- 如果选择使用外部高速时钟(HSE),则需要在System Core中配置RCC;
- 如果使用默认内部时钟(HSI),这一步可以略过;
这里我都使用外部时钟:
配置FMC外设
开发板上SDRAM(W9825G6KH)的原理图如下:
原子的板子设计和cubemx默认配置并不符合,需要手动检查修改GPIO(引脚基本不用修改,但最好核对一下)。
目前首先是在上图的图形界面中配置引脚,图中的图钉形状的在引脚上了代表已经选上了。
根据这些信息,配置SDRAM的基本配置
配置时钟树
生成工程设置
生成工程
点击GENERATE CODE
即可生成MDK-V5工程:
生成的代码配置参数必须正确,这个地方一定要2分频。
3.测试SDRAM读写
3.1 编写SDRAM初始化代码
新建SDRAM初始化文件sdram.h
/* USER CODE BEGIN Private defines */
#define Bank5_SDRAM_ADDR ((uint32_t)0xC0000000)
//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)
uint8_t SDRAM_Send_Cmd(uint8_t bankx,uint8_t cmd,uint8_t refresh,uint16_t regval);
void SDRAM_Initialization_Sequence(SDRAM_HandleTypeDef *hsdram);
然后在sdram.c文件中封装一个向SDRAM发送命令的函数:
/* USER CODE BEGIN 0 */
//发送SDRAM初始化序列
void SDRAM_Initialization_Sequence(SDRAM_HandleTypeDef *hsdram)
{
uint32_t temp=0;
//SDRAM控制器初始化完成以后还需要按照如下顺序初始化SDRAM
SDRAM_Send_Cmd(0,FMC_SDRAM_CMD_CLK_ENABLE,1,0); //时钟配置使能
HAL_Delay(2); //至少延时200us
SDRAM_Send_Cmd(0,FMC_SDRAM_CMD_PALL,1,0); //对所有存储区预充电
SDRAM_Send_Cmd(0,FMC_SDRAM_CMD_AUTOREFRESH_MODE,8,0);//设置自刷新次数
//配置模式寄存器,SDRAM的bit0~bit2为指定突发访问的长度,
//bit3为指定突发访问的类型,bit4~bit6为CAS值,bit7和bit8为运行模式
//bit9为指定的写突发模式,bit10和bit11位保留位
temp=(uint32_t)SDRAM_MODEREG_BURST_LENGTH_1 | //设置突发长度:1(可以是1/2/4/8)
SDRAM_MODEREG_BURST_TYPE_SEQUENTIAL | //设置突发类型:连续(可以是连续/交错)
SDRAM_MODEREG_CAS_LATENCY_3 | //设置CAS值:3(可以是2/3)
SDRAM_MODEREG_OPERATING_MODE_STANDARD | //设置操作模式:0,标准模式
SDRAM_MODEREG_WRITEBURST_MODE_SINGLE; //设置突发写模式:1,单点访问
SDRAM_Send_Cmd(0,FMC_SDRAM_CMD_LOAD_MODE,1,temp); //设置SDRAM的模式寄存器
//刷新频率计数器(以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(&hsdram1,761);
}
//向SDRAM发送命令
//bankx:0,向BANK5上面的SDRAM发送指令
// 1,向BANK6上面的SDRAM发送指令
//cmd:指令(0,正常模式/1,时钟配置使能/2,预充电所有存储区/3,自动刷新/4,加载模式寄存器/5,自刷新/6,掉电)
//refresh:自刷新次数
//regval:模式寄存器的定义
//返回值:0,正常;1,失败.
uint8_t SDRAM_Send_Cmd(uint8_t bankx,uint8_t cmd,uint8_t refresh,uint16_t regval)
{
uint32_t target_bank=0;
FMC_SDRAM_CommandTypeDef Command;
if(bankx==0) target_bank=FMC_SDRAM_CMD_TARGET_BANK1;
else if(bankx==1) target_bank=FMC_SDRAM_CMD_TARGET_BANK2;
Command.CommandMode=cmd; //命令
Command.CommandTarget=target_bank; //目标SDRAM存储区域
Command.AutoRefreshNumber=refresh; //自刷新次数
Command.ModeRegisterDefinition=regval; //要写入模式寄存器的值
if(HAL_SDRAM_SendCommand(&hsdram1,&Command,0XFFFF)==HAL_OK) //向SDRAM发送命令
{
return 0;
}
else return 1;
}
/* USER CODE END 0 */
在fmc.c文件中的函数void MX_FMC_Init(void)里面插入如下函数:SDRAM_Initialization_Sequence(&hsdram1);//发送SDRAM初始化序列
3.2编写SDRAM读写测试代码
① 引入SDRAM驱动头文件:
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include <stdio.h>
#include "sdram.h"
/* USER CODE END Includes */
② 宏定义SDRAM的映射地址以及SDRAM的大小:
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
#define EXT_SDRAM_ADDR ((uint32_t)0xC0000000)
#define EXT_SDRAM_SIZE (32 * 1024 * 1024)
uint32_t bsp_TestExtSDRAM(void);
/* USER CODE END 0 */
③ 编写测试函数:
/* USER CODE BEGIN 4 */
/*
*********************************************************************************************************
* 函 数 名: bsp_TestExtSDRAM
* 功能说明: 扫描测试外部SDRAM的全部单元。
* 形 参: 无
* 返 回 值: 0 表示测试通过; 大于0表示错误单元的个数。
*********************************************************************************************************
*/
uint32_t bsp_TestExtSDRAM(void)
{
uint32_t i;
uint32_t *pSRAM;
uint8_t *pBytes;
uint32_t err;
const uint8_t ByteBuf[4] = {0x55, 0xA5, 0x5A, 0xAA};
/* 写SDRAM */
pSRAM = (uint32_t *)EXT_SDRAM_ADDR;
for (i = 0; i < EXT_SDRAM_SIZE / 4; i++)
{
*pSRAM++ = i;
}
/* 读SDRAM */
err = 0;
pSRAM = (uint32_t *)EXT_SDRAM_ADDR;
for (i = 0; i < EXT_SDRAM_SIZE / 4; i++)
{
if (*pSRAM++ != i)
{
err++;
}
}
if (err > 0)
{
return (4 * err);
}
/* 对SDRAM 的数据求反并写入 */
pSRAM = (uint32_t *)EXT_SDRAM_ADDR;
for (i = 0; i < EXT_SDRAM_SIZE / 4; i++)
{
*pSRAM = ~*pSRAM;
pSRAM++;
}
/* 再次比较SDRAM的数据 */
err = 0;
pSRAM = (uint32_t *)EXT_SDRAM_ADDR;
for (i = 0; i < EXT_SDRAM_SIZE / 4; i++)
{
if (*pSRAM++ != (~i))
{
err++;
}
}
if (err > 0)
{
return (4 * err);
}
/* 测试按字节方式访问, 目的是验证 FSMC_NBL0 、 FSMC_NBL1 口线 */
pBytes = (uint8_t *)EXT_SDRAM_ADDR;
for (i = 0; i < sizeof(ByteBuf); i++)
{
*pBytes++ = ByteBuf[i];
}
/* 比较SDRAM的数据 */
err = 0;
pBytes = (uint8_t *)EXT_SDRAM_ADDR;
for (i = 0; i < sizeof(ByteBuf); i++)
{
if (*pBytes++ != ByteBuf[i])
{
err++;
}
}
if (err > 0)
{
return err;
}
return 0;
}
/* USER CODE END 4 */
④ 在main函数中调用:
/* USER CODE BEGIN 2 */
printf("STM32h743 SDRAM Test By Mculover666\r\n");
SDRAM_Init();
printf("SDRAM W9825G6KH Init success\r\n");
if (bsp_TestExtSDRAM() == 0) {
printf("SDRAM Test success\r\n");
} else {
printf("SDRAM Test fail\r\n");
}
/* USER CODE END 2 */
3.3实验结果
编译,下载到开发板中,在串口助手中查看实验结果: