本篇详细的记录了如何使用STM32CubeMX配置 STM32F767IGT6 的硬件FMC外设与 SDRAM 通信(W9825G6KH)。
1. 准备工作
硬件准备
开发板
首先需要准备一个开发板,这里我准备的是STM32F767IGT6的核心板。SDRAM
核心板板载一片SDRAM,型号为 W9825G6KH,大小为 32 MB。
软件准备
需要准备一份 W9825G6KH-6 的数据手册。
2. STM32 FMC外设概述
2.1. 什么是FMC
FMC全称Flexible Memory Controller,灵活的内存控制器,顾名思义,其主要作用是:负责向外部扩展的存储类设备提供控制信号。
FMC内存控制器支持的存储设备有:
Nor Flash、SRAM、PSRAM
Nand Flash
SDRAM
网卡DM9000(类存储设备)
此外,FMC外设还可以通过配置与LCD控制器连接,它提供Intel 8080并口模式和Motorola 6080并口模式,并且可以灵活的配置为指定的LCD接口类型。
2.2. FMC外设的功能框图
2.3. 外部设备的地址映射(重点)
从FMC的角度来看,外部的存储设备被分为几个固定大小的Bank,每个bank 256 MB。
整个FMC外设映射地址的划分如图:
2.3.1. Bank1
Bank1的地址空间为:0x6000 0000 - 0x6FFF FFFF,支持外接Nor Flash、PSRAM、SRAM等设备,还可以外接DM9000等类存储设备。
整个Bank1的地址空间被划分为四个子bank,每个子bank的大小为64MB,刚好对应FMC外设的地址总线(FMC_A[0:25])有26条(2^26=64MB)。
FMC还有两条内部总线ADDR[27:26],用这两路控制片选信号,如下表:
2.3.2. Bank3
只能外接Nand Flash设备。
2.3.3. SDRAM Bank
只能外接SDRAM设备。
3. 使用STM32CubeMX生成工程
选择芯片型号
打开STM32CubeMX,打开MCU选择器:
搜索并选中芯片STM32F767IGT6:
配置时钟源
如果选择使用外部高速时钟(HSE),则需要在System Core中配置RCC;
如果使用默认内部时钟(HSI),这一步可以略过;
这里我都使用外部时钟:
配置串口
开发板板载了一个CH340换串口,连接到USART1,但是引脚不是默认引脚,需要手动修改。
接下来开始配置USART1并修改引脚:
配置FMC外设
在配置FMC外设的时候,需要了解所用SDRAM的一些参数,如果你还对SDRAM的内部结构不熟悉,请阅读这篇博客:SDRAM学习笔记(eg. W9825G6KH)。
FMC配置
开发板上SDRAM(W9825G6KH)的原理图如下:
通过原理图可以看出:
数据总线位宽使用了16bit:FMC D0 - FMC D15;
地址总线位宽使用了13bit:FMC A0 - FMC A12;
BANK选择信号线有两条:FMC BA0 和 FMC BA1;
时钟使能信号使用FMC SDCKE0,片选信号使能使用FMC SDME0,可以看出使用SDRAM区域1;
其它通用信号线:FMC SDNWE、FMC SDNCAS、FMC SDNRAS、FMC SDCLK;
数据掩码信号线使用 FMC NBL0 和 FMC NBL1,分别控制输出高8位还是低8位;
根据这些信息,在STM32CubeMX中先配置SDRAM1的基本设置:
SDRAM基本参数配置
这部分信息从 SDRAM(W9825G6KH) 的数据手册中即可看到。
① 速度等级:当CL=3时最高速度为166Mhz。因为STM32F767的HCLK=216Mhz,所以需要进行二分频,使SDRAM的时钟频率为108Mhz。
② 行地址宽度和列地址宽度:有A0-A12 总共13条行地址线,有A0-A8总共9条列地址线。
最后配置如下:
SDRAM时序参数配置
通过之前的设置,SDRAM的时钟频率为108Mhz,一个时钟周期就是9.26 ns,所以下面参数的单位都是9.26ns。
① LoadToActiveDelay:TMRD 定义加载模式寄存器的命令与激活命令或刷新命令之间的延迟,最小值为2个clk。
② ExitSelfRefreshDelay:从退出自刷新到行有效的时间延迟,最小72ns,所以参数设置为8。
③ SelfRefreshTime:自刷新周期,最小是42ns,所以设置为6。
④ RowCycleDelay(tRC):刷新命令和激活命令之间的延迟,最小值为60,所以设置为7。
⑤ WriteRecoveryTime:写命令和预充电命令之间的延迟,在CL=3的情况下,最小是2个clk。
⑥ RPDelay(tRP):预充电命令与其它命令之间的延迟,最小15ns,所以此项设置为2。
⑦ RCDDelay(tRCD):激活命令与读/写命令之间的延迟,最小15ns,所以设置为2。
配置情况如下:
配置时钟树
STM32G070RB的最高主频到216M,使HCLK = 216Mhz即可:
生成工程设置
代码生成设置
最后设置生成独立的初始化文件:
生成代码
点击GENERATE CODE即可生成MDK-V5工程:
4. 测试SDRAM读写
4.1. 编写SDRAM初始化代码
新建SDRAM驱动文件sdram_fmc_drv.h:
/**
*@file sdram_fmc_drv.h
*@brief 使用 FMC 操作 SDRAM
*@author mculover666
*@date 2020-08-27
*@note 此驱动测试 W9825G6KH SDRAM芯片通过
*/
#ifndef _SDRAM_FMC_DRV_H_
#define _SDRAM_FMC_DRV_H_
#include "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)
void SDRAM_Init(void);
#endif /* _SDRAM_FMC_DRV_H_ */
然后在c文件中封装一个向SDRAM发送命令的函数:
static int SDRAM_SendCommand(uint32_t CommandMode, uint32_t Bank, uint32_t RefreshNum, uint32_t RegVal)
{
uint32_t CommandTarget;
FMC_SDRAM_CommandTypeDef Command;
if (Bank == 1) {
CommandTarget = FMC_SDRAM_CMD_TARGET_BANK1;
} else if (Bank == 2) {
CommandTarget = FMC_SDRAM_CMD_TARGET_BANK2;
}
Command.CommandMode = CommandMode;
Command.CommandTarget = CommandTarget;
Command.AutoRefreshNumber = RefreshNum;
Command.ModeRegisterDefinition = RegVal;
if (HAL_SDRAM_SendCommand(&hsdram1, &Command, 0x1000) != HAL_OK) {
return -1;
}
return 0;
}
最后实现SDRAM初始化的函数:
void SDRAM_Init(void)
{
uint32_t temp;
/* 1. 时钟使能命令 */
SDRAM_SendCommand(FMC_SDRAM_CMD_CLK_ENABLE, 1, 1, 0);
/* 2. 延时,至少100us */
HAL_Delay(1);
/* 3. SDRAM全部预充电命令 */
SDRAM_SendCommand(FMC_SDRAM_CMD_PALL, 1, 1, 0);
/* 4. 自动刷新命令 */
SDRAM_SendCommand(FMC_SDRAM_CMD_AUTOREFRESH_MODE, 1, 8, 0);
/* 5. 配置SDRAM模式寄存器 */
temp = (uint32_t)SDRAM_MODEREG_BURST_LENGTH_1 | //设置突发长度:1
SDRAM_MODEREG_BURST_TYPE_SEQUENTIAL | //设置突发类型:连续
SDRAM_MODEREG_CAS_LATENCY_3 | //设置CL值:3
SDRAM_MODEREG_OPERATING_MODE_STANDARD | //设置操作模式:标准
SDRAM_MODEREG_WRITEBURST_MODE_SINGLE; //设置突发写模式:单点访问
SDRAM_SendCommand(FMC_SDRAM_CMD_LOAD_MODE, 1, 1, temp);
/* 6. 设置自刷新频率 */
/*
SDRAM refresh period / Number of rows)*SDRAM时钟速度 – 20
= 64000(64 ms) / 4096 *108MHz - 20
= 1667.5 取值1668
*/
HAL_SDRAM_ProgramRefreshRate(&hsdram1, 1668);
}
4.2. 编写SDRAM读写测试代码
接下来在main.c中添加SDRAM测试代码。
此测试代码来自安富莱电子。
① 引入SDRAM驱动头文件:
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include #include "sdram_fmc_drv.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("STM32F767 SDRAM Test By Mculover666rn"); SDRAM_Init(); printf("SDRAM W9825G6KH Init successrn"); if (bsp_TestExtSDRAM() == 0) { printf("SDRAM Test successrn"); } else { printf("SDRAM Test failrn"); } /* USER CODE END 2 */ 4.3. 实验结果 编译,下载到开发板中,在串口助手中查看实验结果: 5. 直接指定变量存储到 SDRAM 空间 第4节中的测试方法是使用指针访问SDRAM空间,未免过于麻烦。在实际使用中,可以直接定义一个非常大的数组,将整个数组都存储到SDRAM上,然后动态的使用SDRAM内存空间。 要注意使用这种方法定义变量时,必须在函数外把它定义成全局变量,才可以存储到指定地址上。 测试过程如下: ① 定义全局变量并指定绝对地址: /* 绝对定位方式访问 SDRAM,这种方式必须定义成全局变量 */ uint8_t testValue __attribute__((at(EXT_SDRAM_ADDR))); ② 在main函数中赋值,然后打印: /* 操作在SDRAM的变量 */ testValue = 0x5a; printf("testValue is %#xrn", testValue); 编译,下载,查看实验结果:
上一篇:STM32 (零)--------STM32介绍
下一篇:mac下搭建stm32开发环境
推荐阅读最新更新时间:2024-11-08 12:07
设计资源 培训 开发板 精华推荐
- 使用 Analog Devices 的 LT1371 的参考设计
- LT3755IMSE-2 50W 白光 LED 头灯驱动器的典型应用电路
- 4.2V、1 节、便携式锂离子电池充电器
- STEVAL-ISA188V1,基于 A6986F3V3、8V、1.5A 同步降压开关稳压器的评估板
- 使用 TB62754AFNG 升压 DC-DC 控制器(内置 6Ch 接收器驱动器,用于白光 LED)的典型应用(6Ch 输出的情况)
- 使用 Analog Devices 的 LTC2922IF-2.5 的参考设计
- MAP9000 演示板,使用 MAP9000 高压交流 LED 驱动器的演示板
- stm32f411 micropython开发板
- 使用 NXP Semiconductors 的 P82B715 的参考设计
- 使用 NXP Semiconductors 的 MC10XS3412CPNA 的参考设计