前言
因为最近要配置 RGB LCD(LTDC) 屏幕要用到 SDRAM,虽然之前也写过一篇这个文章,不过当时太忙,写的也比较潦草,某些细节可能没有写清楚,现在会在这篇文章一一道来,后面也会有一篇 LTDC 配置的文章。
STM32CubeMX 配置
MPU(内存保护单元配置)
跟下面的图片一个个配置就好啦。




FMC 配置
FMC 基本配置
这里也是跟着下面的图片配置,不过要特别注意的是 SDRAM 的引脚有很多,要对应好板子上的引脚不要搞错!否则可能会发生某些问题,如:放在 SDRAM 里面的变量初始化不了、进入硬件故障中断等等。

FMC 时钟配置
我使用的 SDRAM 型号为 "W9825G6KH-6I",其最高时钟频率为166MHz,所以我们这里通过锁相环给到 FMC 的时钟为240MHz 即可,分频后就是120MHz。

程序部分
分散式内存管理
想要将 SDRAM 作为内部 RAM 使用,就要将变量主动分配到这一块内存中,但是应该如何将变量精准分配呢?那么可以看一下这一篇文章,使用分散式管理内存的方式,可以更加方便的管理内存空间:
【STM32H743IIT6 系列】理清 xxRAM、xxROM、xxFlash 的核心作用,附 H7 系列五种内存详解,以及超便捷的内存区域管理方法
startup_stm32h743xx.s 启动文件修改
首先我们要知道,全局变量的定义和初始化是先于 main() 函数的,但是在 main() 函数之前,FMC 所连接的的 SDRAM又没有初始化,所以当你直接用分散式管理内存的方法使用 SDRAM 的时候,必会在没有进入 main() 函数之前就会跳到硬故障中断函数。
由于一定要使用分散式管理内存的方法,那么我们就想到可以在 startup_stm32h743xx.s 启动文件上面动手脚。(其实不将 SDRAM 作为内部 RAM 使用的话可以不用那么操作,直接配置好就可以
点击查看代码
Reset_Handler PROC EXPORT Reset_Handler [WEAK] IMPORT ExitRun0Mode IMPORT SystemInit IMPORT __main IMPORT SystemInit_ExtMemCtl LDR R0, =ExitRun0Mode BLX R0 LDR R0, =SystemInit BLX R0 LDR R0, =SystemInit_ExtMemCtl BLX R0 LDR R0, =__main BX R0 ENDP

在你的启动文件中这个位置加上红色框框中的代码,即可,虽然现在还没有定义,但是后面就会说到了。(假如你的程序没有:ExitRun0Mode,可能因为你的软件是旧版本的)。
提前初始化 FMC SDRAM
SDRAM 写寄存器函数
SDRAM 通过内部寄存器来配置,所以我们要写入其寄存器控制其功能,这是必不可少的。
在 fmc.c 文件加上第一个程序,如下所示:
点击查看代码
FMC_SDRAM_CommandTypeDef command;// 控制指令 /****************************************************************************************************** * 函 数 名: SDRAM_Initialization_Sequence * 入口参数: hsdram - SDRAM_HandleTypeDef定义的变量,即表示定义的sdram * Command - 控制指令 * 返 回 值: 无 * 函数功能: SDRAM 参数配置 * 说 明: 配置SDRAM相关时序和控制方式 *******************************************************************************************************/ void SDRAM_Initialization_Sequence(SDRAM_HandleTypeDef *hsdram, FMC_SDRAM_CommandTypeDef *Command) { __IO uint32_t tmpmrd = 0; register __IO uint32_t index; /* Configure a clock configuration enable command 时钟配置使能*/ Command->CommandMode = FMC_SDRAM_CMD_CLK_ENABLE; // 开启SDRAM时钟 Command->CommandTarget = FMC_COMMAND_TARGET_BANK; // 选择要控制的区域 Command->AutoRefreshNumber = 1; Command->ModeRegisterDefinition = 0; HAL_SDRAM_SendCommand(hsdram, Command, SDRAM_TIMEOUT); // 发送控制指令 /* Delay */ for (index = 0; index<10000; index++); /* Configure a PALL (precharge all) command 对所有存储区域预充电*/ Command->CommandMode = FMC_SDRAM_CMD_PALL; // 预充电命令 Command->CommandTarget = FMC_COMMAND_TARGET_BANK; // 选择要控制的区域 Command->AutoRefreshNumber = 1; Command->ModeRegisterDefinition = 0; HAL_SDRAM_SendCommand(hsdram, Command, SDRAM_TIMEOUT); // 发送控制指令 /* Configure a Auto-Refresh command 设置自动刷新次数*/ Command->CommandMode = FMC_SDRAM_CMD_AUTOREFRESH_MODE; // 使用自动刷新 Command->CommandTarget = FMC_COMMAND_TARGET_BANK; // 选择要控制的区域 Command->AutoRefreshNumber = 8; // 自动刷新次数 Command->ModeRegisterDefinition = 0; HAL_SDRAM_SendCommand(hsdram, Command, SDRAM_TIMEOUT); // 发送控制指令 /* Program the external memory mode register */ //配置模式寄存器,SDRAM的bit0-bit2为指定突发访问的长度 //bit3为指定突发访问的类型,bit4-bit6为CAS值,bit7和bit8为运行模式 //bit9为指定的写突发模式,bit10和bit11位保留位 tmpmrd = (uint32_t)SDRAM_MODEREG_BURST_LENGTH_2 |//设置突发长度:2(可以是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,单点访问 Command->CommandMode = FMC_SDRAM_CMD_LOAD_MODE; // 加载模式寄存器命令 Command->CommandTarget = FMC_COMMAND_TARGET_BANK; // 选择要控制的区域 Command->AutoRefreshNumber = 1; Command->ModeRegisterDefinition = tmpmrd; HAL_SDRAM_SendCommand(hsdram, Command, SDRAM_TIMEOUT); // 发送控制指令 //刷新频率计数器(以SDCLK频率计数),计算方法: //COUNT=SDRAM刷新周期/行数-20=SDRAM刷新周期(us)*SDCLK频率(Mhz)/行数 //我们使用的SDRAM刷新周期为64ms,SDCLK=240/2=120Mhz,行数为8192(2^13). //所以,COUNT=64*1000*120/8192-20=918(20为刷新等待冗余) HAL_SDRAM_ProgramRefreshRate(hsdram, 918); // 配置刷新率 }
然后再对应在 fmc.h 加上对应的宏定义:(注意这里已经提前包含了后面还没加上的程序的全局声明)
点击查看代码
#define SDRAM_Size 32*1024*1024 //32M字节 #define SDRAM_BANK_ADDR ((uint32_t)0xC0000000) // FMC SDRAM 数据基地址 #define FMC_COMMAND_TARGET_BANK FMC_SDRAM_CMD_TARGET_BANK1 // SDRAM 的bank选择 #define SDRAM_TIMEOUT ((uint32_t)0x1000) // 超时判断时间 #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) extern FMC_SDRAM_CommandTypeDef command;// 控制指令 extern void SDRAM_Initialization_Sequence(SDRAM_HandleTypeDef *hsdram, FMC_SDRAM_CommandTypeDef *Command); extern void SystemInit_ExtMemCtl(void); extern void fsmc_sdram_test(void);
集成初始化函数
这里的初始化函数 SystemInit_ExtMemCtl() 就是添加在启动文件中、_main函数前的函数,就是用于提前初始化FMC,可以先看一下程序,后面会进行解读:
点击查看代码
///****************************************************************************************************** //* 函 数 名: SystemInit_ExtMemCtl //* 入口参数: 无 //* 返 回 值: 无 //* 函数功能: 初始化外部 SDRAM 控制器 //* 说 明: 此函数用于初始化 FMC 外设,配置 GPIO 引脚,并对 SDRAM 进行初始化和参数配置。 //* 仅在定义了 DATA_IN_ExtSDRAM 时执行相关操作。 //* 作用: 在进入main函数之前就对FMC进行初始化(很重要!!!!!) //*******************************************************************************************************/ void SystemInit_ExtMemCtl(void) { #if defined (DATA_IN_ExtSDRAM) //-------------------------------------------------------------------- // 变量定义 //-------------------------------------------------------------------- FMC_SDRAM_TimingTypeDef SdramTiming = {0}; GPIO_InitTypeDef GPIO_InitStruct = {0}; __IO uint32_t tmpmrd = 0; uint32_t FMC_Initialized = 0; if (FMC_Initialized) { return; }FMC_Initialized = 1; //-------------------------------------------------------------------- // 时钟使能 //-------------------------------------------------------------------- __HAL_RCC_FMC_CLK_ENABLE(); //-------------------------------------------------------------------- // GPIO初始化 //-------------------------------------------------------------------- __HAL_RCC_GPIOF_CLK_ENABLE(); __HAL_RCC_GPIOH_CLK_ENABLE(); __HAL_RCC_GPIOG_CLK_ENABLE(); __HAL_RCC_GPIOE_CLK_ENABLE(); __HAL_RCC_GPIOB_CLK_ENABLE(); __HAL_RCC_GPIOD_CLK_ENABLE(); 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_2|GPIO_PIN_3|GPIO_PIN_5; 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(GPIOH, &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); //-------------------------------------------------------------------- // FMC-SDRAM初始化 //-------------------------------------------------------------------- hsdram1.Instance = FMC_SDRAM_DEVICE; /* hsdram1.Init */ hsdram1.Init.SDBank = FMC_SDRAM_BANK1; // 选择BANK区 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; // bank数量 hsdram1.Init.CASLatency = FMC_SDRAM_CAS_LATENCY_3; // CAS 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 = 4; SdramTiming.RPDelay = 2; SdramTiming.RCDDelay = 2; if (HAL_SDRAM_Init(&hsdram1, &SdramTiming) != HAL_OK) { Error_Handler( ); } // HAL_SDRAM_Init(&hsdram1, &SdramTiming); SDRAM_Initialization_Sequence(&hsdram1,&command);//配置SDRAM #endif }


最后添加
在 MX_FMC_Init() 函数最后面再添加配置函数,防止在提前初始化后配置丢失。

内存测试
测试程序
添加如下代码,然后放到 main() 函数中测试:
点击查看代码
//SDRAM内存测试 void fsmc_sdram_test(void) { __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:%dKBrn",(uint16_t )(temp-sval+1)*16);//打印SDRAM容量 } }
main() 执行程序
点击查看代码
/* USER CODE BEGIN 0 */ __attribute__((section (".RAM_SDRAM"))) uint32_t SDRAM_Buf1; __attribute__((section (".RAM_SDRAM"))) uint32_t SDRAM_Buf2; __attribute__((section (".RAM_SDRAM"))) uint32_t SDRAM_Buf3; __attribute__((section (".RAM_SDRAM"))) uint32_t SDRAM_Buf4; /* USER CODE END 0 */ /** * @brief The application entry point. * @retval int */ int main(void) { /* USER CODE BEGIN 1 */ /* USER CODE END 1 */ /* MPU Configuration--------------------------------------------------------*/ MPU_Config(); /* Enable the CPU Cache */ /* Enable I-Cache---------------------------------------------------------*/ SCB_EnableICache(); /* Enable D-Cache---------------------------------------------------------*/ SCB_EnableDCache(); /* MCU Configuration--------------------------------------------------------*/ /* Reset of all peripherals, Initializes the Flash interface and the Systick. */ HAL_Init(); /* USER CODE BEGIN Init */ /* USER CODE END Init */ /* Configure the system clock */ SystemClock_Config(); /* USER CODE BEGIN SysInit */ /* USER CODE END SysInit */ /* Initialize all configured peripherals */ MX_GPIO_Init(); MX_USART1_UART_Init(); MX_FMC_Init(); /* USER CODE BEGIN 2 */ Set_Current_USART(USART1_IDX); printf("SDRAM 初始化通过!rn"); fsmc_sdram_test(); SDRAM_Buf1 = 1; SDRAM_Buf2 = 2; SDRAM_Buf3 = 3; SDRAM_Buf4 = 4; printf("rn"); printf("SDRAM_Buf1 = %drn", SDRAM_Buf1); printf("&SDRAM_Buf1 = %prn", &SDRAM_Buf1); printf("rn"); printf("SDRAM_Buf2 = %drn", SDRAM_Buf2); printf("&SDRAM_Buf2 = %prn", &SDRAM_Buf2); printf("rn"); printf("SDRAM_Buf3 = %drn", SDRAM_Buf3); printf("&SDRAM_Buf3 = %prn", &SDRAM_Buf3); printf("rn"); printf("SDRAM_Buf4 = %drn", SDRAM_Buf4); printf("&SDRAM_Buf4 = %prn", &SDRAM_Buf4); printf("rn"); printf("*(0xC0000000) = %drn", *(uint32_t*)0xC0000000); printf("*(0xC0000004) = %drn", *(uint32_t*)0xC0000004); printf("*(0xC0000008) = %drn", *(uint32_t*)0xC0000008); printf("*(0xC000000c) = %drn", *(uint32_t*)0xC000000c); printf("rn"); /* USER CODE END 2 */ /* Infinite loop */ /* USER CODE BEGIN WHILE */ while (1) { /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ } /* USER CODE END 3 */ }
执行结果
下图可见,SDRAM 内存为 32MB,并且内存初始化赋值和读取成功。
