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

目录

一、工程配置

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 onLED1_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=readDMA_Busy=1;			//表示DMA正在传输状态,1=working, 0=idlefor(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=readDMA_Busy=1;			//表示DMA正在传输状态,1=working, 0=idleuint32_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=readfor(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");}elsereturn;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.rhkb.cn/news/1149.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

二分查找算法——山脉数组的峰顶索引

一.题目描述 852. 山脉数组的峰顶索引 - 力扣&#xff08;LeetCode&#xff09; 二.题目解析 题目给了我们一个山脉数组&#xff0c;山脉数组的值分布就如下面的样子&#xff1a; 然后我们只需要返回数组的峰值元素的下标即可。 三.算法原理 1.暴力解法 因为题目明确说明…

重塑视频创作的格局!ComfyUI-Mochi本地部署教程

一、介绍 mochi是近期Genmo公司开源的先进视频生成模型&#xff0c;具有高保真运动和强大的提示遵循性。此模型的发布极大的缩小了闭源和开源视频生成系统之间的差距。 目前&#xff0c;视频生成模型与现实之间存在巨大差距。其中最影响视频生成的两个关键功能也就是运动质量和…

Docker 安装开源的IT资产管理系统Snipe-IT

一、安装 1、创建docker-compose.yaml version: 3services:snipeit:container_name: snipeitimage: snipe/snipe-it:v6.1.2restart: alwaysports:- "8000:80"volumes:- ./logs:/var/www/html/storage/logsdepends_on:- mysqlenv_file:- .env.dockernetworks:- snip…

Oracle重启后业务连接大量library cache lock

一、现象 数据库和前段应用重启后&#xff0c;出现大量library cache lock等待事件。 二、分析解决 本次异常原因是&#xff1a;原因定位3&#xff1a; 库缓存对象无效 Library cache object Invalidations 三、各类情况具体分析如下 原因定位1&#xff1a;由于文字导致的非…

硬件设计-七位半电压表硬件方案(下)

目录 摘要 简介 解决方案和评估系统简介 应用聚焦&#xff1a;高准确度数据采集器 结论 摘要 本文探讨了为仪器仪表应用设计高准确度设备所涉及的挑战&#xff0c;并介绍了由低INL SAR ADC、全集成式超低温漂精密基准电压源、四通道匹配电阻网络和零漂移低噪声放大器构建的…

基于springboot+vue+微信小程序的宠物领养系统

基于springbootvue微信小程序的宠物领养系统 一、介绍 本项目利用SpringBoot、Vue和微信小程序技术&#xff0c;构建了一个宠物领养系统。 本系统的设计分为两个层面&#xff0c;分别为管理层面与用户层面&#xff0c;也就是管理者与用户&#xff0c;管理权限与用户权限是不…

Termora 一个开源的 SSH 跨平台客户端工具

Termora 是一个终端模拟器和 SSH 客户端&#xff0c;支持 Windows&#xff0c;macOS 和 Linux。 功能特性 支持 SSH 和本地终端支持 SFTP 文件传输支持 Windows、macOS、Linux 平台支持 Zmodem 协议支持 SSH 端口转发支持配置同步到 Gist支持宏&#xff08;录制脚本并回放&…

TypeScript Jest 单元测试 搭建

NPM TypeScript 项目搭建 创建目录 mkdir mockprojectcd mockproject初始化NPM项目 npm init -y安装TypeScript npm i -D typescript使用VSCode 打开项目 创建TS配置文件tsconfig.json {"compilerOptions": {"target": "es5","module&…

sql模糊关联匹配

需求目标&#xff1a; 建立临时表 drop table grafana_bi.zbj_gift_2024;USE grafana_bi; CREATE TABLE zbj_gift_2024 (id INT AUTO_INCREMENT PRIMARY KEY,userName VARCHAR(255),giftName VARCHAR(255),giftNum INT,points INT,teacher VARCHAR(255),sendDate DATETIME,…

Web前端:JavaScript标识符与变量

JavaScript介绍 JavaScript 是一种轻量级的脚本语言。所谓“脚本语言”&#xff0c;指的是它不具备开发操作系统的能力&#xff0c;而是只用来编写控制其他大型应用程序的“脚本”。 JavaScript 是一种嵌入式&#xff08;embedded&#xff09;语言。它本身提供的核心语法不算…

mac homebrew配置使用

本文介绍mac上homebrew工具的安装、配置过程。homebrew功能类似于centos的yum&#xff0c;用于软件包的管理&#xff0c;使用上有命令的差异。 本次配置过程使用mac&#xff0c;看官方文档&#xff0c;在linux上也可以用&#xff0c;但我没试过&#xff0c;有兴趣的同学可以试试…

对话新晋 Apache SeaTunnel Committer:张圣航的开源之路与技术洞察

近日&#xff0c;张圣航被推选为 Apache SeaTunnel 的 Committer成员。带着对技术的热情和社区的责任&#xff0c;他将如何跟随 Apache SeaTunnel 社区迈向新的高度&#xff1f;让我们一起来聆听他的故事。 自我介绍 请您简单介绍一下自己&#xff0c;包括职业背景、当前的工作…

智慧公厕大数据驱动下的公共卫生管理与优化

在快速发展的城市化进程中&#xff0c;公共卫生问题日益凸显&#xff0c;成为城市管理的重要议题。智慧公厕&#xff0c;作为公共卫生设施的一次革命性创新&#xff0c;正借助物联网技术的东风&#xff0c;引领公共卫生进入一个全新的生态时代。本文将深入探讨智慧公厕如何利用…

后盾人JS--JS值类型使用(终章)

数值类型转换技巧与NaN类型 什么是NaN呢&#xff1f;顾名思义就是&#xff0c;not a number <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width,…

EFK采集k8s日志

在 Kubernetes 集群中&#xff0c;需要全面了解各个 pod 应用运行状态、故障排查和性能分析。但由于 Pod 是动态创建和销毁的&#xff0c;其日志分散且存储不持久&#xff0c;因此需要通过集中式日志采集方案&#xff0c;将日志收集到统一的平台并配置日志可视化分析和监控告警…

探索网络安全:浅析文件上传漏洞

前言 在数字化时代&#xff0c;网络安全已成为我们每个人都需要关注的重要议题。无论是个人隐私保护&#xff0c;还是企业数据安全&#xff0c;网络威胁无处不在。了解网络安全的基本知识和防护措施&#xff0c;对我们每个人来说都至关重要。 网络安全 网络安全并非只是对网…

计算机网络 (36)TCP可靠传输的实现

前言 TCP&#xff08;传输控制协议&#xff09;是一种面向连接的、可靠的、基于字节流的传输层通信协议。TCP通过多种机制实现可靠传输&#xff0c;这些机制主要包括连接管理、序列号和确认应答机制、重传机制、流量控制、拥塞控制等。 一、连接管理 TCP使用三次握手&#xff0…

零样本极速复刻语音!F5-TTS本地部署教程

一、介绍 F5-TTS 是由上海交通大学、剑桥大学和吉利汽车研究院&#xff08;宁波&#xff09;有限公司于 2024 年共同开源的一款高性能文本到语音 (TTS) 系统&#xff0c;它基于流匹配的非自回归生成方法&#xff0c;结合了扩散变换器 (DiT) 技术。。这一系统能够在没有额外监督…

poi处理多选框进行勾选操作下载word以及多word文件压缩

一、场景 将数据导出word后且实现动态勾选复选框操作 eg: word模板 导出后效果&#xff08;根据数据动态勾选复选框&#xff09; 二、解决方案及涉及技术 ① 使用poi提供的库进行处理&#xff08;poi官方文档&#xff09; ② 涉及依赖 <!-- excel工具 --><depen…

关于使用FastGPT 摸索的QA

近期在通过fastGPT&#xff0c;创建一些基于特定业务场景的、相对复杂的Agent智能体应用。 工作流在AI模型的基础上&#xff0c;可以定义业务逻辑&#xff0c;满足输出对话之外的需求。 在最近3个月来的摸索和实践中&#xff0c;一些基于经验的小问题点&#xff08;自己也常常…