目录
一 STM32F7存储器映射
二 使用STM32CubeMX配置SDRAM
三 SDRAM初始化时序
四 为SDRAM空间配置MPU
五 通过自定义动态分配内存函数使用SDRAM中的空间
ITCM-RAM | 0x0000 0000 ~ 0x4000 | 16 Kbytes | 只能CPU访问 |
Flash Memory On ITCM Interface | 0x0020 0000 ~ 0x003F FFFF | 2 Mbytes | |
Flash Memory On AXIM Interface | 0x0800 0000 ~ 0x081F FFFF | 2 Mbytes | |
Data TCM RAM | 0x2000 0000 ~ (0x2002 0000 - 1 ) | 128 Kbytes | |
Main internal SRAM1 | 0x2002 0000 ~ (0x2007 C000 -1) | 368 Kbytes | |
Auxiliary internal SRAM2 | 0x2007 C000 | 16 Kbytes | |
Peripheral | 0x4000 0000 ~ 0x5FFF FFFF |
512Mbytes | Register Addr |
Bank 1 | 0x6000 0000 ~ 0x6FFF FFFF | 256 Mbytes | NOR/PSRAM/SRAM |
Bank 2 | 0x7000 0000 ~ 0x7FFF FFFF | Reserved | |
Bank 3 | 0x8000 0000 ~ 0x8FFF FFFF | 256 Mbytes | NAND |
Bank 4 | 0x9000 0000 ~ 0x9FFF FFFF | 256 Mbytes | Reserved |
SDRAM Bank 1 | 0xC000 0000 ~ 0xCFFF FFFF | 256 Mbytes | SDRAM |
SDRAM Bank 2 | 0xD000 0000 ~ 0xDFFF FFFF | 256 Mbytes | SDRAM |
该配置参照原理图就可以配置出来.
bank选择线: BA0与BA1都有相连,2bit可以表示四种变化,所以选择4 banks.
Byte enable:表示可以通过LDQM,UDQM线控制访问8bit数据,还是16bit数据.
以上配置主要通过查阅从W9825G6KH数据手册进行确定:
1.从W9825G6KH数据手册中可得知Row Address:A0-A12,共13bits; Column Address:A0-A8,共9bits.
2.CAS latency从W9825G6KH数据手册的目录页就可以看出
3.禁止写保护 并 使能突发读模式提高读效率
4.SDRAM common clock:
SDRAM有4个bank, 在切换bank时,SDRAM需要延迟一定时间, SDRAM common clock就是用于配置该时间.在SDRAM手册中
对应= 2 tick , 所以该值配置为2.
5.SDRAM common read pipe delay: CAS 延迟后延后多少个 HCLK 时钟周期读取数据
6.时序配置
在stm32f7的数据手册有这么一句话"SDRAM clock can be HCLK/2 or HCLK/3" ,所以SDRAM的时钟最大为216/2=108MHz,所以对于SDRAM而言, 1tick = 9.26ns
STM32CubeMX参数 | 值 | 说明 | SDRAM手册对应简写 | 值 |
Load mode register to active delay | 2 | 加载模式寄存器到激活时间的延迟 | 2 tick | |
Exit self-refresh delay | 8 | 退出自我刷新后需要延迟的时间 | 72ns | |
Self-refresh time | 6 | 自我刷新周期 | 55ns | |
SDRAM common row cycle delay | 6 | 行循环延迟 | 2 tick | |
Write recovery time | 2 | 写恢复延迟 | 2 tick | |
SDRAM common row precharge delay | 2 | 行预充电延迟 | 15ns | |
Row to Column delay | 6 | 行到列延迟 | 15ns |
(tips: 按道理SDRAM common row cycle delay 与 Row to Column delay 可以最小设置为2的, 但是在实际使用中发现配置为2时SDRAM并不能正常工作, 经过试验这两个参数可设置为6 )
gpio是根据原理图进行配置的, 可参照下图进行修改:
/* FMC initialization function */
static void MX_FMC_Init(void)
{
/* USER CODE BEGIN FMC_Init 0 */
/* USER CODE END FMC_Init 0 */
FMC_SDRAM_TimingTypeDef SdramTiming = {0};
/* USER CODE BEGIN FMC_Init 1 */
/* USER CODE END FMC_Init 1 */
/** Perform the SDRAM1 memory initialization sequence
*/
hsdram1.Instance = FMC_SDRAM_DEVICE;
/* hsdram1.Init */
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 */
SdramTiming.LoadToActiveDelay = 2;
SdramTiming.ExitSelfRefreshDelay = 8;
SdramTiming.SelfRefreshTime = 6;
SdramTiming.RowCycleDelay = 6;
SdramTiming.WriteRecoveryTime = 2;
SdramTiming.RPDelay = 2;
SdramTiming.RCDDelay = 6;
if (HAL_SDRAM_Init(&hsdram1, &SdramTiming) != HAL_OK)
{
Error_Handler( );
}
/* USER CODE BEGIN FMC_Init 2 */
/* USER CODE END FMC_Init 2 */
}
static void HAL_FMC_MspInit(void){
/* USER CODE BEGIN FMC_MspInit 0 */
/* USER CODE END FMC_MspInit 0 */
GPIO_InitTypeDef GPIO_InitStruct ={0};
if (FMC_Initialized) {
return;
}
FMC_Initialized = 1;
/* Peripheral clock enable */
__HAL_RCC_FMC_CLK_ENABLE();
/** FMC GPIO Configuration
PF0 ------> FMC_A0
PF1 ------> FMC_A1
PF2 ------> FMC_A2
PF3 ------> FMC_A3
PF4 ------> FMC_A4
PF5 ------> FMC_A5
PC0 ------> FMC_SDNWE
PC2 ------> FMC_SDNE0
PC3 ------> FMC_SDCKE0
PF11 ------> FMC_SDNRAS
PF12 ------> FMC_A6
PF13 ------> FMC_A7
PF14 ------> FMC_A8
PF15 ------> FMC_A9
PG0 ------> FMC_A10
PG1 ------> FMC_A11
PE7 ------> FMC_D4
PE8 ------> FMC_D5
PE9 ------> FMC_D6
PE10 ------> FMC_D7
PE11 ------> FMC_D8
PE12 ------> FMC_D9
PE13 ------> FMC_D10
PE14 ------> FMC_D11
PE15 ------> FMC_D12
PD8 ------> FMC_D13
PD9 ------> FMC_D14
PD10 ------> FMC_D15
PD14 ------> FMC_D0
PD15 ------> FMC_D1
PG2 ------> FMC_A12
PG4 ------> FMC_BA0
PG5 ------> FMC_BA1
PG8 ------> FMC_SDCLK
PD0 ------> FMC_D2
PD1 ------> FMC_D3
PG15 ------> FMC_SDNCAS
PE0 ------> FMC_NBL0
PE1 ------> FMC_NBL1
*/
GPIO_InitStruct.Pin = GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_3
|GPIO_PIN_4|GPIO_PIN_5|GPIO_PIN_11|GPIO_PIN_12
|GPIO_PIN_13|GPIO_PIN_14|GPIO_PIN_15;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF12_FMC;
HAL_GPIO_Init(GPIOF, &GPIO_InitStruct);
GPIO_InitStruct.Pin = GPIO_PIN_0|GPIO_PIN_2|GPIO_PIN_3;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF12_FMC;
HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
GPIO_InitStruct.Pin = GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_4
|GPIO_PIN_5|GPIO_PIN_8|GPIO_PIN_15;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF12_FMC;
HAL_GPIO_Init(GPIOG, &GPIO_InitStruct);
GPIO_InitStruct.Pin = GPIO_PIN_7|GPIO_PIN_8|GPIO_PIN_9|GPIO_PIN_10
|GPIO_PIN_11|GPIO_PIN_12|GPIO_PIN_13|GPIO_PIN_14
|GPIO_PIN_15|GPIO_PIN_0|GPIO_PIN_1;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF12_FMC;
HAL_GPIO_Init(GPIOE, &GPIO_InitStruct);
GPIO_InitStruct.Pin = GPIO_PIN_8|GPIO_PIN_9|GPIO_PIN_10|GPIO_PIN_14
|GPIO_PIN_15|GPIO_PIN_0|GPIO_PIN_1;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF12_FMC;
HAL_GPIO_Init(GPIOD, &GPIO_InitStruct);
/* USER CODE BEGIN FMC_MspInit 1 */
/* USER CODE END FMC_MspInit 1 */
}
SDRAM数据手册中有这么一段话
从该段话中可以得出初始化时序如下:
STM32CubeMX自动生成的初始化函数如下:
(Tips: 如果只做以上配置是不会自动生成以下初始化代码的,如果要自动生成该段代码,需要在STM32CubeMX中使用SDRAM,之所以我这里生成了这段代码是因为我配置了TouchGFX的Parameter Settings中的SDRAM Instances:
如果没有自动生成,我们可以自己复制如下代码到自己的工程。
/**
* @brief Programs the SDRAM device.
* @retval None
*/
void MX_SDRAM_InitEx(void)
{
__IO uint32_t tmpmrd = 0;
/* Step 1: Configure a clock configuration enable command */
Command.CommandMode = FMC_SDRAM_CMD_CLK_ENABLE;
Command.CommandTarget = FMC_SDRAM_CMD_TARGET_BANK1;
Command.AutoRefreshNumber = 1;
Command.ModeRegisterDefinition = 0;
/* Send the command */
HAL_SDRAM_SendCommand(&hsdram1, &Command, SDRAM_TIMEOUT);
/* Step 2: Insert 100 us minimum delay */
/* Inserted delay is equal to 1 ms due to systick time base unit (ms) */
HAL_Delay(1);
/* Step 3: Configure a PALL (precharge all) command */
Command.CommandMode = FMC_SDRAM_CMD_PALL;
Command.CommandTarget = FMC_SDRAM_CMD_TARGET_BANK1;
Command.AutoRefreshNumber = 1;
Command.ModeRegisterDefinition = 0;
/* Send the command */
HAL_SDRAM_SendCommand(&hsdram1, &Command, SDRAM_TIMEOUT);
/* Step 4: Configure an Auto Refresh command */
Command.CommandMode = FMC_SDRAM_CMD_AUTOREFRESH_MODE;
Command.CommandTarget = FMC_SDRAM_CMD_TARGET_BANK1;
Command.AutoRefreshNumber = 8;
Command.ModeRegisterDefinition = 0;
/* Send the command */
HAL_SDRAM_SendCommand(&hsdram1, &Command, SDRAM_TIMEOUT);
/* Step 5: Program the external memory mode register */
tmpmrd = (uint32_t)SDRAM_MODEREG_BURST_LENGTH_1 |\
SDRAM_MODEREG_BURST_TYPE_SEQUENTIAL |\
SDRAM_MODEREG_CAS_LATENCY_3 |\
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 = tmpmrd;
/* Send the command */
HAL_SDRAM_SendCommand(&hsdram1, &Command, SDRAM_TIMEOUT);
/* Step 6: Set the refresh rate counter */
/* Set the device refresh rate */
HAL_SDRAM_ProgramRefreshRate(&hsdram1, REFRESH_COUNT);
}
到这里,SDRAM基本配置完成, 可以定义一个数组到SDRAM空间,对该数组进行读写操作, 以此判断SDRAM是否正常工作:
uint16_t test_buf[128] __attribute__((at(0xC0000000))); //定义一个数组,该数组首地址为SDRAM首地址0xC0000000
//写该数组
for(uint8_t i = 0;i < 128 ; i++){
test_buf[i] = i;
}
//将该数组的值通过串口打印出来
for(uint8_t i = 0;i < 128 ; i++){
printf("%d ",test_buf[i]);
}
为了防止出现在访问SDRAM时出现莫名其妙的数据错误,建议配置MPU对SDRAM进行保护
附上配置截图:
具体设计原理可以参考正点原子的例子,这里我就直接贴代码了
malloc.c代码如下所示
#include "malloc.h"
uint8_t mem_rdy; //内存管理是否就绪
//内存池(32字节对齐), 外部SDRAM内存池,前面2M给LTDC用了(1280*800*2)
__align(32) uint8_t mem_base[MEM_MAX_SIZE] __attribute__((at(0xC0600000)));
uint32_t mem_map[MEM_MAP_SIZE] __attribute__((at(0xC0600000 + MEM_MAX_SIZE)));//内存管理表
/***************************************************************************************
* @brief 内存管理初始化
* @input
* @return
***************************************************************************************/
static void mem_init(void)
{
memset(mem_map, 0, MEM_MAP_SIZE *4); //内存状态表数据清零
mem_rdy = 1; //内存管理初始化OK
}
/***************************************************************************************
* @brief 内存分配(内部调用)
* @input size:要分配的内存大小(字节)
* @return 返回值:0XFFFFFFFF,代表错误;其他,内存偏移地址
***************************************************************************************/
static uint32_t sdram_malloc(uint32_t size)
{
signed long offset=0;
uint32_t nmemb; //需要的内存块数
uint32_t cmemb = 0;//连续空内存块数
uint32_t i;
if( !mem_rdy ){
mem_init();//未初始化,先执行初始化
}
if(size == 0){
return 0xFFFFFFFF;//不需要分配
}
nmemb = size / MEM_BLOCK_SIZE;//获取需要分配的连续内存块数
if(size % MEM_BLOCK_SIZE){
nmemb++;
}
for(offset = MEM_MAP_SIZE - 1; offset >= 0; offset--)//搜索整个内存控制区
{
if( !mem_map[offset] )
cmemb++;//连续空内存块数增加
else
cmemb=0; //连续内存块清零
if(cmemb == nmemb){ //找到了连续nmemb个空内存块
for(i=0; i < nmemb; i++) { //标注内存块非空
mem_map[offset+i] = nmemb;
}
return (offset * MEM_BLOCK_SIZE);//返回偏移地址
}
}
return 0XFFFFFFFF;//未找到符合分配条件的内存块
}
/***************************************************************************************
* @brief 释放内存(内部调用)
* @input memx:所属内存块;
offset:内存地址偏移
* @return 返回值:0,释放成功;1,释放失败;
***************************************************************************************/
static uint8_t sdram_free(uint32_t offset)
{
int i;
if( !mem_rdy )//未初始化,先执行初始化
{
mem_init();
return 1;//未初始化
}
if(offset < MEM_MAX_SIZE) //偏移在内存池内.
{
int index = offset / MEM_BLOCK_SIZE; //偏移所在内存块号码
int nmemb = mem_map[index]; //内存块数量
for(i=0; i < nmemb; i++) //内存块清零
{
mem_map[index+i] = 0;
}
return 0;
}else
return 2;//偏移超区了.
}
/***************************************************************************************
* @brief 释放内存(外部调用)
* @input ptr:内存首地址
* @return
***************************************************************************************/
void mem_free(void *ptr)
{
uint32_t offset;
if(ptr == NULL)
return;//地址为0.
offset = (uint32_t)ptr - (uint32_t)mem_base;
sdram_free(offset); //释放内存
}
/***************************************************************************************
* @brief 分配内存(外部调用)
* @input size:内存大小(字节)
* @return
***************************************************************************************/
void *mem_malloc(uint32_t size)
{
uint32_t offset;
offset = sdram_malloc(size);
if(offset == 0xFFFFFFFF)
return NULL;
else
return (void*)((uint32_t)mem_base + offset);
}
malloc.h文件内容如下所示
#ifndef __MALLOC_H
#define __MALLOC_H
#include "stdint.h"
#include "string.h"
#ifndef NULL
#define NULL 0
#endif
//SDRAM内存参数设定
#define MEM_MAX_SIZE 20*1024*1024 //最大管理内存20M
#define MEM_BLOCK_SIZE 64 //内存块大小为64字节
#define MEM_MAP_SIZE (MEM_MAX_SIZE / MEM_BLOCK_SIZE) //内存表大小
//用户调用函数
void mem_free(void *ptr) ; //内存释放(外部调用)
void *mem_malloc(uint32_t size); //内存分配(外部调用)
#endif