细说MCU用DMA控制ADC采样和串口传送的实现方法

目录

一、建立工程

1.相同的配置

2.配置ADC

3.配置DMA

二、代码修改

1.定义存储ADC采样结果的数组

2.启动ADC与定时器

3.编写主程序代码

4.重定义回调函数

5.查看结果

三、修改DMA模式

1. 修改DMA模式为Circular

2.查看结果


        采用DMA(Direct Memory Access,直接存储器访问)控制器实现A/D采样。采用这种方式时,一旦配置好ADC参数及所使用的DMA通道,DMA控制器就会自动将A/D转换结果送至指定的存储器空间中(数组)。在使用A/D转换数据时,只需要在主程序中读取相应的数组变量就可以了,无需再调用HAL_ADC_GetValue()等函数来获取A/D转换结果。

        采用DMA的方式可以不占用CPU的资源,直接由DMA控制器来实现外设(或存储器与存储器之间的数据交互。所以,这种方式在实际中是比较实用的,并且可以极大地提高CPU的工作效率。

一、建立工程

       本文项目以来的硬件工程及配置参考本文作者的下述文章,工程配置基本一致。本文只描述不一样的地方。细说MCU用定时器控制ADC采样频率的实现方法并通过Simulink查看串口输出波形-CSDN博客  https://wenchm.blog.csdn.net/article/details/140523545icon-default.png?t=N7T8https://wenchm.blog.csdn.net/article/details/140523545

1.相同的配置

  • 配置串口;
  • 配置TIM3,TIM4;
  • 选择时钟源和Debug模式,配置系统时钟和ADC时钟;
  • 配置GPIO,LED;

2.配置ADC

        在硬件配置界面中打开Analog→ADC1,在其模式Mode区中,通道1(IN1)选择IN1 Single-ended;在下面的配置(Configuration)区中,需要对几个参数进行调整:

        首先,在ADC设置(ADC_Settings)参数栏,依然可以不对ADC的时钟进行分频,还将预分频参数(Clock Prescaler)选择为Asynchronous clock mode divided by 1。本例中用定时器实现对采样频率的控制。

        随后,依然将ADC设置(ADC_Settings)参数栏中连续转换模式(Continuous Conversion Mode)设置为Disabled,由于要用DMA,所以需要使能DMA连续请求(DAM Continuous Requests)参数。

3.配置DMA

        打开DMA设置(DMA Settings)选项卡,先添加一个ADC1的DMA请求。DMA有多个可选通道,这里随便选择一个即可(共有两个DMA,每个都有8个通道)。此外,优先级有四级,从低(Low)到很高(Very High),可以先保持默认值Low。

        DMA请求设置(DMA Request Settings)栏,可以设置DMA的模式;模式有两种:常规(Normal)和循环(Circular)。如果是Normal模式,仅会执行一次DMA,若要继续执行,则要重新启动。在Circular模式下,可以连续执行DMA。此例中,先将DMA模式设置为Normal。此外,在增量地址(Increment Address)中,勾选上存储器(Memory),这样就可以将数据顺次存储到一个数组中。因为A/D的转换结果需要一个16位的数,所以将数据宽度(Data Width)设置为半字(Half Word),一个字为32位。

        ADC1的DMA请求设置完毕后,设置DMA连续请求(DAM Continuous Requests)参数为Enabled。

        在ADC规则转换模式(ADC_Regular_ConversionMode)栏,还是将外部触发转换源(External Trigger Conversion Source)选择为Timer 3 Trigger Out event。在ADC规则转换模式参数栏中,将Rank下的采样时间选择为2.5个周期。

        由于是使用DMA来实现将A/D采样结果传递到存储器(数组)的,所以无需配置ADC的中断。不过,因为配置了ADC的DMA功能,所以会用到DMA的中断。由于上面配置的是DMA1的通道1,所以会自动开启DMA1的通道1中断。打开ADC配置界面中的NVIC设置(NVIC Settings),可以看到DMA1 channel 1 global interrupt已经自动被使能了,并且不能取消。另外一个ADC1的中断(ADC1 and AD2 global interrupt)由于用不到,所以无需开启。

二、代码修改

1.定义存储ADC采样结果的数组

        首先定义存储ADC采样结果的数组,本例中用数组变量ADC1ConvertedData。将存储ADC采样结果的数组定义为全局变量,同时定义一个后面会用到的变量ADCDMAFlag,将它们一并放到主程序中的注释对中:

/* USER CODE BEGIN PV */
uint16_t ADC1ConvertedData[ADC_CONVERTED_DATA_BUFFER_SIZE] = {0};
uint8_t ADCDMAFlag = 0;
/* USER CODE END PV */

        其中,数组长度ADC_CONVERTED_DATA_BUFFER_SIZE可以定义到main.h中:

/* USER CODE BEGIN Private defines */
#define ADC_CONVERTED_DATA_BUFFER_SIZE (uint16_t) 60
/* USER CODE END Private defines */

2.启动ADC与定时器

        本例中无需开启ADC1的中断。不过,要在主函数的初始化代码中调用ADC校验函数HAL_ADCEx_Calibration_Start,启动DMA方式的ADC转换(通过调用HAL_ADC_Start_DMA函数),并开启TIM3(通过调用HAL_TIM_Base_Start)。将上述三个函数的调用放到while(1)之前、MX_ADC1_Init()之后的注释对中:

 /* USER CODE BEGIN 2 */HAL_ADCEx_Calibration_Start(&hadc1,ADC_SINGLE_ENDED);HAL_ADC_Start_DMA(&hadc1,(uint32_t*)&ADC1ConvertedData,ADC_CONVERTED_DATA_BUFFER_SIZE);HAL_TIM_Base_Start(&htim3);HAL_TIM_Base_Start_IT(&htim4);HAL_TIM_PWM_Start(&htim4,TIM_CHANNEL_1);/* USER CODE END 2 */

        ADC的采样是由TIM3控制的,采样值存入存储器(数组)的过程是通过DMA完成的,即ADC采样值在DMA控制器的控制下直接传送到数组ADC1ConvertedData中。虽然没有开启ADC1的中断,但在DMA完成设定长度的ADC采样数据传递后,也会调用一次回调函数HAL_ADC_ConvCpltCallback()。这里所谓的“设定长度”,就是函数HAL_ADC_Start_DMA()中的第三个参数。该参数在前面的代码中被设定为60。

        TIM4用来产生信号源。

3.编写主程序代码

        如果要通过串口送出采样值数据,可以在本次DMA传送完毕后进行。如果DMA还在更新时就进行串口数据发送,可能会出现数据不连续的情况。所以,可以在回调函数HAL_ADC_ConvCpltCallback()中将一个标志变量置位(可使用前面定义的变量ADCDMAFlag),置位就表示DMA传送完毕。然后,在while(1)循环中,以此标志位为条件,实现一段完整的采样值数据发送。串口数据发送,可以通过在主程序中调用串口发送函数来实现。

  /* Infinite loop *//* USER CODE BEGIN WHILE */while (1){/* USER CODE END WHILE *//* USER CODE BEGIN 3 */if(ADCDMAFlag == 1){ADCDMAFlag = 0;HAL_ADC_Stop_DMA(&hadc1);HAL_UART_Transmit(&huart2,(uint8_t*)&ADC1ConvertedData,ADC_CONVERTED_DATA_BUFFER_SIZE*2,0xFFFF);HAL_ADC_Start_DMA(&hadc1,(uint32_t*)&ADC1ConvertedData,ADC_CONVERTED_DATA_BUFFER_SIZE);HAL_Delay(1000);}}/* USER CODE END 3 */

        上面这段代码中有两个是控制DMA的函数,有一个是串口发送数据的函数。第一个函数是让DMA停止工作,暂停数据搬运,然后用函数HAL_UART_Transmit发送A/D采样数据。注意,在HAL_UART_Transmit的参数中,设置发送数据的长度为ADC采样数据的2倍,这是因为串口每次只能发送1个字节的数据,而一个A/D采样值会占用2个字节。数据发送完毕后,再重新启动ADC的DMA传输。

4.重定义回调函数

        此外,在main.c中重新定义回调函数HAL_ADC_ConvCpltCallback():

/* USER CODE BEGIN 4 */
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* AdcHandle)
{ADCDMAFlag =1;
}//信号源
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{HAL_GPIO_TogglePin(LED_GPIO_Port,LED_Pin);
}
/* USER CODE END 4 */

5.查看结果

        施加信号源(可以是PA5)到ADC输入端PA0上,打开串口接收的Simulink模型,即可看到通过串口送来的信号波形。

三、修改DMA模式

1. 修改DMA模式为Circular

        上面例子中,主程序每间隔1000 ms发送一组数据;每次发送前要关闭DMA,发送后再重启。这种方式送来的两组数据其实并非连续的数据。那么,如何让串口实时向外连续发送A/D采样的数据呢?

        在前面配置ADC1的DMA、设置ADC1的DMA请求的模式时,选择的是Normal。如果选择Circular,DMA就会持续传送ADC采样数据到数组中,不过会循环覆盖;如果能够在下次DMA数据传递完成前将数据发送出去,就不会有影响。假如还是设置ADC采集缓冲区长度为60,则DMA一次会传送60个采样值数据;因为采样频率为1 kHz,所以完成这些数据的采样需要60 ms的时间。加上DMA的处理时间,DMA完成这些数据的传递至少需要60 ms。这60个ADC采样值,占120个字节。串口发送1个字节的数,至少要发送10个二进制位(8个数据位、1个停止位和1个起始位),所以发送120个字节的数据,对应的二进制位数为1200,而设置的串口波特率为115200 bit/s发送1200位需要的时间为(1200/115200)s,约为10.4 ms。这个时间小于DMA搬运一次数据所需的60 ms。所以完全可以实现通过串口的数据实时发送。

        修改while(1)循环中的代码如下:

  /* Infinite loop *//* USER CODE BEGIN WHILE */while (1){/* USER CODE END WHILE *//* USER CODE BEGIN 3 *///DMA标准模式/*if(ADCDMAFlag == 1){ADCDMAFlag = 0;HAL_ADC_Stop_DMA(&hadc1);HAL_UART_Transmit(&huart2,(uint8_t*)&ADC1ConvertedData,ADC_CONVERTED_DATA_BUFFER_SIZE*2,0xFFFF);HAL_ADC_Start_DMA(&hadc1,(uint32_t*)&ADC1ConvertedData,ADC_CONVERTED_DATA_BUFFER_SIZE);HAL_Delay(1000);} *///DMA循环模式if(ADCDMAFlag ==1){ADCDMAFlag =0;HAL_UART_Transmit(&huart2,(uint8_t *)&ADC1ConvertedData,ADC_CONVERTED_DATA_BUFFER_SIZE*2,0xFFFF);}}/* USER CODE END 3 */

2.查看结果

 

         DMA模式修改为循环后,串口的工作效率更高,不丢数据,达到了实时传递数据的效果。

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

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

相关文章

WSL2 Centos7 Docker服务启动失败怎么办?

wsl 安装的CentOS7镜像,安装了Docker之后,发现用systemctl start docker 无法将docker启动起来。 解决办法 1、编辑文件 vim /usr/lib/systemd/system/docker.service将13行注释掉,然后在下面新增14行的内容。然后保存退出。 2、再次验证 可以发现,我们已经可以正常通过s…

关于Mysql的面试题(实时更新中~)

一、主键约束与“not null unique”区别 1、作为Primary Key的域/域组不能为null,而Unique Key可以。 2、在一个表中只能有一个Primary Key,而多个Unique Key可以同时存在。unique not null 可以 将表的一列或多列定义为唯一性属性,而prima…

redis的集群模式

目录 1. 为什么使用redis集群 2. 主从模式 2.1修改配置文件 2.2 开启三台redis服务 2.3配置主从关系 3. 哨兵模式 3.1 监控功能 3.2 选举的机制 3.3 准备条件 4. 去中心化模式 4.1 准备三主三从 4.2 启动redis 4.3 分配槽以及主从关系 4.4 命令行的客户端 redis提供…

CAD框架介绍

1、适用范围:矢量编辑软件如 服装模板软件、CAD软件、绘图软件 2、支持PLT,DXF,PDF,GCode(服装裁割指令)等矢量文件导入 3、支持简易的自动手动排料 4、直线,曲线等编辑功能 5、分页输出绘图指令 6、良好的框架结构:绘图引擎…

d3d12.dll 文件缺失如何解决?五种修复丢失问题的方法

d3d12.dll 文件缺失如何解决?它为什么会不见呢?今天,我们将探讨 d3d12.dll 文件的重要性、原因以及丢失时的解决策略。本文将全面介绍 d3d12.dll 文件,并提供五种修复丢失问题的方法。 d3d12.dll文件是什么的详细介绍 d3d12.dll …

laravel为Model设置全局作用域

如果一个项目中存在这么一个sql条件在任何情况下或大多数情况都会被使用,同时很容易被开发者遗忘,那么就非常适用于今天要提到的这个功能,Eloquent\Model的全局作用域。 首先看一个示例,有个数据表,结构如下&#xff1…

深入浅出WebRTC—NACK

WebRTC 中的 NACK(Negative Acknowledgment)机制是实时通信中处理网络丢包的关键组件。网络丢包是常见的现象,尤其是在无线网络或不稳定连接中。NACK 机制旨在通过请求重传丢失的数据包来减少这种影响,从而保持通信的连续性和质量…

vue学习笔记(十一)——开发心得(axios的封装、promise细节、vue-router开发中的使用)

1. axios的网络请求的封装 1.1 为什么要封装api? 代码分层,便于以后的修改,无需触碰逻辑页面 目标: 网络请求,不散落在各个逻辑页面里,封装起来方便以后修改 1.2 封装api步骤 ① 在项目 src 下新建目录 utlis &am…

C++——初识模板

前言 模板是C中的重大板块,是使C真正超越C语言的工具,在C模板没有设计出来之前其实C是没有那么被行业和社会所认可的,本节我们将初步了解C中的模板(仅作大致讲解,具体的细枝末节将会再过几节讲解)&#xf…

Qt多语言功能实现

本文介绍Qt多语言功能实现。 应用程序多语言支持是常用功能,比如产品需要出口到不同语种的国家。采用Qt的多语言支持工具可以方便实现应用程序的多语言功能。本文以中英文语言切换为例,简要介绍Qt的多语言功能实现。 1.界面设计 界面设计需要考虑使用…

【数据分享】2013-2022年我国省市县三级的逐日SO2数据(excel\shp格式\免费获取)

空气质量数据是在我们日常研究中经常使用的数据!之前我们给大家分享了2000——2022年的省市县三级的逐日PM2.5数据和2013-2022年的省市县三级的逐日CO数据(均可查看之前的文章获悉详情)! 本次我们分享的是我国2013——2022年的省…

数据隐私保护与区块链技术的结合:新兴趋势分析

在当今数字化时代,数据隐私保护成为了一个备受关注的重要话题。随着个人数据的不断生成和流通,如何有效保护用户的隐私成为了技术创新的一个重要方向。区块链技术作为一种去中心化、安全性高且可追溯的技术手段,正在逐渐成为解决数据隐私保护…

golang 基础 泛型编程

(一) 示例1 package _caseimport "fmt"// 定义用户类型的结构体 type user struct {ID int64Name stringAge uint8 }// 定义地址类型的结构体 type address struct {ID intProvince stringCity string }// 集合转列表函数&#…

杰发科技Bootloader(2)—— 基于7840的Keil配置地址

序 在7840的sample代码里面有一个简单的Boot跳转APP的示例 PFlash地址从0开始 DFlash的地址从1000000开始 Boot解析 他的boot地址配置为0 Boot的代码主要是这几行,主要作用就是Flash的跳转 int main(void) {SystemClock_Config();InitDebug();printf("demo…

Leetcode 721.账户合并(hash+dfs)☆

思路: 最核心的地方在于如何合并?这里是通过具有相同的email进行账户的合并,这个相同的email类似于图中的共同节点将两个账户连接起来,所以将原来 账户名 -> 邮件1 邮件2.。。变成hash 邮件1 ->账户id1,账户id2…

ModelArts中sinh算子的开发

一、环境配置 1、创建notebook并连接 使用ModelArts新建一个notebook,我这里镜像选择第一个,里面含有cann和Ascend910处理器,我这里环境只能使用ssh连接,创建一个密钥对,保存到C盘中的user/Administrator/目录下。 在网页中选择使用vscode接入,等待vscode打开后,选择密…

【数据结构初阶】一篇文章带你超深度理解【单链表】

hi ! 目录 前言: 1、链表的概念和结构 2、单链表(Single List,简写SList)的实现 2.1 定义链表(结点)的结构 2.2 创建一个链表 2.3 打印链表 2.4 尾插 2.5 头插 2.6 尾删 2.7 头…

PT2262-IR

PT2262是一款很古老的编码芯片,其兼容型号有:SC2262,AD2262,SC2260(需改变匹配电阻)等。 依据其datasheet,PT2262射频模式工作原理: CODE BITS A Code Bit is the basic component of the encoded waveform, and ca…

Docker核心技术:Docker原理之Cgroups

云原生学习路线导航页(持续更新中) 本文是 Docker核心技术 系列文章:Docker原理之Cgroups,其他文章快捷链接如下: 应用架构演进容器技术要解决哪些问题Docker的基本使用Docker是如何实现的 Docker核心技术:…

Maven学习——Maven的下载、安装与配置(详细攻略!)

目录 前言 1.下载与安装 2.配置Maven的环境变量 3.配置Maven的本地仓库 4. 配置Maven的镜像远程仓库 前言 我在之前写了一篇博客,是介绍Maven的基本概念和下载安装,但是由于篇幅过长,Maven的下载与安装写的并不详细🐶&#x…