当前位置: 首页 > article >正文

【STM32H743IIT6】将外部SDRAM作为内部SRAM使用的方法及需要解决的问题

前言

STM32H743的片上随机存取存储器(RAM)容量最大约为1KB。对于简单项目而言,这一容量尚可满足需求。但在处理更为复杂的应用程序时,尤其是在随机存取存储器方面,“空间不足”的问题就会不可避免地出现。此时,W9825G6KH便能发挥作用。然而,在将其用作内部静态随机存取存储器(SRAM)时,系统很容易进入硬错误中断(hardfault_handler)。在后续内容中,我将详细阐述如何配置同步动态随机存取存储器(SDRAM)。

单片机内存组成

在真正开始之前,先在这里简单解读一下单片机编译之后产生的内存:

程序存储器(Flash)

  • 代码段(Code):这部分存储的是单片机程序的可执行指令,也就是我们编写的程序代码被编译后生成的机器码。如在 C 语言中,函数体里的语句经过编译就会变成代码段中的指令。比如一个简单的加法函数。
  • 只读数据段(RO - data):存储的是常量数据,这些数据在程序运行过程中不会被修改。常见的如、字符串常量、使用 const 关键字修饰的变量等。

数据存储器(RAM)

  • 可读写数据段(RW - data):存储已经初始化的全局变量和静态变量。这些变量在程序运行过程中可以被修改。
  • 零初始化数据段(ZI - data):存储未初始化或初始化为 0 的全局变量和静态变量。在程序启动时,系统会自动将这部分内存初始化为 0。
  • 栈(Stack):栈是一种后进先出(LIFO)的数据结构,主要用于存储函数调用时的局部变量、函数参数、返回地址等信息。当调用一个函数时,会在栈上为该函数的局部变量分配内存空间;函数返回时,这些内存空间会被释放。
  • 堆(Heap):堆用于动态内存分配。在程序运行过程中,如果需要动态地分配和释放内存,可以使用 malloc、calloc、realloc 等函数从堆中分配内存,使用 free 函数释放内存。

配置MPU

在高性能板子中,MPU的配置是必不可少的,否则一不注意就会进入硬错误中断,同时配置MPU也是为了板子的内存安全。以下就是MPU在CubeMX中的配置:

配置SDRAM

直接看这个博主写的优秀的文章,跟着他配置一下就搞定了:

【CubeMX-HAL库】STM32H743—FMC配置SDRAM_stm32h743 sdram-CSDN博客

外部SDRAM作为内部SRAM

方法一(不可行)

首先在Keil5魔法棒中如此设置:

接着编译...运行...,你就会发现程序卡住了,正好(恰好?)卡在了 hardfault_handler() 当中,然后你想着调试,结果发现一进调试就进了 hardfault_handler() 中,是不是很神奇,还没进到main函数就寄了。

原因

这里就直接说了,当时可是找了我两天,焦头烂额的,要感谢硬汉嵌入式论坛的一篇博文:

STM32H7 SDRAM启动的坑 - STM32H7 - 硬汉嵌入式论坛 - Powered by Discuz!

就是在 SystemInit () 函数中(找不到的 Ctrl+F 跳转):

不知道出于什么原因,在进入 _main 前就把FMC关闭了,这时你再调用SDRAM的内存的时候,就会发生硬错误进入中断。为什么要说是 _main 之前呢?详细请看下图的 startup 启动文件(汇编):

可以见到是先进入SystemInit 才进入的 _main。

方法二(可行,重要)

由于上面我们已经知道了问题出在哪里,针对其进行解决就行!

加上宏定义

增加 SystemInit_ExtMemCtl 函数

新版的库函数中没有这个(官方例程里都有,用来在 SystemInit 中初始化SDRAM的),也不知道是不是ST公司又犯病了,所以这里我们自己加上,加到fmc.c文件里吧:

注意!注意!注意!这个函数中的FMC的SDRAM的初始化都是直接从下面的 MX_FMC_Init 和 HAL_FMC_MspInit初始化函数中复制的,目的就是为了让 FMC 的 SDRAM 在进入_main之前初始化好!!!

/******************************************************************************************************
*	函 数 名: SystemInit_ExtMemCtl
*	入口参数: 无
*	返 回 值: 无
*	函数功能: 初始化外部 SDRAM 控制器
*	说    明: 此函数用于初始化 FMC 外设,配置 GPIO 引脚,并对 SDRAM 进行初始化和参数配置。
*             仅在定义了 DATA_IN_ExtSDRAM 时执行相关操作。
* 作用:		在进入main函数之前就对FMC进行初始化(很重要!!!!!)
*******************************************************************************************************/
void SystemInit_ExtMemCtl(void)
{
	// 仅在定义了 DATA_IN_ExtSDRAM 时执行 SDRAM 初始化操作
	#if defined (DATA_IN_ExtSDRAM)

	// 定义 SDRAM 时序结构体并初始化为 0
	FMC_SDRAM_TimingTypeDef SdramTiming = {0};
	// 用于临时存储 MRD(模式寄存器定义)的变量
	__IO uint32_t tmpmrd = 0;
	// 用于标记 FMC 是否已经初始化的标志变量
	uint32_t FMC_Initialized = 0;

	// 检查 FMC 是否已经初始化,如果是则直接返回
	if (FMC_Initialized) {
			return;
	}
	// 标记 FMC 已经初始化
	FMC_Initialized = 1;

	// 定义外设时钟初始化结构体
	RCC_PeriphCLKInitTypeDef PeriphClkInitStruct = {0};

	//------------------------ 初始化外设时钟 ------------------------
	// 选择要初始化的外设时钟为 FMC
	PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_FMC;
	// 选择 FMC 的时钟源为 D1HCLK
	PeriphClkInitStruct.FmcClockSelection = RCC_FMCCLKSOURCE_D1HCLK;
	// 配置外设时钟,如果配置失败则调用错误处理函数
	if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct) != HAL_OK)
	{
			Error_Handler();
	}

	//------------------------ 初始化 GPIO 引脚 ------------------------
	// 定义 GPIO 初始化结构体
	GPIO_InitTypeDef GPIO_InitStruct = {0};

	// 配置 GPIOF 引脚
	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);

	// 配置 GPIOC 引脚
	GPIO_InitStruct.Pin = GPIO_PIN_0|GPIO_PIN_2|GPIO_PIN_3;
	HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);

	// 配置 GPIOG 引脚
	GPIO_InitStruct.Pin = GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_4
												|GPIO_PIN_5|GPIO_PIN_8|GPIO_PIN_15;
	HAL_GPIO_Init(GPIOG, &GPIO_InitStruct);

	// 配置 GPIOE 引脚
	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;
	HAL_GPIO_Init(GPIOE, &GPIO_InitStruct);

	// 配置 GPIOD 引脚
	GPIO_InitStruct.Pin = GPIO_PIN_8|GPIO_PIN_9|GPIO_PIN_10|GPIO_PIN_13
												|GPIO_PIN_14|GPIO_PIN_15|GPIO_PIN_0|GPIO_PIN_1
												|GPIO_PIN_4|GPIO_PIN_5|GPIO_PIN_7;
	HAL_GPIO_Init(GPIOD, &GPIO_InitStruct);

	//------------------------ 使能 FMC 时钟 ------------------------
	// 使能 FMC 接口时钟
	__HAL_RCC_FMC_CLK_ENABLE();

	//------------------------ 初始化 SDRAM 句柄 ------------------------
	// 定义 SRAM 句柄(这里未使用,可考虑移除)
	SRAM_HandleTypeDef hsram1;
	// 定义 SDRAM 句柄
	SDRAM_HandleTypeDef hsdram1;

	// 配置 SDRAM 句柄的实例
	hsdram1.Instance = FMC_SDRAM_DEVICE;
	// 配置 SDRAM 句柄的初始化参数
	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;

	//------------------------ 配置 SDRAM 时序 ------------------------
	// 配置 SDRAM 时序参数
	SdramTiming.LoadToActiveDelay = 2;
	SdramTiming.ExitSelfRefreshDelay = 8;
	SdramTiming.SelfRefreshTime = 6;
	SdramTiming.RowCycleDelay = 6;
	SdramTiming.WriteRecoveryTime = 4;
	SdramTiming.RPDelay = 2;
	SdramTiming.RCDDelay = 2;

	// 初始化 SDRAM,如果初始化失败则调用错误处理函数
	if (HAL_SDRAM_Init(&hsdram1, &SdramTiming) != HAL_OK)
	{
			Error_Handler();
	}

	//------------------------ 执行 SDRAM 初始化序列 ------------------------
	// 调用 SDRAM 初始化序列函数,配置 SDRAM
	SDRAM_Initialization_Sequence(&hsdram1, &command);

	#endif
}

改变启动文件startup

如下修改:

使用SDRAM(必须要那么用)

请看硬汉的博客:

1、【原创】像使用内部SRAM一样定义使用STM32H7的外部SDRAM,含MDK和IAR两版 - STM32H7 - 硬汉嵌入式论坛 - Powered by Discuz!

【【STM32H7教程】第26章 STM32H7的TCM,SRAM等五块内存的超方便使用方式 - CSDN App】https://blog.csdn.net/Simon223/article/details/95200519?sharetype=blog&shareId=95200519&sharerefer=APP&sharesource=2301_79288228&sharefrom=link

根据这样方法来使用并且测试就行,不要用方法一来测试,因为方法一会之间在_main前就会分配好内存,就会直接进入硬错误中断!接下来就可以给一个超大的数组来体验32MB的超大内存了,假如还使用FFT的话,也就可以体验一下几十万甚至上百万个点的FFT了[doge]

 

 


http://www.kler.cn/a/569981.html

相关文章:

  • 小米和华为的需求管理及产品策划
  • Linux下安装VS Code
  • Go红队开发—文件操作
  • ZT23 小美的蛋糕切割
  • Spark主备切换了解么
  • UCRP4320-通用认知无线电平台
  • 每日一题-奶酪题(蓝桥杯)【模拟】
  • 【心得】一文梳理高频面试题 HTTP 1.0/HTTP 1.1/HTTP 2.0/HTTP 3.0的区别并附加记忆方法
  • 【SpringSecurity】——认证、注销、权限控制和注销、记住密码、自定义登入页等知识总结
  • 【JavaEE】SpringMVC简单练习
  • sql-labs靶场笔记
  • selenium grid分布式
  • 【每日八股】计算机网络篇(二):TCP 和 UDP
  • Kotlin中RxJava用法
  • 测试工程师的DeepSeek提效2:自动化测试应用
  • android TabLayout设置tab的时候文字默认居中,选中文字加粗
  • 微信小程序接入DeepSeek模型(火山方舟),并在视图中流式输出
  • PostgreSQL全页写机制深度解析:如何平衡WAL性能与数据可靠性
  • IDEA Tab 页设置多行显示
  • Docker + Vue2 热重载:为什么需要 CHOKIDAR_USEPOLLING=true?