STM32使用DMA实现GPIO的高速翻转

news2024/12/22 23:11:41

STM32使用DMA实现GPIO的高速翻转

  • 一、前言
  • 二、原理
  • 三、配置IO口
  • 四、配置DMA
  • 五、程序内容
    • 5.1 gpio配置
    • 5.2 keil中添加`.C`和`.h`文件(需要DMA发送控制GPIO的数组波形文件)==如不想这么麻烦的可以略过,在main函数中定义一个数组即可,因为我的数组太大了,而且为了方便更改,故单独存了一个文件==
    • 5.3 DMA实现GPIO的高速翻转代码实现 ==(memory to memory模式)==
    • 5.4 输出结果
  • 总结

一、前言

接着上一篇博客的内容,上一篇博客实现了定时器输出pwm,这次我们使用DMA来驱动gpio高速翻转,来探索一下stm32h750的gpio翻转极限

二、原理

这里大家可能有点疑惑,为什么要用dma来控制gpio
使用DMA的原因有两点:
第一点就是资源消耗问题,我的项目中有个数组,该数组有13240个元素,每个元素都是16位的,分别对应16个gpio口的状态,当然我可以通过一个循环来发送这些成员,但是这样对cpu的资源消耗非常大,发送的时候cpu就干不了其他事了,如果中断处理其他任务,那么gpio的控制就不连续;尝试过多线程,效果不尽人意
第二点就是速度问题,通过一个循环来发送这些成员,最终结果还是把这些数据写入GPIO的寄存器中,用dma直接将数据搬运到GPIO的寄存器中当然是最快的了

解答一下疑惑,什么是DMA
在这里插入图片描述

顺带提一下GPIO的寄存器,我们一会要用到的是ODR
在这里插入图片描述
在这里插入图片描述

三、配置IO口

接着上一次的工程,配置GPIO口
我的项目中用到 GPIOD0 —— D15和 GPIOE0 —— E15 共32个io,本例程中只用到GPIOE0 —— E15,读者可只配置GPIOE0 —— E15

有两种配置方法
方法一:在cubemx中配置PD0PE0为输出口,再在keil中修改整个PBPE口。此方法的缺点是每次在CUBEMX中修改了功能重新生成之后,都需要在keil中修改
方法二:直接在cubemx中将需要的32个io全部配置为输出。此方法的缺点是工作量较大,选32个io比较辛苦,优点是一劳永逸

在这里我用方法一,读者可按自己的喜好自行选择

根据stm32官方手册介绍,GPIO每次翻转最快只需要两个时钟周期
在这里插入图片描述

在这里插入图片描述

四、配置DMA

添加一个dma,选择存储器到存储器,模式选normal

为什么选存储器到存储器,而不选存储器到外设?GPIO不是外设吗?
关于这个问题,好多博主都只讲了怎么配置dma,并没有详细的讲原理,这里我简单的讲一下:其实选存储器到存储器和选存储器到外设都是一样的,区别在使用的时候绑定外设GPIO寄存去的地址。接下来我们再添加一个存储器到外设的,试验对比一下
但是外设一般是指在stm32芯片之外的设备,我们要控制的GPIO是在stm32芯片上的,所以理论上它不属于外设(但是我们当作是有一个外设以并口的方式连接到GPIO上,也是可以的),这里更推荐选memory to memory模式,即选存储器到存储器

关于循环模式的问题,也简单讲一下,先看图
在这里插入图片描述
看完这个知道你要选normal还是circular了吧,很多人说选了normal,只能发送一次,第二次再发的时候没反应了,至于选normal怎么触发第二次发送的问题,在下一篇博客会讲到

在这里插入图片描述
在这里插入图片描述

五、程序内容

5.1 gpio配置

由于我们刚刚只配置了PD0PE0,所以还需要配置一下使用整个PBPE

打开 main.c 找到 MX_GPIO_Init()函数,将 GPIO_PIN_0改成GPIO_PIN_All

static void MX_GPIO_Init(void)
{
  GPIO_InitTypeDef GPIO_InitStruct = {0};

  /* GPIO Ports Clock Enable */
  __HAL_RCC_GPIOH_CLK_ENABLE();
  __HAL_RCC_GPIOA_CLK_ENABLE();
  __HAL_RCC_GPIOC_CLK_ENABLE();
  __HAL_RCC_GPIOD_CLK_ENABLE();
  __HAL_RCC_GPIOE_CLK_ENABLE();

  /*Configure GPIO pin Output Level */
  HAL_GPIO_WritePin(GPIOD, GPIO_PIN_All, GPIO_PIN_RESET);

  /*Configure GPIO pin Output Level */
  HAL_GPIO_WritePin(GPIOE, GPIO_PIN_All, GPIO_PIN_RESET);

  /*Configure GPIO pin : PD0 */
  GPIO_InitStruct.Pin = GPIO_PIN_All;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
  HAL_GPIO_Init(GPIOD, &GPIO_InitStruct);

  /*Configure GPIO pin : PE0 */
  GPIO_InitStruct.Pin = GPIO_PIN_All;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
  HAL_GPIO_Init(GPIOE, &GPIO_InitStruct);

}

在这里插入图片描述

5.2 keil中添加.C.h文件(需要DMA发送控制GPIO的数组波形文件)如不想这么麻烦的可以略过,在main函数中定义一个数组即可,因为我的数组太大了,而且为了方便更改,故单独存了一个文件

打开工程所在目录,在目录下新建一个文件夹(文件夹名随意,中文没试过,不知道会不会报错)存放.C.h文件,我这里建的文件夹命名为User
可以直接将.C.h文件放置在新建的文件夹里,也可以在文件夹里再新建个文件夹分类存放,总之按个人喜好就行
在这里插入图片描述
在这里插入图片描述
我的.C.h文件内容:

先是WaveData.C文件

#include "WaveData.h"

const unsigned int wave_data[13240] = { 
0X98EC,0X58EC,0X98EC,0X58EC,0X98D4,0X58D4,0X98D4,0X58D4,0X98BC,0X58BC,0X98BC,0X58BC,0X98A4,0X58A4,0X98A4,0X58A4,0X988C,0X588C,
0X9888,0X5888,0X9870,0X5870,0X9870,0X5870,0X9858,0X5858,0X9858,0X5858,0X9840,0X5840,0X9840,0X5840,0X9828,0X5828,0X9828,
0X582C,0X9814,0X5814,0X9814,0X5814,0X97FC,0X57FC,0X97FC,0X57FC,0X97E4,0X57E4,0X97E4,0X57E4,0X97CC,0X57CC,0X97CC,0X57CC,
0X97B0,0X57B0,0X97B0,0X57B0,0X9798,0X5798,0X9798,0X5798,0X9780,0X5780,0X9780,0X5780,0X9768,0X5768,0X9768,0X5768,0X9750,
0X5754,0X9754,0X5754,0X973C,0X573C,0X973C,0X573C,0X9724,0X5724,0X9724,0X5724,0X970C,0X570C,0X970C,0X570C,0X96F4,0X56F4,
0X96F0,0X56F0,0X96D8,0X56D8,0X96D8,0X56D8,0X96C0,0X56C0,0X96C0,0X56C0,0X96A8,0X56A8,0X96A8,0X56A8,0X9690,0X5690,0X9690,
0X5694,0X967C,0X567C,0X967C,0X567C,0X9664,0X5664,0X9664,0X5664,0X964C,0X564C,0X964C,0X564C,0X9634,0X5634,0X9634,0X5634,
0X9618,0X5618,0X9618,0X5618,0X9600,0X5600,0X9600,0X5600,0X95E8,0X55E8,0X95E8,0X55E8,0X95D0,0X55D0,0X95D0,0X55D0,0X95B8,
0X55BC,0X95BC,0X55BC,0X95A4,0X55A4,0X95A4,0X55A4,0X958C,0X558C,0X958C,0X558C,0X9574,0X5574,0X9574,0X5574,0X955C,0X555C,
//...太多了这里不贴了...
}

下面是是WaveData.h文件

#ifndef _IMAGEDATA_H_
#define _IMAGEDATA_H_

// WaveData.c
/* --------------------------------------- */
extern const unsigned int wave_data[];

#endif
/* FILE END */

接下来将WaveData.C文件添加到工程里,并添加路径
在这里插入图片描述
在这里插入图片描述
可以看到.c文件就添加进来了,别急,现在还用不了,还需要添加的.h文件所在的文件夹到工程路径中
在这里插入图片描述
build一下就会发现自动找到.h文件了
在这里插入图片描述

5.3 DMA实现GPIO的高速翻转代码实现 (memory to memory模式)

main.c文件中添加如下代码:

包含头文件

/* USER CODE BEGIN Includes */
#include "WaveData.h"
/* USER CODE END Includes */

如果略过了 < 5.2 keil中添加.C.h文件” > 的可以定义一个dma缓冲区:

/* USER CODE BEGIN PV */
uint16_t dma_buff[12] = {0x0000, 0xFFFF, 0x0000, 0xFFFF, 0x0000, 0xFFFF, 0x0055, 0x00AA, 0x0000, 0xFFFF, 0xAA55, 0x55AA};
/* USER CODE END PV */

启动 DMA 传输

/* USER CODE BEGIN 2 */
HAL_DMA_Start(&hdma_memtomem_dma1_stream0, (uint32_t)(dma_buff), (uint32_t)(&GPIOE->ODR), sizeof(dma_buff)/sizeof(dma_buff[0]));
/* USER CODE END 2 */

5.4 输出结果

== 需要注意,因为我们前面配置DMA选的是normal模式,该模式只发送一次即停止发送了,所以只有在开发板商店的一瞬间或触发一次DMA搬运,搬运完即停止搬运了,后面就没有输出了,看图 ==
在这里插入图片描述
我这个是用的我的数组,13240个元素,如果用的是uint16_t dma_buff[12] = {0x0000, 0xFFFF, 0x0000, 0xFFFF, 0x0000, 0xFFFF, 0x0055, 0x00AA, 0x0000, 0xFFFF, 0xAA55, 0x55AA};,则转瞬即逝,如图
在这里插入图片描述
在这里插入图片描述
发现和我们预设的数组并不一致,这里可能是dma的总线频率设置太高了,降一下频率试试
在这里插入图片描述

DMA挂在AHB1总线上的,将AHB1由原来的240MHz降低至120MHz
在这里插入图片描述
再次测试
在这里插入图片描述

看起来没什么问题,可以看到翻转频率达到了20MHz,导出数据解析一下
在这里插入图片描述

和我们的数组uint16_t dma_buff[12] = {0x0000, 0xFFFF, 0x0000, 0xFFFF, 0x0000, 0xFFFF, 0x0055, 0x00AA, 0x0000, 0xFFFF, 0xAA55, 0x55AA};匹配,没有问题

如果要方便观察,可以将DMA选为CIRCULAR模式,或者直接在void MX_DMA_Init(void)函数中将DMA模式由 DMA_NORMAL 更改为 DMA_CIRCULAR 模式,在DMA_CIRCULAR 模式下,会自动循环发送dma_buff[8]的内容

static void MX_DMA_Init(void)
{

  /* DMA controller clock enable */
  __HAL_RCC_DMA1_CLK_ENABLE();

  /* Configure DMA request hdma_memtomem_dma1_stream0 on DMA1_Stream0 */
  hdma_memtomem_dma1_stream0.Instance = DMA1_Stream0;
  hdma_memtomem_dma1_stream0.Init.Request = DMA_REQUEST_MEM2MEM;
  hdma_memtomem_dma1_stream0.Init.Direction = DMA_MEMORY_TO_MEMORY;
  hdma_memtomem_dma1_stream0.Init.PeriphInc = DMA_PINC_ENABLE;
  hdma_memtomem_dma1_stream0.Init.MemInc = DMA_MINC_DISABLE;
  hdma_memtomem_dma1_stream0.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;
  hdma_memtomem_dma1_stream0.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;
  hdma_memtomem_dma1_stream0.Init.Mode = DMA_CIRCULAR;//DMA_NORMAL;  //改为DMA_CIRCULAR模式
  hdma_memtomem_dma1_stream0.Init.Priority = DMA_PRIORITY_LOW;
  hdma_memtomem_dma1_stream0.Init.FIFOMode = DMA_FIFOMODE_ENABLE;
  hdma_memtomem_dma1_stream0.Init.FIFOThreshold = DMA_FIFO_THRESHOLD_FULL;
  hdma_memtomem_dma1_stream0.Init.MemBurst = DMA_MBURST_SINGLE;
  hdma_memtomem_dma1_stream0.Init.PeriphBurst = DMA_PBURST_SINGLE;
  if (HAL_DMA_Init(&hdma_memtomem_dma1_stream0) != HAL_OK)
  {
    Error_Handler( );
  }

  /* Configure DMA request hdma_dma_generator0 on DMA1_Stream1 */
  hdma_dma_generator0.Instance = DMA1_Stream1;
  hdma_dma_generator0.Init.Request = DMA_REQUEST_GENERATOR0;
  hdma_dma_generator0.Init.Direction = DMA_MEMORY_TO_PERIPH;
  hdma_dma_generator0.Init.PeriphInc = DMA_PINC_ENABLE;
  hdma_dma_generator0.Init.MemInc = DMA_MINC_DISABLE;
  hdma_dma_generator0.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;
  hdma_dma_generator0.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;
  hdma_dma_generator0.Init.Mode = DMA_NORMAL;
  hdma_dma_generator0.Init.Priority = DMA_PRIORITY_LOW;
  hdma_dma_generator0.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
  if (HAL_DMA_Init(&hdma_dma_generator0) != HAL_OK)
  {
    Error_Handler( );
  }

}

在这里插入图片描述

可以看到,改成循环模式后就会一直循环发送

为了方便观察,我们把数组改成如下,dma改成循环发送

uint16_t dma_buff[12] = {0x0000, 0xFFFF, 0x0000, 0xFFFF, 0x0000, 0xFFFF, 0x0000, 0xFFFF, 0x0000, 0xFFFF, 0x0000, 0xFFFF};

结果如图:
在这里插入图片描述

总结

最后探了一下STM32H750VBT6DMA控制GPIO的极限翻转频率,可以看到虽然可以到40Mhz,但是已经不稳定了,比较稳定的是在10MHz以下

图片中dma缓冲区为 uint16_t dma_buff[12] = {0x0000, 0xFFFF, 0x0000, 0xFFFF, 0x0000, 0xFFFF, 0x0000, 0xFFFF, 0x0000, 0xFFFF, 0x0000, 0xFFFF};
可以看到后面多了一些东西
在这里插入图片描述

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

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

相关文章

0基础学C#笔记07:选择排序法

文章目录 前言一、选择排序原理二、使用步骤三、打印结果总结 前言 我们常用的排序方法有十种&#xff0c;分别是&#xff1a; 冒泡排序&#xff1b;选择排序&#xff1b;插入排序&#xff1b;希尔排序&#xff1b;归并排序&#xff1b;快速排序&#xff1b;堆排序&#xff1…

全志F1C200S嵌入式驱动开发(spi-nor驱动)

【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱:feixiaoxing @163.com】 和v3s一样,f1c200s本身也支持spi-nor flash。当然,不管是norflash,还是nandflash,都是为了能够让程序脱离sd卡,直接依靠板子上面的flash,就可以完成正常地加载和运行工作。tf…

《Docker容器编排模式:了解Sidecar、Ambassador等模式,构建高效稳定的容器化应用》

&#x1f337;&#x1f341; 博主 libin9iOak带您 Go to New World.✨&#x1f341; &#x1f984; 个人主页——libin9iOak的博客&#x1f390; &#x1f433; 《面试题大全》 文章图文并茂&#x1f995;生动形象&#x1f996;简单易学&#xff01;欢迎大家来踩踩~&#x1f33…

基于 Graviton2处理器构建容器化基因分析工作负载

概述 相对于基于传统 x86架构的处理器来说&#xff0c;Amazon 设计的基于 ARM 架构的 Graviton 处理器为 EC2中运行的云工作负载提供了更佳的性价比。基于 Graviton2 的实例支持广泛的通用型、突发型、计算优化型、内存优化型、存储优化型和加速计算型工作负载&#xff0c;包括…

重生之我要学c++第二课

在上期内容&#xff0c;我们讲述了c相比于C语言的更简易之处&#xff0c;本期就让我们继续学习相关的知识&#xff0c;了解c宇宙 引用(起别名) 引用不是新定义一个变量&#xff0c;而是给已存在变量取了一个别名&#xff0c;编译器不会为引用变量开辟内存空 间&#xff0c;它…

基于 KubeKey 扩容 Kubernetes v1.24 Worker 节点实战

前言 知识点 定级&#xff1a;入门级KubeKey 扩容 Worker 节点openEuler 操作系统的基本配置Kubernets 基本命令 实战服务器配置(架构 1:1 复刻小规模生产环境&#xff0c;配置略有不同) 主机名IPCPU内存系统盘数据盘用途ks-master-0192.168.9.912450100KubeSphere/k8s-mas…

新能源汽车的发展

目录 1.什么是新能源 2.什么是新能源汽车 3.新能源汽车的优点 4.新能源汽车的危害 5.新能源汽车未来的发展 1.什么是新能源 新能源是指与传统能源&#xff08;如化石燃料&#xff09;相比&#xff0c;更具可再生性、清洁性和低碳排放的能源形式。它主要通过利用自然资源和可…

C语言---每天小练习,从大到小输出

题目&#xff1a;从大到小输出 写代码将三个整数数按从大到小输出。 例如&#xff1a; 输入&#xff1a;2 3 1 输出&#xff1a;3 2 1 int main() {// 初始化int a 0;int b 0;int c 0;int d 0;scanf("%d %d %d", &a, &b, &c);if (a < b) {…

Unity Shader - UI/Default shader 优化示例

文章目录 环境优化示例Texture Format : Alpha 8 和 shaderlab : _TextureSampleAddshaderlab : _UIMaskSoftnessX 和 _UIMaskSoftnessYshaderlab _Colorshader ARM Mobile Studio - Graphics Analyzer优化前优化后 环境 Unity : 2020.3.37f1 Pipeline : BRP 优化 做性能优化…

laravel10.x nginx服务推荐配置文件

laravel10.x 服务器配置 如果您正在将应用程序部署到运行Nginx的服务器&#xff0c;则可以使用以下配置文件作为配置web服务器的起点。很可能&#xff0c;此文件需要根据服务器的配置进行自定义。如果你想在管理服务器方面获得帮助&#xff0c;可以考虑使用第一方Laravel服务器…

3分钟搭建一个springboot项目并运行起来

第一步&#xff1a; 创建一个maven项目。 第二步&#xff1a; 导入maven依赖&#xff0c;代码如下&#xff1a; <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0"xmlns:xsi"http:/…

执行 yum install gcc 报 【-bash: $‘yum\302\240install\302\240gcc‘: 未找到命令】

执行 yum install gcc 报错 找了一圈&#xff0c;执行&#xff1a;sudo apt-get install yum 执行&#xff1a;wget http://yum.baseurl.org/download/3.2/yum-3.2.28.tar.gz 在线下载yum完成 对其进行解压&#xff1a;tar zxvf yum-3.2.28.tar.gz 解压后如下&#xff1a; 执行…

vue3+h5实现虚拟列表

目录 vue3h5实现虚拟列表component / List.vuecomponent / itemComponent.vue使用组件效果 vue3h5实现虚拟列表 安装&#xff1a;npm i vue3-virtual-scroll-listpackage.json "dependencies": {"vue": "^3.2.36","vue3-virtual-scroll-li…

7.20 ARM-A7核心三盏LED灯

思维导图 汇编代码&#xff1a; .text .global _start _start: /**********LED1点灯**************/ RCC_INIT_E:ldr r0,0x50000A28ldr r1,[r0]orr r1,r1,#(0x1 << 4)str r1,[r0]RCC_INIT_F:ldr r0,0x50000A28ldr r1,[r0]orr r1,r1,#(0x1 << 5)str r1,[r0]LED_INI…

SAP 后台作业简单介绍 job

主要事务代码&#xff1a;SM36、SM37 作业设置 1. 一般数据&#xff1a;作业名称&#xff0c;优先级&#xff0c;目标服务器 优先级适用于资源受限情况&#xff0c;目标服务器适用于服务器环境依赖的程序&#xff0c;比如操作服务器文件等类似的处理 2. 开始条件&#xff0c;…

LRU页面置换算法(C语言实现)

1、实验目的 &#xff08;1&#xff09;熟悉虚拟存储器页面置换过程&#xff1b; &#xff08;2&#xff09;通过编写和调试页面置换算法的模拟程序以加深对页面置换算法的理解&#xff1b; &#xff08;3&#xff09;掌握LRU算法的原理&#xff1b; &#xff08;4&#xf…

Ubuntu中安装Vivado软件

文章目录 Ubuntu中安装Vivado加载License修改软件运行权限安装下载器驱动运行Vivado软件连接开发板测试驱动交叉编译器 Ubuntu中安装Vivado 跨系统文件复制的设置在文章Ubuntu 的安装及其设置中已经介绍过了。 在Ubuntu中找到一个需要存放Vivado软件安装包的文件夹&#xff0c…

二分图博弈学习笔记

前言&#xff1a;最近每场训练赛都有博弈题&#xff0c;而且我都被薄纱了。。。真烦 二分图博弈是少有的直接跟图论挂钩的一种博弈模型 一个博弈是二分图博弈应当满足一下条件&#xff1a; 博弈人数为两人&#xff0c;轮流操作 博弈状态转移可以表示成一张二分图 不可访问已…

MySQL 的 crash-safe浅谈

MySql执行流程 MySQL作为当下最流行的开源关系型数据库&#xff0c;有一个很关键和基本的能力&#xff0c;就是必须能够保证数据不会丢。那么在这个能力背后&#xff0c;MySQL是如何设计才能保证不管在什么时间崩溃&#xff0c;恢复后都能保证数据不会丢呢&#xff1f;有哪些…

在Win11的WSL子系统Ubuntu上安装Gnome桌面环境

目录 1. 使用 WSL 在 Win11 上安装 Linux 2. 安装Ubuntu 22.04默认Gnome桌面环境 2.1更新Ubuntu 22.04软件包 2.2 安装Ubuntu桌面环境 2.3 重启服务 2.4 重启Ubuntu 22.04系统 2.5 登录Gnome桌面环境 在Win11上安装ubuntu版linux系统并实现默认Gnome桌面环境&#xff08…