stm32之ADC应用实例(单通道、多通道、基于DMA)-转载精华帖,最后一部分的代码是精华
硬件:STM32F103VCT6
开发工具:Keil uVision4
下载调试工具:ARM仿真器
网上资料很多,这里做一个详细的整合。(也不是很详细,但很通俗)。
所用的芯片内嵌3个12位的模拟/数字转换器(ADC),每个ADC共用多达16个外部通道,2个内部通道。
3个:代表ADC1、ADC2、ADC3(下图是芯片固件库的截图)
这里写图片描述
12位:也叫ADC分辨率、采样精度。先来看看二进制的12位可表示0-4095个数,也就是说转换器通过采集转换所得到的最大值是4095,如:“111111111111”=4095,那么我们怎么通过转换器转换出来的值得到实际的电压值呢?如果我们要转换的电压范围是0v-3.3v的话,转换器就会把0v-3.3v平均分成4096份。设转换器所得到的值为x,所求电压值为y。
那么就有:
这里写图片描述
16个外部通道:简单的说就是芯片上有16个引脚是可以接到模拟电压上进行电压值检测的。16个通道不是独立的分配给3个转换器(ADC1、ADC2、ADC3)使用,有些通道是被多个转换器共用的。首先看看16个通道在固件库的宏定义(写代码要看的):
这里写图片描述
到这里大家可能会有疑问,每个通道到底对应哪个引脚呢?下面先给出部分引脚图:
这里写图片描述
16个通道的引脚都在上面的图中,拿其中的一个进行说明:
ADC123_IN10:字母“ADC”不用多说,“123”代表它被3个(ADC1、ADC2、ADC3)转换器共用的引脚,“10”对应刚才那张宏定义图里面的ADC_Channel_10,这样就能找到每个通道对应的引脚了。
2个内部通道:一个是内部温度传感器,一个是内部参考电压。
在某个项目中要用到芯片里面的AD转换器,那么要怎么写应用代码?(以下是代码讲解)
芯片固件的库函数为我们提供了很多封装好的函数,只要运用它提供的函数接口就可以了,宏观上来讲就搞懂两个事情就行了:
初始化(设置用的哪个引脚、单通道、还是多通道同时转换、是否使用DMA等配置)?
怎么让转换器进行一次数据获取?
以下分别讲述三种不同方式(单通道、多通道、基于DMA的多通道采集)的ADC应用实例:
/*单通道的ADC采集*/
void Adc_Config(void)
{
/*定义两个初始化要用的结构体,下面给每个结构体成员赋值*/
ADC_InitTypeDef ADC_InitStructure;
GPIO_InitTypeDef GPIO_InitStructure;
/*
使能GPIOA和ADC1通道时钟
注意:除了RCC_APB2PeriphClockCmd还有RCC_APB1PeriphClockCmd,那么该如何选择?
APB2:高速时钟,最高72MHz,主要负责AD输入,I/O,串口1,高级定时器TIM
APB1:低速时钟,最高36MHz,主要负责DA输出,串口2、3、4、5,普通定时器TIM,USB,IIC,CAN,SPI
*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA |RCC_APB2Periph_ADC1, ENABLE );
RCC_ADCCLKConfig(RCC_PCLK2_Div6); //72M/6=12, ADC的采样时钟最快14MHz
/*配置输入电压所用的PA0引脚*/
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN; //GPIO_Mode_AIN:模拟输入(还有其他什么模式?请看下面的附录图1)
GPIO_Init(GPIOA, &GPIO_InitStructure);
ADC_DeInit(ADC1); //复位,将ADC1相关的寄存器设为默认值
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; //工作模式:ADC1和ADC2独立工作模式 (还有其他什么模式?请看下面的附录图2)
ADC_InitStructure.ADC_ScanConvMode = DISABLE; //数模转换工作:扫描(多通道)模式=ENABLE、单次(单通道)模式=DISABLE
ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;//数模转换工作:连续=ENABLE、单次=DISABLE
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; //ADC转换由软件触发启动 (还有其他什么模式?请看下面的附录图3)
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; //ADC数据右对齐 除了右就是左:ADC_DataAlign_Left
ADC_InitStructure.ADC_NbrOfChannel = 1; //顺序进行规则转换的ADC通道的数目 范围是1-16
ADC_Init(ADC1, &ADC_InitStructure); //根据ADC_InitStruct中指定的参数初始化外设ADC1的寄存器
/*为啥要设置下面这一步?
细心的你可以发现上面初始化了一个引脚通道,初始化了一个ADC转换器,但ADC转换器并不知道你用的是哪个引脚吧?
这一步目的是:设置指定ADC的规则组通道(引脚),设置它们的转化顺序和采样时间
函数原型:void ADC_RegularChannelConfig(ADC_TypeDef* ADCx, u8 ADC_Channel, u8 Rank, u8 ADC_SampleTime)
参数1 ADCx:x可以是1或者2来选择ADC外设ADC1或ADC2
参数2 ADC_Channel:被设置的ADC通道 范围ADC_Channel_0~ADC_Channel_17
参数3 Rank:规则组采样顺序。取值范围1到16。
ADC_SampleTime:指定ADC通道的采样时间值 (取值范围?请看下面的附录图4)
*/
ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_239Cycles5 );
ADC_Cmd(ADC1, ENABLE); //使能指定的ADC 注意:函数ADC_Cmd只能在其他ADC设置函数之后被调用
/*下面4步按流程走,走完就行*/
ADC_ResetCalibration(ADC1); //重置指定的ADC的校准寄存器
while(ADC_GetResetCalibrationStatus(ADC1)); //等待上一步操作完成
ADC_StartCalibration(ADC1); //开始指定ADC的校准状态
while(ADC_GetCalibrationStatus(ADC1));//等待上一步操作按成
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
初始化完成之后,在主函数中:
void main(void)
{
float ADC_ConvertedValue;
float ADC_ConvertedValueLocal;
Adc_Config();
while(1)
{
ADC_SoftwareStartConvCmd(ADC1, ENABLE); //启动转换
while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC )); //等待转换完成
ADC_ConvertedValue=ADC_GetConversionValue(ADC1); //获取转换结果*ADC_ConvertedValue*
ADC_ConvertedValueLocal=(float)ADC_ConvertedValue*(3.3/4096); //计算出实际电压值*ADC_ConvertedValueLocal*
//这里适当加上一些延迟
//最好连续转换几次 取平均值 这里就省略写了 点到为止
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
附录图1-GPIO_Mode值:
这里写图片描述
附录图2-ADC_Mode值:
这里写图片描述
附录图3-ADC_ExternalTrigConv值:
这里写图片描述
附录图4-ADC_SampleTime值:
这里写图片描述
对于一些刚接触stm32的人来说,看了上面的代码可能还会有很多疑问。
为什么要使能时钟?时钟到底设置多少才合适?
对于ADC_GetConversionValue(ADC1)这个函数参数并没有指定那个通道,如果多个通道同时使用CAN1转换器转换时怎么获取每个通道的值?
第一个问题,所有的外设都要使能时钟,时钟源分为外部时钟和内部时钟,外部时钟比如接8MHz晶振,内部时钟就在芯片内部集成,时钟源为所有的时序电路提供基本的脉冲信号。时钟源好比是一颗跳动的心脏,它按照一定的频率在跳动,所有的器官(外设)要跟心脏(时钟源)桥接起来才能工作,但不同的外设需要的频率不同,所以在时钟源跟外设之中常常还会有一些分频器或者倍频器,以实现对频率的衰减或增强。还想了解更多专业的解释可以去研究stm32的时钟树图。
第二个问题,回答这个问题那么就等于开始介绍多通道转换怎么实现了,看下图
这里写图片描述
由图理解,一个ADC转换器只能选择转换一个通道,那么对比单通道我们只需做一下改变(以双通道为例):
1.在void Adc_Config(void)函数里面添加:
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
GPIO_Init(GPIOA, &GPIO_InitStructure);
1
2
3
配置多一个IO(PA1)口, 也就是通道1。
2.在void Adc_Config(void)函数里面添加:
ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_239Cycles5 );
1
2
先不指定ADC转换通道。
3.在主函数循环里改为:
while(1)
{
/*先采集通道1数据*/
ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_239Cycles5 );
ADC_SoftwareStartConvCmd(ADC1, ENABLE);
while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC ));
ADC_ConvertedValue=ADC_GetConversionValue(ADC1);
ADC_ConvertedValueLocal=(float)ADC_ConvertedValue*(3.3/4096);
/*再采集通道2数据*/
ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 1, ADC_SampleTime_239Cycles5 );
ADC_SoftwareStartConvCmd(ADC1, ENABLE);
while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC ));
ADC_ConvertedValue=ADC_GetConversionValue(ADC1);
ADC_ConvertedValueLocal=(float)ADC_ConvertedValue*(3.3/4096);
//加入适当延时
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
完成以上三步就能把单通道扩展到双通道(或者更多个通道)。不过还有一种基于DMA的多通道转换更加合适。
首先简单介绍DMA,DMA(Direct Memory Access,直接内存存取) ,用来提供在外设和存储器之间或者存储器和存储器之间的高速数据传输。无需CPU干预,节省CPU资源;ADC转换出来的值直接赋值给定义好的变量中。配置好的DMA可以不停地将ADC转换值写到该变量中,在主函数直接判断该变量就知道此时的AD值,也就是说在主函数中不需要调用ADC_GetConversionValue()函数来获取转换值。
DMA跟其他外设一样需要进行配置通道,使能时钟等参数。
下面直接看代码分析:
/*基于DMA的ADC多通道采集*/
violate uint32 ADCConvertedValue[10][3];//用来存放ADC转换结果,也是DMA的目标地址,3通道,每通道采集10次后面取平均数
void DMA_Init(void)
{
DMA_InitTypeDef DMA_InitStructure;
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);//使能时钟
DMA_DeInit(DMA1_Channel1); //将通道一寄存器设为默认值
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&(ADC1->DR);//该参数用以定义DMA外设基地址
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)&ADCConvertedValue;//该参数用以定义DMA内存基地址(转换结果保存的地址)
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;//该参数规定了外设是作为数据传输的目的地还是来源,此处是作为来源
DMA_InitStructure.DMA_BufferSize = 3*10;//定义指定DMA通道的DMA缓存的大小,单位为数据单位。这里也就是ADCConvertedValue的大小
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//设定外设地址寄存器递增与否,此处设为不变 Disable
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;//用来设定内存地址寄存器递增与否,此处设为递增,Enable
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;//数据宽度为16位
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;//数据宽度为16位
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; //工作在循环缓存模式
DMA_InitStructure.DMA_Priority = DMA_Priority_High;//DMA通道拥有高优先级 分别4个等级 低、中、高、非常高
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;//使能DMA通道的内存到内存传输
DMA_Init(DMA1_Channel1, &DMA_InitStructure);//根据DMA_InitStruct中指定的参数初始化DMA的通道
DMA_Cmd(DMA1_Channel1, ENABLE);//启动DMA通道一
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
下面是ADC的初始化,可以将它与上面的对比一下有啥不同,重复的就不解析了
void Adc_Init(void)
{
ADC_InitTypeDef ADC_InitStructure;
GPIO_InitTypeDef GPIO_InitStructure;
/*3个IO口的配置(PA0、PA1、PA2)*/
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
GPIO_Init(GPIOA, &GPIO_InitStructure);
/*IO和ADC使能时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1|RCC_APB2Periph_GPIOA,ENABLE);
RCC_ADCCLKConfig(RCC_PCLK2_Div6);
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
ADC_InitStructure.ADC_ScanConvMode = ENABLE;
ADC_InitStructure.ADC_ContinuousConvMode = ENABLE; //连续转换
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
ADC_InitStructure.ADC_NbrOfChannel = 3;
ADC_Init(ADC1, &ADC_InitStructure);
ADC_RegularChannelConfig(ADC1,ADC_Channel_0,1,ADC_SampleTime_239Cycles5);//通道一转换结果保存到ADCConvertedValue[0~10][0]
ADC_RegularChannelConfig(ADC1,ADC_Channel_1,2,ADC_SampleTime_239Cycles5););//通道二转换结果保存到ADCConvertedValue[0~10][1]
ADC_RegularChannelConfig(ADC1,ADC_Channel_2,3,ADC_SampleTime_239Cycles5); );//通道三转换结果保存到ADCConvertedValue[0~10][2]
ADC_DMACmd(ADC1, ENABLE);//开启ADC的DMA支持
ADC_Cmd(ADC1, ENABLE);
ADC_ResetCalibration(ADC1);
while(ADC_GetResetCalibrationStatus(ADC1));
ADC_StartCalibration(ADC1);
while(ADC_GetCalibrationStatus(ADC1));
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
做完这两步,ADCConvertedValue数组的值就会随输入的模拟电压改变而改变,在主函数中最好取多几次的平均值,再通过公式换算成电压单位。下面是主函数:
int main(void)
{
int sum;
u8 i,j;
float ADC_Value[3];//用来保存经过转换得到的电压值
ADC_Init();
DMA_Init();
ADC_SoftwareStartConvCmd(ADC1, ENABLE);//开始采集
while(1)
{
for(i=0;i<3;i<++)
{
sum=0;
for(j=0;j<10;j++)
{
sum+=ADCConvertedValue[j][i];
}
ADC_Value[i]=(float)sum/(10*4096)*3.3;//求平均值并转换成电压值
//打印(略)
}
//延时(略)
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
ADCConvertedValue的定义用了violate修饰词,因为这样可以保证每次的读取都是从绝对地址读出来的值,不会因为被会编译器进行优化导致读取到的值不是实时的AD值。
最后提醒一下,接线测试的时候记得接上基准电压,就是VREF+和VREF-这两个引脚。如果不想外接线测试就将内部通道的电压读出来,这样就不用配置IO口了。
---------------------
作者:蜗牛kkk
来源:CSDN
原文:https://blog.csdn.net/weixin_42653531/article/details/81123770
版权声明:本文为博主原创文章,转载请附上博文链接!
stm32之ADC应用实例(单通道、多通道、基于DMA)-转载精华帖,最后一部分的代码是精华的更多相关文章
- stm32之ADC应用实例(单通道、多通道、基于DMA)
文本仅做记录.. 硬件:STM32F103VCT6 开发工具:Keil uVision4 下载调试工具:ARM仿真器 网上资料很多,这里做一个详细的整合.(也不是很详细,但很通俗). 所用的芯片内嵌 ...
- STM32 之ADC单次转换模式和连续转换模式
一.背景 在STM32中的AD的单通道采样中可以设置成单次转换模式和连续转换模式,如何理解这两个转换模式的区别,通过程序又是怎样实现的? 二.正文 首先理解单次转换模式,即ADC进行单次转换(单样本) ...
- STM32之ADC实例(基于DMA方式)
版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.csdn.net/zouleideboke/article/details/75112224 ADC简介: ADC(An ...
- 案例 stm32单片机,adc的双通道+dma 内部温度
可以这样理解 先配置adc :有几个通道就配置几个通道. 然后配置dma,dma是针对adc的,而不是针对通道的. 一开始我以为一个adc通道对应一个dma通道.(这里是错的,其实是我想复杂了) 一个 ...
- STM32使用HAL库实现ADC单通道转换
STM32的ADC转换还是很强大的,它具有多个通道选择,这里我就不细说,不了解的可以自行百度,这里只是选取单通道,实现ADC转换.在文章开始之前,我说一下数据左对齐跟右对齐的差别,以前一直糊里糊涂的, ...
- STM32 ADC多通道转换DMA模式与非DMA模式两种方法(HAL库)
一.非DMA模式(转) 说明:这个是自己刚做的时候百度出来的,不是我自己做出来的,因为感觉有用就保存下来做学习用,原文链接:https://blog.csdn.net/qq_24815615/arti ...
- 多通道(Multichannel)单通道(singlechannel)图像概念梳理
在做机器视觉时,常常要将一个多通道图像分离成几个单通道图像或者将几个单通道图像合成一个多通道图像,以方便图像处理,但是.写这篇博客,是为加深对这两个概念的理解,下面会给出部分OpenCV对单通道与多通 ...
- 【stm32】ADC的规则通道和注入通道混合使用
之前完成了规则通道DMA的数据传输了,不过平时在使用ADC的时候可能就会遇到很多情况,不可能就这样简单的按规则通道来采样,DMA存储,使用数据的:可能有时候会需要立刻采样,那样我们就需要利用到注入通道 ...
- stm32CubeMx 实现单通道ADC DMA采集
今天要做的是ADC单通道DMA采集实验 MCU : STM32F429 开发工具:STM32CubeMx 版本号 5.0.0 实验目的:实现ADC1 13通道 DMA采集 一 :简介 首先,我们来看一 ...
随机推荐
- OO_Unit2 关于性能优化与测试的那些事
OO_Unit2 关于性能优化与测试的那些事 OO的第2单元到本周也就正式完结了.尽管这个单元的主旋律是多线程,但"面向对象"的基本思想仍然是我们一切架构与优化的出发点与前提.因此 ...
- Android-SQLite的介绍 以及四个基本操作~
在Android 开发中SQLite起着很重要的作用,网上SQLite的教程有很多很多,不过那些教程大多数都讲得不是很全面.本人总结了一些SQLite的常用的方法,借着论坛的大赛,跟大家分享分享的.一 ...
- 如何优雅地学习计算机2<-->Helloworld
0.导入 在进行粗略的学习计算机底层知识和变量后,我们来开始编写年轻人的第一个程序--Helloworld. 我们需要用到的工具有:1.Dev-C++(也可以使用其他软件)2.脑子(最重要) ...
- 消息中间件-ActiveMQ支持的消息协议
package com.study.mq.a1_example.helloworld.queue; import org.apache.activemq.ActiveMQConnectionFacto ...
- Kubernetes 学习笔记-- kafka往couchdb里倒东西
首先吐槽下国内这些论坛的技术精神,不是我崇洋媚外,有些复读机烦不烦啊,别人的东西吃进去吐出来好玩么? 还有一些不懂装懂,这种最可恶,明明自己都不明白自己在写什么,还是往精华区发,简直离谱,知道自己多挣 ...
- 关于Green AI
上一篇文章提到了模型不环保这个话题.这篇文章就这个问题展开唠叨一下. 自从BERT, GPT此类的大型模型诞生以来,小作坊们除了把pre-trained的模型拿过来微调一下,就束手无策了,因为成本实在 ...
- Dynamics CRM实体系列之图表
本节开始讲解Dynamics CRM的图表功能.任何产品基本上都会有数据分析的工具,Dynamics CRM当然也不例外,作为一个专门做销售管理的软件数据分析自然也是对于销售管理者的决策有很大的作用的 ...
- HUAWEI防火墙双出口根据链路优先级主备备份
组网图形 组网需求 通过配置根据链路优先级主备备份,FW可以在主接口链路故障时,使用备份接口链路转发流量,提高传输的可靠性. 如图1所示,企业从ISP1租用2条链路,带宽均为50M,从ISP2租用1条 ...
- go gin框架和springboot框架WEB接口性能对比
1 简要概述 最近看起go lang,真的被go的goroutine(协程)惊艳到了,一句 go function(){#todo},即可完成一个并发的工作. 看到gin这个web框架时,突然就特别想 ...
- 【Springboot】Springboot自动装配原理
1.核心注解就是 EnableAutoConfiguration 该注解会激活SpringBoot的自动装配功能: 代码如下: @Target(ElementType.TYPE) @Retentio ...