细说STM32F407单片机以DMA方式读写外部SRAM的方法

news2025/1/13 2:41:59

目录

一、工程配置

1、时钟、DEBUG、GPIO、CodeGenerator

2、USART3

3、NVIC 

4、 FSMC

5、DMA 2 

(1)创建MemToMem类型DMA流

(2)开启DMA流的中断

二、软件设计

1、KEYLED

2、fsmc.h、fsmc.c、dma.h、dma.c

3、main.h

4、main.c

三、运行与调试


        本文作者旨在介绍如何使用DMA方式读写外部SRAM。继续使用旺宝红龙开发板STM32F407ZGT6 KIT V1.0。

        参考文章:细说STM32F407单片机以轮询方式读写外部SRAM的方法-CSDN博客  https://wenchm.blog.csdn.net/article/details/144959391

        原理图,详见参考文章。 

一、工程配置

        外部SRAM还可以通过DMA方式访问,HAL驱动程序中提供了HAL_SRAM_Write_DMA()和HAL_SRAM_Read_DMA()两个函数用于外部SRAM的DMA方式读写数据。但是在FSMC的参数设置界面并没有DMA设置界面外部SRAM的DMA配置方法与一般的外设不同。在FSMC组件的配置界面没有DMA设置页面,为此需要在CubeMX里单独创建一个DMA流,然后在程序里编写少量代码将创建的DMA流与Bank 1子区3对象关联。

        工程仍然引用KEYLED文件夹中的文件,使用方法详见参考文章。其按键的功能定义:

[S2]KeyUp   = Write directly.    LED1 ON
[S3]KeyDown = Write by DMA.      LED2 ON
[S4]KeyLeft = Read by DMA.       LED3 ON

1、时钟、DEBUG、GPIO、CodeGenerator

        外部时钟,25MHz,设置到HCLK=168MHz,PCLK1=42MHz,PCLK2=84MHz,其它,都设置成168MHz。

        DEBUG,选择serial wire,CodeGenerator的设置同参考文章1。

2、USART3

        使用管脚PB10、PB11,不用USART6是因为IDE提示与FSMC有争执。

3、NVIC 

         开启DMA2全局中断,优先级=1,修改TIME BASE中断优先级=0。

4、 FSMC

        设置与参考文章相同。 

5、DMA 2 

(1)创建MemToMem类型DMA流

        在组件面板的System Core分组里有一个DMA组件,可以在这里管理已经为外设的DMA请求配置好的DMA流,也可以在这里直接创建DMA配置。DMA组件没有任何模式参数需要设置,界面如图所示:

 

        访问外部SRAM的DMA传输方向是Memory To Memory(存储器到存储器),只有DMA2控制器支持这种类型的DMA传输,在Mem ToMem页面配置的DMA流会自动显示在DMA2页面。本示例创建的DMA配置,DMA请求只能选择为MEMTOMEM,选择一个流DMA2 Stream2(只能是DMA2控制器的DMA流),传输方向是Memory To Memory。

        这个DMA流的属性参数设置需要注意以下事项:

  • DMA流的工作模式(Mode)只能设置为正常(Normal)模式,不能设置为循环模式。
  • DMA流会自动使用FIFO,且不能关闭。
  • 源存储器和目标存储器的数据宽度Data Width设置为Word,这是因为函数HAL_SRAM_Write_DMA()和HAL_SRAM_Read_DMA()只支持uint32_t类型的数据缓冲区。
  • 源存储器和目标存储器都应该开启地址自增功能。

        CubeMX会为这样配置的一个DMA流生成初始化代码,也就是会定义DMA_HandleTypeDef类型的DMA流对象变量,并根据CubeMX里的设置生成赋值代码,用函数HAL_DMA_Init()进行DMA流的初始化,但是不会生成代码将DMA流对象与外设关联,也就是不会生成调用函数__HAL_LINKDMA()的代码,需要用户自己在程序中编写代码将DMA流对象与FSMC Bank 1子区3对象关联。这与前面介绍过的一些外设使用DMA的配置方法有差异。

(2)开启DMA流的中断

        前面创建的DMA配置中用到DMA2 Stream2流,这个DMA流的中断并不会自动打开。不打开DMA流的中断,DMA传输完成中断事件的回调函数就不会被调用。所以,还需要在NVIC管理界面开启DMA2 Stream2的全局中断,将这个中断的抢占优先级设置为1,以防回调函数里直接或间接用到延时函数HAL Delay()。

二、软件设计

1、KEYLED

         本例的项目中要使用KEYLED,其中的keyled.c和keyled.h的使用方法与参考文章相同。

2、fsmc.h、fsmc.c、dma.h、dma.c

         由IDE自动生成,不需要修改。

3、main.h

/* USER CODE BEGIN Private defines */
void SRAM_WriteDirect();
void SRAM_WriteDMA();
void SRAM_ReadDMA();
/* USER CODE END Private defines */

4、main.c

/* USER CODE BEGIN Includes */
#include "keyled.h"
#include <stdio.h>
/* USER CODE END Includes */
/* USER CODE BEGIN PD */
// SRAM的容量不同,该处的定义就不同,更改SRAM就得修改此处的定义
#define SRAM_ADDR_BEGIN	 0x68000000UL //Bank1子区3的SRAM起始地址
#define SRAM_ADDR_HALF	 0x6801FFFFUL //SRAM容量256K*16bit,中间地址128K字节
#define SRAM_ADDR_END	 0x6803FFFFUL //SRAM容量256K*16bit,结束地址512K字节
//#define SRAM_ADDR_HALF 0x68080000UL //SRAM容量512K*16bit,中间地址512K字节
//#define SRAM_ADDR_END	 0x680FFFFFUL //SRAM容量512K*16bit,结束地址1024K字节
/* USER CODE END PD */
/* USER CODE BEGIN PV */
#define	 COUNT 5			//缓存区数据个数
uint32_t txBuffer[COUNT];	//DMA发送缓存区
uint32_t rxBuffer[COUNT];	//DMA接收缓存区

uint8_t	 DMA_Direction=1;	//DMA传输方向,1=write, 0=read
uint8_t	 DMA_Busy=0;		//DMA工作状态,1=busy, 0=idle

/* USER CODE END PV */
/* USER CODE BEGIN 2 */
  __HAL_LINKDMA(&hsram3,hdma,hdma_memtomem_dma2_stream0);  //将DMA传输配置与外设hsram3关联

  printf("Demo19_2_FSMC_DMA: External SRAM\r\n");
  printf("Read/Write SRAM by DMA\r\n");

  //显示菜单
  printf("[S2]KeyUp   = Write directly.\r\n");
  printf("[S3]KeyDown = Write by DMA.\r\n");
  printf("[S4]KeyLeft = Read by DMA.\r\n\r\n");

  // MCU output low level LED light is on
  LED1_OFF();
  LED2_OFF();
  LED3_OFF();

  /* USER CODE END 2 */

        在main()函数里完成外设初始化后,执行了下面一行语句:

__HAL_LINKDMA(&hsram3,hdma,hdma_memtomem_dma2_stream0);

         执行这行语句相当于执行了下面两行语句:

(&hsram3)->hdma =&(hdma_memtomem_dma2_stream0);	//hsram3的hdma指向具体的DMA流对象
(hdma_memtomem_dma2_stream0).Parent=(&hsram3);	//DMA流对象的Parent指向具体外设hsram3

         所以,其功能就是将DMA流对象hdma_memtomem_dma2_stream0与外设hsram3互相关联。就是将初始化DMA流对象的代码放到了函数MX_DMA_Init()里,没有自动生成调用__HAL_LINKDMA()的代码实现DMA流与外设的互相关联。所以在main()函数里,需要调用函数__HAL_LINKDMA()将外设hsram3与DMA流对象hdma_memtomem_dma2_stream0关联起来。

 /* USER CODE BEGIN 3 */
	KEYS  curKey=ScanPressedKey(KEY_WAIT_ALWAYS);
	switch(curKey)
	{
		case KEY_UP:{
			SRAM_WriteDirect();
			LED1_ON();
			LED2_OFF();
			LED3_OFF();
			break;
		}

		case KEY_DOWN:{
			SRAM_WriteDMA();
			LED1_OFF();
			LED2_ON();
			LED3_OFF();
			break;
		}

		case KEY_LEFT:{
			SRAM_ReadDMA();
			LED1_OFF();
			LED2_OFF();
			LED3_ON();
			break;
		}

		default:{
			LED1_OFF();
			LED2_OFF();
			LED3_OFF();
		}
	}

	HAL_Delay(500);	//延时,消除按键抖动影��?
  }
  /* USER CODE END 3 */

        文件main.c定义了几个全局变量用于DMA数据传输。

        在外设初始化部分,MX_DMA_Init()用于DMA初始化,就是初始化CubeMX中定义的MemToMem类型的DMA流对象。MX_FSMC_Init()用于FSMC初始化,这个函数的代码与参考文章的示例的定义完全相同。

        外设初始化完成后,要调用函数__HAL_LINKDMA()将DMA流对象hdma_memtomem_dma2_stream0与FSMC Bank 1子区3对象hsram3关联。(有时候,会用refactor方法将其更名为hdma_m2m_sram。但是重新生成式又会恢复到IDE自动生成的对象名。)

        主程序里显示了一个菜单,while循环里通过检测按键对菜单做出响应,响应代码中用到3个自定义函数,USER CODE BEGIN 4数据对里介绍这几个函数的实现代码。

/* USER CODE BEGIN 4 */
//直接写入
void SRAM_WriteDirect()
{
	//准备数组数据
	printf("Writing 32bit array directly...\r\n");
	uint32_t Value=600;
	uint32_t *pAddr_32b=(uint32_t *)(SRAM_ADDR_BEGIN+256);	//给指针赋值
	//准备数组,写数据
	for(uint8_t i=0; i<COUNT; i++)
	{
		txBuffer[i]=Value;
		HAL_SRAM_Write_32b(&hsram3, pAddr_32b, &Value, 1);
		printf("The data writed at ADD %p = %ld\r\n",pAddr_32b,Value);
		Value += 5;
		pAddr_32b ++;
	}

	if (HAL_SRAM_Write_32b(&hsram3, pAddr_32b, txBuffer, COUNT) == HAL_OK)
		printf("Array is written directly successfully.\r\n\r\n");
}

//DMA方式写入SRAM
void SRAM_WriteDMA()
{
	printf("Writing 32bit array by DMA...\r\n");
	uint32_t Value=800;
	uint32_t *pAddr_32b=(uint32_t *)(SRAM_ADDR_BEGIN+256);

	DMA_Direction=1;	//DMA传输方向,1=write, 0=read
	DMA_Busy=1;			//表示DMA正在传输状态,1=working, 0=idle
	for(uint8_t i=0; i<COUNT; i++)
	{
		txBuffer[i]=Value;
		Value += 6;
	}
	HAL_SRAM_Write_DMA(&hsram3, pAddr_32b, txBuffer, COUNT);
}

//DMA方式读取SRAM
void SRAM_ReadDMA()
{
	printf("Reading 32bit array by DMA...\r\n");
	DMA_Direction=0;	//DMA传输方向,1=write, 0=read
	DMA_Busy=1;			//表示DMA正在传输状态,1=working, 0=idle

	uint32_t *pAddr_32b=(uint32_t *)(SRAM_ADDR_BEGIN+256);
	HAL_SRAM_Read_DMA(&hsram3, pAddr_32b, rxBuffer, COUNT);	//以DMA方式读取SRAM
}

//DMA传输完成中断回调函数
void HAL_SRAM_DMA_XferCpltCallback(DMA_HandleTypeDef *hdma)
{
	uint32_t *pAddr_32b=(uint32_t *)(SRAM_ADDR_BEGIN+256);
	if (DMA_Direction == 1)
	{	//DMA传输方向,1=write, 0=read
		for(uint8_t i=0; i<COUNT; i++){
			printf("The data writed at ADD %p = %ld\r\n",pAddr_32b,txBuffer[i]);
			pAddr_32b ++;
		}
		printf("Written by DMA complete.\r\n\r\n");
	}
	else if (DMA_Direction == 0)
	{
		for(uint8_t i=0; i<COUNT; i++){
			printf("The data read at ADD %p = %ld\r\n",pAddr_32b,rxBuffer[i]);
			pAddr_32b ++;
		}
		printf("Read by DMA complete.\r\n\r\n");
	}
	else
		return;
	DMA_Busy=0;			//表示DMA结束了传输,1=working, 0=idle
}

int __io_putchar(int ch)
{
	HAL_UART_Transmit(&huart3,(uint8_t*)&ch,1,0xFFFF);
	return ch;
}
/* USER CODE END 4 */

        按下KeyUp键后,调用函数SRAM_WriteDirect(),其功能是调用函数HAL_SRAM_Write_32b()向外部SRAM写入一个数组的数据,主要是为了测试DMA方式读出的数据是否正确。hsram3关联的DMA流是MemToMem类型的,使用函数HAL_SRAM_Write_DMA()以DMA方式写入数据,或使用函数HAL_SRAM_Read_DMA()以DMA方式读取数据时,都使用这个DMA流,回调函数都是HAL_SRAM_DMA_XferCpltCallback()。所以,定义了两个全局变量表示DMA传输方向和DMA工作状态。

  • 全局变量DMA_Direction表示DMA传输方向:DMA_Direction为1时,表示数据写入;为0时,表示数据读出。
  • 全局变量DMA_Busy表示是否正在进行DMA传输:DMA_Busy为1时,表示正在进行DMA传输;为0时,表示空闲。

        按下KeyDown键时,调用函数SRAM_WriteDMA(),其功能是调用HAL_SRAM_Write_DMA()以DMA方式向外部SRAM写入一个数组的数据。在开启DMA传输之前,将全局变量DMA_Direction设置为1,表示写入操作,将DMA_Busy设置为1。

        按下KeyLeftt键时,调用函数SRAM_ReadDMA(),其功能是调用函数HAL_SRAM_Read_DMA(),以DMA方式从外部SRAM读取一批数据。在开启DMA传输之前,将全局变量DMA_Direction设置为0,表示读取操作,DMA_Busy设置为1。

        函数HAL SRAM_Write_DMA()和HAL_SRAM_Read_DMA()启动的DMA传输完成后,会触发DMA流的传输完成事件中断,会调用相同的一个回调函数HAL_SRAM_DMA_XferCpltCallback(),所以需要在这个回调函数区分DMA传输方向。通过全局变量DMA_Direction可以判断DMA传输方向,从而做出相应的响应。回调函数处理完成后,将全局变量DMA_Busy的值设置为0,表示DMA传输完成。

        在函数SRAM_WriteDMA()和SRAM_ReadDMA()中启动DMA传输之前,理论上还应该判断变量DMA_Busy的值。如果DMA_Busy为1,表示有未完成的DMA传输,需要等待DMA_Busy变为0之后再启动一次DMA传输。本示例中使用按键启动DMA传输,手动操作速度很慢,所以未做判断处理。

三、运行与调试

        测试过程中仍然发现与参考文章一样的情况,等待作者解决后重新发布出来。 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2275761.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Proteus-8086调试汇编格式的一点心得

这阵子开始做汇编的微机实验&#xff08;微机原理与接口技术题解及实验指导&#xff0c;吴宁版本13章&#xff09;&#xff0c;中间出了挺多问题&#xff0c;解决后记录下。 先上电路图 用子电路来仿真发现仿真的时候子电路这块根本没有高低电平输出&#xff0c;只好把子电路拿…

FreeROTS学习 内存管理

内存管理是一个系统基本组成部分&#xff0c;FreeRTOS 中大量使用到了内存管理&#xff0c;比如创建任务、信号量、队列等会自动从堆中申请内存&#xff0c;用户应用层代码也可以 FreeRTOS 提供的内存管理函数来申请和释放内存 FreeRTOS 内存管理简介 FreeRTOS 创建任务、队列…

【西北工业大学主办 | EI检索稳定 | 高H值专家与会报告】2025年航天航空工程与材料技术国际会议(AEMT 2025)

2025 年航天航空工程与材料技术国际会议&#xff08;AEMT 2025&#xff09;将于2025年2月28日至3月2日在中国天津召开。本届会议由西北工业大学主办&#xff0c;由北京航空航天大学、北京理工大学作为支持单位加入&#xff0c;AEIC 学术交流中心协办。 AEMT 2025 旨在汇聚来自全…

目标检测跟踪中的Siamese孪生网络与普通卷积网络(VGG、ResNet)有什么区别?

1、什么是Siamese网络&#xff1f; Siamese网络又叫孪生网络&#xff0c;是一种特殊的神经网络架构&#xff0c;由一对&#xff08;或多对&#xff09;共享参数的子网络组成&#xff0c;用于学习输入样本之间的相似性或关系。最早在 1994 年由 Bromley 等人提出&#xff0c;最…

网络攻击行为可视化分析系统【数据分析 + 可视化】

一、系统背景 随着信息技术的快速发展&#xff0c;网络已成为现代社会不可或缺的一部分。然而&#xff0c;与此同时&#xff0c;网络攻击手段也日益多样化和复杂化&#xff0c;给企业和个人的信息安全带来了极大的威胁。传统的网络攻击分析方法往往依赖于人工分析和处理大量的…

一个运行在浏览器中的开源Web操作系统Puter本地部署与远程访问

文章目录 前言1.关于Puter2.本地部署Puter3.Puter简单使用4. 安装内网穿透5.配置puter公网地址6. 配置固定公网地址 &#x1f4a1; 推荐 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。【点击跳转到网站…

C语言 操作符_位操作符、赋值操作符、单目操作符

1.位操作符 & - 按&#xff08;2进制&#xff09;位与 | - 按&#xff08;2进制&#xff09;位或 ^ - 按&#xff08;2进制&#xff09;位异或 只适用于整型 例&#xff1a;实现交换两个变量的值&#xff0c;要求不能新建变量 //3^3 0 -> a^a 0 //011 //011 //000 …

图像处理 | 图像二值化

在图像处理领域&#xff0c;图像二值化是一个重要的操作&#xff0c;它将彩色或灰度图像转换为只有两种颜色&#xff08;通常是黑白&#xff09;的图像。二值化广泛应用于文字识别、图像分割、边缘检测等领域&#xff0c;尤其在处理简洁和高对比度的图像时非常有效。本文将深入…

IP 地址与蜜罐技术

基于IP的地址的蜜罐技术是一种主动防御策略&#xff0c;它能够通过在网络上布置的一些看似正常没问题的IP地址来吸引恶意者的注意&#xff0c;将恶意者引导到预先布置好的伪装的目标之中。 如何实现蜜罐技术 当恶意攻击者在网络中四处扫描&#xff0c;寻找可入侵的目标时&…

Web基础之什么是HTTP协议

Q&#xff1a;什么是HTTP协议&#xff1f; 概念&#xff1a;Hyper Text Transfer Protocol&#xff0c;超文本传输协议&#xff0c;规定了浏览器和服务器之间数据传输的规则。 特点&#xff1a; 1&#xff0e;基于TCP协议&#xff1a;面向连接&#xff0c;安全 2&#xff0e;基…

#渗透测试#谷歌扩展学习#编写一个属于自己的谷歌扩展

目录 一、Chrome扩展程序是什么 二、如何自己编写一个简单谷歌扩展 1. 创建项目文件夹 2. 创建 manifest.json 文件 3. 创建 popup.html 文件 4. 创建 popup.js 文件 5. 加载扩展程序到Chrome浏览器 6. 测试扩展程序 三、Chrome插件图标设计技巧 1. 简洁明了 2. 独特…

LayerNorm的思考

文章目录 1. LayerNorm2. 图解3. softmax4. python 代码 1. LayerNorm y x − E [ x ] v a r ( x ) ϵ ∗ γ β \begin{equation} y\frac{x-\mathrm{E}[x]}{\sqrt{\mathrm{var}(x)\epsilon}}*\gamma\beta \end{equation} yvar(x)ϵ ​x−E[x]​∗γβ​​ 2. 图解 矩阵A …

ExplaineR:集成K-means聚类算法的SHAP可解释性分析 | 可视化混淆矩阵、决策曲线、模型评估与各类SHAP图

集成K-means聚类算法的SHAP可解释性分析 加载数据集并训练机器学习模型 SHAP 分析以提取特征对预测的影响 通过混淆矩阵可视化模型性能 决策曲线分析 模型评估&#xff08;多指标和ROC曲线的目视检查&#xff09; 带注释阈值的 ROC 曲线 加载 SHAP 结果以进行下游分析 与…

Kafka 会丢消息吗?

目录 01 生产者(Producer) 02 消息代理(Broker) 03 消费者(Consumer) 来源:Kafka 会丢消息吗? Kafka 会丢失信息吗? 许多开发人员普遍认为,Kafka 的设计本身就能保证不会丢失消息。然而,Kafka 架构和配置的细微差别会导致消息的丢失。我们需要了解它如何以及何时…

Open FPV VTX开源之第一次出图

Open FPV VTX开源之第一次出图 1. 源由2. 连线2.1 飞控2.2 调试 3. serial3.1 启动log - uboot3.2 登录版本 - linux3.3 获取有线IP 4. ssh - linux5. PixelPilot出图6. 总结7. 参考资料8. 补充 - 8812AU网卡 1. 源由 在《Open FPV VTX开源之硬件规格及组成》章节中&#xff0…

仓颉笔记——写一个简易的web服务并用浏览器打开

创建一个web服务端&#xff0c;同时创建一个客户端去读取这个服务端。 也满足浏览器打开web的需求。 直接上代码。 import net.http.* import std.time.* import std.sync.* import std.log.LogLevel// 1. 构建 Server 实例 let server ServerBuilder().addr("127.0.0.1&…

Trie树算法

Trie树&#xff0c;也称为前缀树或字典树&#xff0c;是一种特殊的树型数据结构。它用于存储一组字符串&#xff0c;使得查找、插入和删除字符串的操作非常高效。类似这种&#xff0c; 模板&#xff1a; 这是用数组来模拟上图中的树的结构&#xff0c;逻辑上和上图结构一致。 …

03-51单片机定时器和串口通信

一、51单片机定时器 1.定时器介绍 1.1为什么要使用定时器 在前面的学习中&#xff0c;用到了 Delay 函数延时&#xff0c;这里学习定时器以后&#xff0c;就可以通过定时器来完成&#xff0c;当然定时器的功能远不止这些&#xff1a; 51 单片机的定时器既可以定时&#xff…

搭建docker私有化仓库Harbor

Docker私有仓库概述 Docker私有仓库介绍 Docker私有仓库是个人、组织或企业内部用于存储和管理Docker镜像的存储库。Docker默认会有一个公共的仓库Docker Hub,而与Docker Hub不同,私有仓库是受限访问的,只有授权用户才能够上传、下载和管理其中的镜像。这种私有仓库可以部…

【深度学习】核心概念-人工神经网络(Artificial Neural Network, ANN)

人工神经网络是一种受生物神经系统启发的机器学习模型&#xff0c;旨在通过连接大量的节点&#xff08;称为神经元或节点&#xff09;来模拟人脑的学习方式。它是一种在监督学习和非监督学习中广泛应用的深度学习模型。 人工神经网络的基本结构 一个人工神经网络通常由以下三个…