细说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.mzph.cn/web/46944.shtml

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

相关文章

WebRTC QOS方法十三.1(TimestampExtrapolator接收时间预估)

一、背景介绍 虽然我们可通过时间戳的差值和采样率计算出发送端视频帧的发送节奏,但是由于网络延迟、抖动、丢包,仅知道视频发送端的发送节奏是明显不够的。我们还需要评估出视频接收端的视频帧的接收节奏,然后进行适当平滑,保证…

卷积神经网络【CNN】--池化层的原理详细解读

池化层(Pooling Layer)是卷积神经网络(CNN)中的一个关键组件,主要用于减少特征图(feature maps)的维度,同时保留重要的特征信息。 一、池化层的含义 池化层在卷积神经网络中扮演着降…

JavaScript与DOM的奇妙探险:从入门到精通的实战笔记

文章目录 JavaScript基本说明特点两种使用方式在script中写使用script标签引入JS文件 数据类型介绍特殊值 运算符算数运算符赋值运算符逻辑运算符:![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/bbf5c150699845af837d3c45c926e941.png)条件运算符 数组的…

Java_Docker

镜像和容器: 镜像仓库: 存储和管理镜像的平台,镜像仓库中有非常多常用软件的镜像,Docker官方维护了一个公共仓库​​​​​​:​Docker Hub 部署MySQL: docker run -d \--name mysql \-p 3306:3306 \-e TZAsia/Shang…

Guns v7.3.0:基于 Vue3、Antdv 和 TypeScript 打造的开箱即用型前端框架

摘要 本文深入探讨了Guns v7.3.0前端项目,该项目是基于Vue3、Antdv和TypeScript的前端框架,以Vben Admin的脚手架为基础进行了改造。文章分析了Guns 7.3.0的技术特点,包括其使用Vue3、vite2和TypeScript等最新前端技术栈,以及提供…

如何防止热插拔烧坏单片机

大家都知道一般USB接口属于热插拔,实际任意带电进行连接的操作都可以属于热插拔。我们前面讲过芯片烧坏的原理,那么热插拔就是导致芯片烧坏的一个主要原因之一。 在电子产品的整个装配过程、以及产品使用过程经常会面临接口热插拔或者类似热插拔的过程。…

IDEA的工程与模块管理

《IDEA破解、配置、使用技巧与实战教程》系列文章目录 第一章 IDEA破解与HelloWorld的实战编写 第二章 IDEA的详细设置 第三章 IDEA的工程与模块管理 第四章 IDEA的常见代码模板的使用 第五章 IDEA中常用的快捷键 第六章 IDEA的断点调试(Debug) 第七章 …

Redis的AOF持久化策略(AOF的工作流程、AOF的重写流程,操作演示、注意事项等)

文章目录 缓冲AOF 策略(append only file)AOF 的工作流程AOF 缓冲区策略AOF 的重写机制重写完的AOF文件为什么可以变小?AOF 重写流程 缓冲AOF 策略(append only file) AOF 的核心思路是 “实时备份“,只要我添加了新的数据或者更新了新的数据&#xff0…

问题:4、商业保险与政策性保险的主要不同之处是:经营主体不同、经营目标不同、承保机制不同。 #学习方法#其他#学习方法

问题:4、商业保险与政策性保险的主要不同之处是:经营主体不同、经营目标不同、承保机制不同。 参考答案如图所示

Linux云计算 |【第一阶段】ENGINEER-DAY3

主要内容: LVM逻辑卷管理、VDO、RAID磁盘阵列、进程管理 一、新建逻辑卷 1、什么是逻辑卷 逻辑卷(Logical Volume)是逻辑卷管理(Logical Volume Management,LVM)系统中的一个概念。LVM是一种用于磁盘管理…

【人工智能】机器学习 -- 贝叶斯分类器

目录 一、使用Python开发工具,运行对iris数据进行分类的例子程序NaiveBayes.py,熟悉sklearn机器实习开源库。 1. NaiveBayes.py 2. 运行结果 二、登录https://archive-beta.ics.uci.edu/ 三、使用sklearn机器学习开源库,使用贝叶斯分类器…

[React 进阶系列] useSyncExternalStore hook

[React 进阶系列] useSyncExternalStore hook 前情提要,包括 yup 的实现在这里:yup 基础使用以及 jest 测试 简单的提一下,需要实现的功能是: yup schema 需要访问外部的 storage外部的 storage 是可变的React 内部也需要访问同…

产品经理-工作中5大类技术名词解析(19)

在产品经理与开发的团队协作中,如果自己知道一些专业术语,对业务的开展是有帮助的,很多时候,在沟通过程当中,就是因为自己不懂,所以才不知道怎么去做,想要什么样的结果 在力所能及的情况下,平时,多了解一些专业术语,是有好处的 数据结构 数据结构是技术人员将数据进…

【iOS】static、extern、const、auto关键字以及联合使用

目录 前言extern关键字static关键字const关键字 联合使用static和externstatic和constextern和const auto关键字 先了解一下静态变量所在的全局/静态区的特点:【iOS】内存五大分区 前言 上面提到的全局/静态区中存放的是全局变量或静态变量: 全局变量…

人工智能大模型发展的新形势及其省思

作者简介 肖仰华,复旦大学计算机科学技术学院教授、博导,上海市数据科学重点实验室主任。研究方向为知识图谱、知识工程、大数据管理与挖掘。主要著作有《图对称性理论及其在数据管理中的应用》、《知识图谱:概念与技术》(合著&a…

C++基础语法:STL之容器(5)--序列容器中的list(二)

前言 "打牢基础,万事不愁" .C的基础语法的学习 引入 序列容器的学习.以<C Prime Plus> 6th Edition(以下称"本书")内容理解 本书中容器内容不多只有几页.最好是有数据结构方面的知识积累,如果没有在学的同时补上 接上一篇C基础语法:STL之容器…

excel系列(三) - 利用 easyexcel 快速实现 excel 文件导入导出

一、介绍 在上篇文章中&#xff0c;我们介绍了 easypoi 工具实现 excel 文件的导入导出。 本篇我们继续深入介绍另一款更优秀的 excel 工具库&#xff1a;easyexcel 。 二、easyexcel easyexcel 是阿里巴巴开源的一款 excel 解析工具&#xff0c;底层逻辑也是基于 apache p…

HTTPS 的加密过程 详解

HTTP 由于是明文传输&#xff0c;所以安全上存在以下三个风险&#xff1a; 窃听风险&#xff0c;比如通信链路上可以获取通信内容。篡改风险&#xff0c;比如通信内容被篡改。冒充风险&#xff0c;比如冒充网站。 HTTPS 在 HTTP 与 TCP 层之间加入了 SSL/TLS 协议&#xff0c…

Spring Cloud LoadBalanced

负载均衡(Load Balance&#xff0c;简称 LB) 是⾼并发, ⾼可⽤系统必不可少的关键组件. 当服务流量增⼤时, 通常会采⽤增加机器的⽅式进⾏扩容, 负载均衡就是⽤来在多个机器或者其他资源中, 按照⼀定的规则合理分配负载. 负载均衡的⼀些实现 就像是eureka中对请求进行轮询的…

Java对象创建过程的解析

Java对象创建过程的解析 1. 类的加载与连接2. 内存分配2.1 分配方式2.2 本地线程缓冲分配&#xff08;TLAB&#xff09; 3. 初始化内存4. 设置对象头 &#x1f496;The Begin&#x1f496;点点关注&#xff0c;收藏不迷路&#x1f496; 对象的创建是一个涉及多个步骤的复杂过程…