STM32F4系列探究1——三重ADC扫描连续采样+DMA双缓冲区存储
文章目录
- 前言
- 一、STM32F4的ADC理论知识学习
- 1.基础知识
- 2.扫描模式与连续模式
- 3.单ADC模式下双缓存机制的实现
- 4.三重同步采样模式
- 二、由定时器触发的单缓冲区模式
- 1.时钟树配置
- 2.ADC部分配置
- 3.定时器部分配置
- 4.中断配置总结
- 5.main.c修改
- 三、连续模式下的双缓冲区模式
- 1.Cube设置修改
- 2.main.c修改
前言
本文的主要目的是通过学习官方文档,掌握使用STM32CubeMx实现STM32F446ZET6芯片的多重ADC采样与DMA存储的功能,STM32F4系列在使用Cube时均可以参考本文章
一、STM32F4的ADC理论知识学习
1.基础知识
STM32F446系列有3个ADC,即ADC1,ADC2和ADC3,其中ADC1有19个通道,ADC2和ADC3有16个通道。需要注意的ADC的主要参数如下所示:
- 12-bit,10-bit,8-bit或者6-bit采样
- 拥有扫描模式,连续模式
- 有单重模式,双重模式和三重模式
- 在双重/三重模式中也可开启DMA数据存储
- 可以使用VREF+提供外置的精确参考电压供ADC采样
- 在转换结束或者采样值超出设定阈值时产生中断
- VREF-必须接到VSSA
下图是关于ADC所使用到的各引脚的电平要求,在设计电路时应注意要对模拟电源电压和数字电源电压进行隔离,对模拟地和数字地进行隔离。一般推荐使用外部芯片产生的高精度参考电压作为VREF+确保ADC采样精度。
三个ADC的每个通道都对应芯片上相应的IO引脚,具体可以参考芯片的datasheet,这里不再赘述。需要注意的是,ADC1的17,18通道分别接到了内部基准源和VBAT/Vsense上,没有接到IO引脚上,其中18通道意味着VBAT/4和温度传感器只能选择一个进行测量。至于内部基准源Vrefint,其有助于提高ADC的测量准确度。该内部基准源的出厂测量值存储在内存的一个地址上,读取后即可作为参考电压来校准ADC的采样值。该地址在手册中有写,不同型号芯片的位置可能不同。
STM32F446的最高采样速率为2.4MSPS,而ADC完成一次采样加转换最少需要3+12=15个周期,这意味着ADCCLK的最高频率为36MHz,而ADCCLK由PCLK2分频而来,因此在配置时钟树时要注意相关参数。
2.扫描模式与连续模式
一个ADC有多个通道,在扫描模式下,ADC依次对选好的通道序列进行采样,并在对通道序列整个采样完一次后可以产生一个中断用于处理采集到的数据。扫描模式又可以分为单个转换模式和连续转换模式,连续转换模式下,当通道序列采样完后,又从头开始重新采样。
在连续采样模式下,采样到的数据如何及时处理是一个大难题,一个方法是利用每次系列采样完毕时产生的中断及时将数据进行处理或者转移走,但该方法费时费力,占用CPU资源,会出现时序配合的问题。一般在连续采样模式下,都是使用DMA将数据存储在一个BUFF里,这个BUFF可以存储很多数据,在数据存满时会触发中断,此时应停止采样,并对数据进行处理。
3.单ADC模式下双缓存机制的实现
那么问题来了,如果我需要一直连续的采样,中间不进行暂停,有没有好的办法呢。STM32提供了双缓存机制,也就是当数据存储满BUFF1后触发中断,通过操作使DMA开始将数据存储到BUFF2,此时就可以对BUFF1进行操作了,BUFF2存满后同理切换到BUFF1并开始处理BUFF2的数据。再单ADC模式下,该机制的实现很简单,但是无法通过Cube进行设置,需要手动添加两部分代码,如下所示:
首先在DMA初始化函数中添加如下代码:
if (HAL_DMAEx_MultiBufferStart((&hadc1)->DMA_Handle,(uint32_t)&hadc1.Instance->DR,mem0addr,mem1addr,memsize)!= HAL_OK){printf("\r\n HAL_DMAEx_MultiBufferStart failed!");
}
HAL_ADC_Start_DMA(&hadc1,(uint32_t *)mem0addr, memsize);
__HAL_DMA_ENABLE_IT(&hdma_adc1,DMA_IT_TC); //开启传输完成中断
接着是在DMA中断中修改代码令BUFF切换:
void DMA2_Stream0_IRQHandler(void)
{if(__HAL_DMA_GET_FLAG(&hdma_adc1,DMA_FLAG_TCIF0_4)!=RESET)//DMA传输完成{__HAL_DMA_CLEAR_FLAG(&hdma_adc1,DMA_FLAG_TCIF0_4);//清除DMA传输完成中断标志位if (hdma_adc1.Instance->CR&(1 << 19))//buf0已满,正常处理buf1{DMAFLAG=0;}else //buf1已满,正常处理buf0{DMAFLAG=1;}}
}
只要添加以上两部分代码就可以实现双缓存功能。
在一些简单的功能实现中,并不需要使用连续模式,而是使用定时器以特定的频率进行扫描采样。这时候需要对ADC的触发源进行配置,来确定在什么情况下触发一次ADC采样。此时可以利用ADC采样完毕后的中断进行数据的处理,该方式适用于定时器触发的频率较低时的采样,可以适应绝大部分情况下的采样需求。
4.三重同步采样模式
多重ADC的使用有多种模式,这里只介绍一种模式,三重规则同步模式(triple injected simultaneous mode),即由ADC1控制ADC2和ADC3,三个ADC同时触发一次转换。
在三重ADC模式下,DMA有3种模式来传输数据,这里只介绍最常用的模式,注意该模式在双重ADC模式下不可使用:
先传输ADC1的一个半字(half-word),再传输ADC2的一个半字,接着传输ADC3的一个半字,接着传输ADC1的一个半字,如此循环往复。
再三重规则同步模式下,需要注意三个ADC的采样通道序列的长度应是相等的,可以按照下图中的序列进行采样。再每个Conversion后,都会产生三个DMA传输请求,依次传输ADC1,ADC2,ADC3的数据。请注意,这和双重模式下的DMA是不同的,具体请参考手册。
二、由定时器触发的单缓冲区模式
首先实现一个简单的但能满足大多数情况的模式,即由定时器触发ADC进行一定频率的采样,在DMA的目标缓冲区填满后,触发中断进行数据的处理。需要注意的是,如果打开DMA Continuous Requesets,新采样的数据会覆盖原来的缓冲区,因此要及时对数据进行处理,注意时序问题。
1.时钟树配置
首先要关注系统时钟,也就是SYSCLK。这里为了保证时钟的精确性,我使用了25MHz的外接晶振HSE作为时钟源,经过简单的数学计算配置SYSCLK为120MHz,配置PCLK2为60MHz,这意味着ADCCLK的最高频率为30MHz。具体时钟树配置如下所示:
2.ADC部分配置
通过阅读手册可以知道,三重ADC同步采样模式下,由ADC1进行整体的管理与触发。因此,关于多重ADC模式的设置位于ADC1的配置界面中。分别使能ADC1,ADC2,ADC3,根据我自己的需求,每个ADC的规则序列有4个通道,ADC1的设置界面如下所示:
需要注意的是,当不使能DMA Continuous Requests时,DMA在填满缓冲区一次后不会再进行数据的传输,因此一般情况下都应该打开该选项。在ADC1的DMA界面添加DMA,如下图所示,模式设置为Circular:
对于ADC2和ADC3的设置则较为简单:
同样要注意打开DMA Continuous Requests,并分别在ADC2与ADC3的DMA界面添加相应的DMA,并将模式设置为Circular。
3.定时器部分配置
从ADC1的配置中可以发现,ADC使用了TIM2的Trigger Out event进行触发,因此要对TIM2进行相应的配置。在STM32F4系列中,TIM2的时钟源为PCLK1。由时钟树可知,其时钟频率为60MHz,对于定时器的触发时间,有如下公式:
发生中断时间=(Prescaler+1)* (Period+1)/PCLK1
若设置ADC的采样频率为10KHz ,则有Prescaler=59,Period=99,具体配置如下:
4.中断配置总结
ADC1,ADC2,ADC3的DMA中断都是默认开启的,不需要修改。定时器中断与其他外设的中断可以根据自己的需求进行开启,需要注意的是,如果使用了SDIO,则在SD卡读写时应当关闭所有中断。
5.main.c修改
在初始化函数后,添加如下代码即可启动ADC与定时器,需要注意定时器应在ADC启动后再启动,如果想要触发定时器中断,则应使用函数HAL_TIM_Base_Start_IT(&htim2),具体代码如下:
if(HAL_ADC_Start(&hadc3)!=HAL_OK){Error_Handler();}if(HAL_ADC_Start(&hadc2)!=HAL_OK){Error_Handler();}if(HAL_ADCEx_MultiModeStart_DMA(&hadc1,(uint32_t*)ADC_Result,48)!=HAL_OK){Error_Handler();}HAL_TIM_Base_Start(&htim2);
其中ADC_Result即为DMA传输的目标缓冲区,其数据格式为uint16_t,其大小可以自定,但应当为一次规则序列采样的数据数目的整数倍。
再main.c中实现DMA回调函数,即可进行数据处理,一般推荐在回调函数中将标志位置1,在while循环中进行相应的操作,并将标志置0。回调函数示例代码如下所示:
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
{DMAFLAG++;
}
void HAL_ADC_ConvHalfCpltCallback(ADC_HandleTypeDef* hadc)
{DMA_HALF_FLAG++;
}
上述两个函数分别时DMA缓冲区全满与缓冲区半满的回调函数,打开位于stm32f4xx_hal_dma.c的HAL_DMA_RegisterCallback(DMA_HandleTypeDef hdma, HAL_DMA_CallbackIDTypeDef CallbackID, void ( pCallback)(DMA_HandleTypeDef *_hdma))可以知道,每个DMA通道都有很多个回调函数,分别在缓冲区全满,缓冲区半满,双缓存情况下的缓冲区1(另一个是缓冲区0)全满,双缓存情况下的缓冲区1半满等中断下触发相应回调。在本例中,Cube为ADC注册了DMA缓冲区全满,半满以及错误的回调函数,只需要在main.c中对相关回调进行重写即可在触发相应中断时执行自己想要进行的操作。
实际上,有了半满中断后,双缓存的意义便不太大了。在一些特殊情况下,比如需要在采样过程中修改缓冲区的地址,则需要利用DMA的双缓存机制的相关函数,因此下一节对双缓存机制进行探究。
三、连续模式下的双缓冲区模式
1.Cube设置修改
只需要在ADC1,ADC2与ADC3的设置中关闭定时器触发的设置并打开连续模式即可,如下所示:
此时定时器TIM2可以选择关闭。
2.main.c修改
需要注意的是,STM32的双缓存机制的设置函数理论上为HAL_DMAEx_MultiBufferStart_IT(),但是使用该函数无法进行缓冲区的切换,可能是官方文档存在一定BUG。为此,我摸索出可以实现相关机制的配置代码如下:
void HAL_ADC_MutiM1ConvCpltCallback(DMA_HandleTypeDef *_hdma);HAL_DMA_RegisterCallback((&hadc1)>DMA_Handle,HAL_DMA_XFER_M1CPLT_CB_ID,HAL_ADC_MutiM1ConvCpltCallback);
if (HAL_DMAEx_MultiBufferStart((&hadc1)->DMA_Handle,(uint32_t)&hadc1.Instance->DR,(uint32_t)buf0,(uint32_t)buf1,AD_BufferSize)!= HAL_OK){printf("\r\n HAL_DMAEx_MultiBufferStart failed!");
}
if(HAL_ADC_Start(&hadc3)!=HAL_OK){Error_Handler();
}
if(HAL_ADC_Start(&hadc2)!=HAL_OK){Error_Handler();
}
if(HAL_ADCEx_MultiModeStart_DMA(&hadc1,(uint32_t*)buf0,AD_BufferSize)!=HAL_OK){Error_Handler();
}
__HAL_DMA_ENABLE_IT(&hdma_adc1,DMA_IT_TC);
__HAL_DMA_DISABLE_IT(&hdma_adc1,DMA_IT_HT);
在上述代码中,首先利用RegisterCallback函数注册dma的缓冲区2全满回调函数,这样在缓存2填满后会触发回调函数进入用户定义的HAL_ADC_MutiM1ConvCpltCallback()中。同时使用非中断模式启动DMA的双缓存存储,在ADC启动后打开缓冲区全满中断,即DMA_IT_TC标志,可以根据需要选择是否打开缓冲区半满中断,即DMA_IT_HT标志。要注意的是,如果要打开缓冲区半满中断,应当给缓冲区2添加一个缓冲区半满回调函数,只需要再注册函数的调用中将ID从HAL_DMA_XFER_M1CPLT_CB_ID改为HAL_DMA_XFER_M1HALFCPLT_CB_ID,同时再自行定义一个回调函数即可。
双缓存机制的意义在于可以改变缓冲区的目标地址,需要使用
HAL_DMAEx_ChangeMemory()函数,打开stm32f4xx_hal_dma_ex.c文件即可查看该函数的定义和调用方法。
STM32F4系列探究1——三重ADC扫描连续采样+DMA双缓冲区存储相关推荐
- HAL库学习笔记ADC篇----ADC多通道连续转换+DMA方式
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言 一.Cube配置(F407VET6) 二.使用步骤 1. 代码总览 2.代码注意事项以及解析 总结 前言 第一次写,无 ...
- 如何使用S32K1的PDB模块触发多个ADC通道连续采样
文章目录 1. 外设模块介绍 1.1 PDB模块简介 1.2 ADC通道 1.3 ADC触发源 1.4 PDB触发多个ADC通道的两种方式 2. 例程 2.1 例程功能介绍 2.2 例程编写 2.2. ...
- stm32 adc 连续和扫描_技术分享 | STM32多个ADC模块同时采样转换的应用示例
在STM32家族里,多数系列芯片内含2到3个ADC模块,有的甚至更多,比方G4系列可以有5个ADC模块.其中,通道数因不同的系列或型号多少不等,几个到几十个的都有.有时,我们可能需要多个ADC模块同时 ...
- 基于DMA通道的连续ADC扫描读取
ADC ADC即是模数转换器,将电压信号转换为数字信号. 以stm32f407为例,其拥有18个12位ADC转换通道,其中16个外部通道以及俩个内部通道. ADC有单次,连续,扫描或间断模式执行转换, ...
- stm32F4的ADC+DMA+Timer,实现2MHz连续采样。1LSB分辨率,极低噪声。
1. ADC+DMA+Timer的实现原理 stm32F407/405的ADC为12位逐次逼近型ADC,有着高达2.4MHz的采样率,分辨率 1LSB.这样参数的ADC放在市面上单卖,也起码是¥10+ ...
- STM32F4系列ADC最大转换速率及操作条件(以STM32F407ZGT6为例)
STM32F4系列ADC最大转换速率及操作条件(以STM32F407ZGT6为例) 前言 一.如何获取数据手册? 二.STM32F4系列ADC相关基础 ADC时钟 ADC采样时间 ADC分辨率 三.一 ...
- STM8S系列基于IAR开发单通道ADC连续采样示例
STM8S系列基于IAR开发单通道ADC连续采样示例
- STM32F4时钟触发ADC双通道采样DMA传输进行FFT+测频率+采样频率可变+显示波形(详细解读)...
此文转载自:https://blog.csdn.net/qq_45620831/article/details/110819495 写在前面的婆婆妈妈的话 本人大三,参加过数次电赛,来CSDN好久, ...
- STM32F4时钟触发ADC双通道采样DMA传输进行FFT+测频率+采样频率可变+显示波形(详细解读)
写在前面的婆婆妈妈的话(代码链接在最下方) 本人大三,参加过数次电赛,来CSDN好久, 每次都是在绝望中从这里找到了希望,每次都仿佛一个即将被怪兽打翻的小船突然被危险流浪者救起来.是众多前辈的智慧,让 ...
最新文章
- 【Codeforces】1015B Obtaining the String(字符串 交换)
- linux升级补丁tar,Linux内核升级补丁安装手册(一)
- HighNewTech:LL / GCP BOOTH at CES 2019 - January 8-11, 2019 - Westgate Convention Center Las Vegas
- 科大星云诗社动态20210306
- 按钮不通过表单连接servlet_JavaWeb之Servlet(一)
- YBTOJ洛谷P1407:稳定婚姻(强连通分量)
- 服务器e系列和l的区别,i.e.和 e.g.的区别和使用方法
- 单体预聚合的目的是什么_高分子化学实验指导书-修改-2012
- 《Python Cookbook 3rd》笔记(2.1):使用多个界定符分割字符串
- php里运行js,在PHP 中运行JS - mickelfeng的个人空间 - OSCHINA - 中文开源技术交流社区...
- 二十、SAP中定义内表
- 8-18 高可用读写分离
- 抽象、多样性与可变性
- 单总线led驱动芯片WS2811在linux下的驱动
- Java Web程序设计——JSP技术(一)
- 终极算法——第一章:机器学习的革命
- Sprite Renderer
- StatusBarUtil 状态栏工具类(实现沉浸式状态栏/变色状态栏)
- 管好自己,但行好事,不渡他人
- Minecraft我的世界服务器配置记录
热门文章
- html在线表单生成,一种基于html5的在线表单设计系统的制作方法
- Kuick:创业大军中脱颖而出的少数派
- python进行Word解析
- dnf打团正在连接服务器进不去是吗鬼,DNF打团速成职业注意事项解读 不再做手残辅助...
- 计算机毕业设计Java重庆旅游景点(源码+系统+mysql数据库+Lw文档)
- 三情과 生活과의 關係
- c语言求三个数最值非函数,C语言编程:从键盘任意输入三个数,编写求其最大值、最小值的函数,用指针作函数参数实现。...
- 【SSM-报销单】6.报销单-审核报销单,打款
- 通过Opencv打开指定摄像头的方法
- 后端获取接口数据属性为中文JSON取值(key是中文或者数字)处理方法