✅作者简介:嵌入式入坑者,与大家一起加油,希望文章能够帮助各位!!!!
📃个人主页:@rivencode的个人主页
🔥系列专栏:玩转STM32
💬保持学习、保持热爱、认真分享、一起进步!!
一.ADC简介
Analog-to-Digital Converter的缩写。指模/数转换器或者模拟/数字转换器。是指将连续变量的模拟信号转换为离散的数字信号的器件。
12位ADC是一种逐次逼近型模拟数字转换器。它有多达18个通道,可测量16个外部和2个内部信号源。各通道的A/D转换可以单次、连续、扫描或间断模式执行。ADC的结果可以左对齐或右对齐方式存储在16位数据寄存器中。模拟看门狗特性允许应用程序检测输入电压是否超出用户定义的高/低阀值。
ADC的输入时钟不得超过14MHz,它是由PCLK2经分频产生。
2.GPIO模拟输入
这模式主要为片上外设ADC而配置,从外部读取模拟信号
- 模拟信号:测试信号未经过采样前,均是时间和幅值均是连续的信号称为模拟信号,例如连续变化的电压,电流,温度等等。
- 数字信号:模拟信号经等间隔“采样”及幅值量化以后,时间和幅值均是不连续的(离散)的信号,例如0 /1**
所以前面所谓的外部通道其实就是单片机的GPIO引脚,一般ADC有16个外部通道也就是16个采集模拟的信号的GPIO引脚
将GPIO引脚采集的模拟信号(电压)通过ADC外设的的数字转换器将模拟信号(电压)转换为表示一定比例电压值的数字信号(12位的二进制序列)。
12位ADC是什么意思?
其实ADC外设还有16位精度更高。
举个例子假设你要采集的电压范围是0~3.3V,而电压通过ADC外设的的数字转换器将电压值按比例转化为数字值(12位的二进制序列)
0V 对应 数字量 0
3.3V 对应 数字量 4095 - 2的12次方-1 所以叫做12位ADC
呈线性关系,所以知道模拟量就可以求数字量,知道数字量也可以求模拟量。
如果是16位的ADC外设对应的数字量是2的16次方-1,数字量的范围更大在转换的时候误差会更小
ADC的主要特性
● 12位分辨率- 12位ADC
● 转换结束、注入转换结束和发生模拟看门狗事件时产生中断
● 单次和连续转换模式
● 从通道0到通道n的自动扫描模式
● 自校准
● 带内嵌数据一致性的数据对齐
● 采样间隔可以按通道分别编程
● 规则转换和注入转换均有外部触发选项
● 间断模式
● 双重模式(带2个或以上ADC的器件)
● ADC转换时间:
─ STM32F103xx增强型产品:时钟为56MHz时为1μs(时钟为72MHz为1.17μs)
● ADC供电要求:2.4V到3.6V
● ADC输入范围:VREF- ≤ VIN ≤ VREF+
● 规则通道转换期间有DMA请求产生。
先总的看一下看不懂不要紧,上面的特性后面会基本讲到,并且有对应的实验来深刻理解上面的ADC的特性。
二.ADC功能框图(重点)
1.电压输入范围
ADC 输入范围为:VREF- ≤ ADC 输入范围(VIN )≤ VREF+。由 VREF-、VREF+ 、VDDA 、VSSA、这四个外部引脚决定。
一般把 VSSA 和 VREF-接地,把 VREF+和 VDDA 接 3V3,得到ADC 的输入电压范围为:0~3.3V,也就是我们可测量的电压范围
在 64 脚以下的 CPU 中,没有 VREF-和 VREF+这两个引脚,ADC 电压输入范围直接由 VDDA和 VSSA决定。
但是我们要测量其他范围电压怎么办,假如要测量-10V~10V范围的电压,而我们的单片机只能测量0 ~ 3.3V的电压,所以我们可以借助外部的电路将 -10V ~ 10V按一定比例转化为 0 ~ 3.3V的电压,这样单片机就能测量超过3.3V 或者 低于0V的电压了。
原理:基尔霍夫定律:结点流入的电流等于流出电流
0V 就对应 -10V
3.3V 就对应 10V
如何求对应的三个电阻的阻值
所以我们就上面一顿操作,其实就是加了一个转化电路,将输入的电压范围-10 ~10V 按比例转化为单片机可测量的 0 ~ 3.3V,然后测出电压是范围是 0 ~3.3V最后在通过公式将又转化为 -10 ~ 10V 这样就可以测量 -10 ~ 10V的电压了。
2.输入通道
STM32 的 ADC 多达 18 个通道,其中外部的 16 个通道(对应16个GPIO引脚)就是框图中的 ADCx_IN0、ADCx_IN1…ADCx_IN5。。其中 ADC1/2/3 还有内部通道:ADC1 的通道 16 连接到了芯片内部的温度传感器,Vrefint 连接到了通道 17。ADC2 的模拟通道 16 和 17 连接到了内部的 VSS。
ADC3 的模拟通道 9、14、15、16 和 17 连接到了内部的 VSS
上图是STM32F103ZET6(144个引脚)的ADC通道,引脚越少的型号的上图的通道数会减少,因为引脚不够
例如RCT6 ADC3没有通道4~8
有16个多路通道。可以把转换组织成两组:规则组和注入组。
● 规则组由多达16个转换组成。规则通道和它们的转换顺序在ADC_SQRx寄存器中选择。规则组中转换的总数应写入ADC_SQR1寄存器的L[3:0]位中。
● 注入组由多达4个转换组成。注入通道和它们的转换顺序在ADC_JSQR寄存器中选择。注入组里的转换总数目应写入ADC_JSQR寄存器的L[1:0]位中。
如果ADC_SQRx或ADC_JSQR寄存器在转换期间被更改,当前的转换被清除,一个新的启动脉冲将发送到ADC以转换新选择的组。
温度传感器/ VREFINT内部通道
温度传感器和通道ADC1_IN16相连接,内部参照电压VREFINT和ADC1_IN17相连接。可以按注入或规则通道对这两个内部通道进行转换。
注意: 温度传感器和VREFINT只能出现在主ADC1中
规则通道
规则通道:顾名思意,规则通道就是很规矩的意思,我们平时一般使用的就是这个通道
注入通道
如果在规则通道转换过程中,有注入通道触发(外部触发或者软件触发),那么就会打断规则通道的转换,先转换完注入通道,等注入通道转换完成后,再回到规则通道的转换流程。这点跟中断程序很类似,所以,注入通道只有在规则通道存在时才会出现。
清除ADC_CR1寄存器的JAUTO位,并且设置SCAN位,即可使用触发注入功能。
1.利用外部触发或通过设置ADC_CR2寄存器的ADON位,启动一组规则通道的转换。
2.如果在规则通道转换期间产生一外部注入触发,当前转换被复位,注入通道序列被以单次扫描方式进行转换。
3.然后,恢复上次被中断的规则组通道转换。如果在注入转换期间产生一规则事件,注入转换不会被中断,但是规则序列将在注入序列结束后被执行。
注入通道可以打断规则通道,规则通道不能打断注入通道
自动注入
如果设置了JAUTO位,在规则组通道之后,注入组通道被自动转换。这可以用来转换在ADC_SQRx和ADC_JSQR寄存器中设置的多至20个转换序列。
在此模式里,必须禁止注入通道的外部触发。=如果除JAUTO位外还设置了CONT位,规则通道至注入通道的转换序列被连续执行==。
对于ADC时钟预分频系数为4至8时,当从规则转换切换到注入序列或从注入转换切换到规则序列时,会自动插入1个ADC时钟间隔;当ADC时钟预分频系数为2时,则有2个ADC时钟间隔的延迟
上面的意思是在规则通道转换完毕会自动转换注入通道,相当于可以多转换几个通道
通道的转换顺序
规则通道:
一共有18个通道,16个外部通道,2个内部通道。
例如:
1.要让通道7第一个转换直接在 ADC_SQR3寄存器中的SQ1写入0x07
–5位二进制-- 00111
2.要让通道10第15个转换,直接在 ADC_SQR1寄存器中的SQ15入0x0A–5位二进制-- 01010
注入通道:
注入序列寄存器 JSQR 只有一个,最多支持 4 个通道,具体多少个由 JSQR 的 JL[1:0]决定,这里的4个通道在16个通道中可以任意选择4个通道
直接看下图就一目了然了:
3.开启通道的触发源
ADC 转换可以由ADC 控制寄存器 2: ADC_CR2 的 ADON 这个位来控制,写 1 相当于开启ADC转换器的电源,如果要真正开启转换还要一个触发信号,而触发转换的方式有两种一种是软件触发,一种是外部触发。
1.软件触发
第一步:要将控制寄存器2ADC_CR2中的EXTSEL[2:0]或者JEXTSEL[2:0]分别设置规则通道的软件触发和注入通道的软件触发
规则通道EXTSEL[2:0]:
注入通道JEXTSEL[2:0]:
第二步:设置SWSTART为1开始转换规则通道或者设置JSWSTART为1始转换注入通道
总结:首先开启ADC转换器的电源ADC_CR2 的 ADON 写1,第二步EXTSEL[2:0]或者JEXTSEL[2:0]设置为规则或转换通道的软件触发模式,此时在将:设置SWSTART为1开始转换规则通道或者设置JSWSTART为1开始转换注入通道。
上面的代码就是设置SWSTART为1开始规则通道的转换。
2.外部触发
ADC 还支持触发转换,这个触发包括内部定时器触发和外部 IO 触发。触发源有很多,具体选择哪一种触发源:
第一步选择触发源
由 ADC 控制寄存2:ADC_CR2 的EXTSEL[2:0]和 JEXTSEL[2:0]位来控制。EXTSEL[2:0]用于选择规则通道的触发源,JEXTSEL[2:0]用于选择注入通道的触发源
第二步启动外部触发:
选定好触发源之后,触发源是否要激活,则由ADC 控制寄存器 2:ADC_CR2 的 EXTTRIG 和 JEXTTRIG 这两位来激活。
其中 ADC3 的规则转换和注入转换的触发源与 ADC1/2 的有所不同。
、
第三步:等待事件发送触发通道转换
例如:外部事件按键触发,一按按键启动转换,或者定时器事件触发通道转换。
后面会有按键触发规则通道转换的实验
4.通道的转换时间
1)ADC的时钟频率
一般我们设置 PCLK2=HCLK=72MHZ,由于ADC外设是挂载在APB2总线上,而且ADC时钟频率不能超过14MHZ所以我们只能:
PCLK2经过6分频->ADCCLK=72/6=12MHZ
PCLK2经过8分频->ADCCLK=72/8=9MHZ
所以ADC外设的时钟最高只能到12MHZ
2)ADC外设的转换时间
ADC使用若干个ADC_CLK周期对输入电压采样
,采样周期数目可以通过ADC_SMPR1和ADC_SMPR2寄存器中的SMP[2:0]位更改。每个通道可以分别用不同的时间采样。
总转换时间如下计算
TCONV = 采样时间+ 12.5个周期
转换时间=通道的采样时间+固定的12.5个周期
时钟周期=1/时钟频率
例如:
一个时钟周期=1/14HMZ=1/14μs
当ADCCLK=14MHz,采样时间为1.5周期
TCONV = 1.5 + 12.5 = 14个时钟周期 = 14*1/14=1μs
但前面讲了实际上ADC的时钟频率达不到14MHZ最高只能是:
PCLK2经过6分频->ADCCLK=72/6=12MHZ
一个时钟周期=1/14HMZ=1/12μs
一般我们设置 PCLK2=72M,经过 ADC 预分频器能分频到最大的时钟只能是 12M,采样周期设置为 1.5 个周期。
TCONV = 1.5 + 12.5 = 14个时钟周期 = 14*1/12=1.17μs
算出最短的转换时间为 1.17us,这个才是最常用的。
4.单次转换模式与连续转换模式与扫描模式
1)单次转换模式下,ADC只执行一次转换。该模式既可通过设置ADC_CR2寄存器的ADON位(只适用于规则通道)启动也可通过外部触发启动(适用于规则通道或注入通道),这时CONT位为0。
一旦选择通道的转换完成:
● 如果一个规则通道被转换:
─ 转换数据被储存在16位ADC_DR寄存器中
─ EOC(转换结束)标志被设置
─ 如果设置了EOCIE,则产生中断。
● 如果一个注入通道被转换:
─ 转换数据被储存在16位的ADC_DRJ1寄存器中
─ JEOC(注入转换结束)标志被设置
─ 如果设置了JEOCIE位,则产生中断
2)在连续转换模式中,当前面ADC转换一结束马上就启动另一次转换,此模式可通过外部触发启动或通过设置ADC_CR2寄存器上的ADON位启动,此时CONT位是1。
每个转换后:
● 如果一个规则通道被转换:
─ 转换数据被储存在16位的ADC_DR寄存器中
─ EOC(转换结束)标志被设置
─ 如果设置了EOCIE,则产生中断。
● 如果一个注入通道被转换:
─ 转换数据被储存在16位的ADC_DRJ1寄存器中
─ JEOC(注入转换结束)标志被设置
─ 如果设置了JEOCIE位,则产生中断。
3)扫描模式
此模式用来扫描一组模拟通道。
扫描模式可通过设置ADC_CR1寄存器的SCAN位来选择。一旦这个位被设置,ADC扫描所有被ADC_SQRX寄存器(对规则通道)或ADC_JSQR(对注入通道)选中的所有通道。在每个组的每个通道上执行单次转换。在每个转换结束时,同一组的下一个通道被自动转换。
如果设置了CONT位(也就是所谓的连续转化模式),转换不会在选择组的最后一个通道上停止,而是再次从选择组的第一个通道继续转换。
如果设置了DMA位,在每次EOC后,DMA控制器把规则组通道的转换数据传输到SRAM中。而注入通道转换的数据总是存储在ADC_JDRx寄存器中(因为注入通道有4个而注入数据寄存器有4个)
看完上面官方文字可能还不理解接下来就分几种情况:
- 1.当开启单次转换模式与不开启扫描模式(一定是单通道)
上面当如果设置了EOCIE,当转换完成时会产生中断
-
2.当开启连续转换模式与不开启扫描模式(一定是单通道)
-
3.当开启单次转换模式与开启扫描模式(多通道)
由于ADC规则数据寄存器只有一个,所以下一个通道采集的数据会覆盖上一个通道采集的数据,所以多通道采集必须使用DMA传输将数据保存在SRMA一个数组变量中(后面马上会讲)
但是对于注入通道而言,一共有4个通道但ADC 注入数据寄存器有4个不需要担心数据覆盖的问题 -
4.当开启连续转换模式与开启扫描模式(多通道)
与上面一样规则多通道会有数据覆盖需要DMA传输,而注入通道不需要
5.数据寄存器与数据对齐
1)规则数据寄存器
ADC 规则组数据寄存器 ADC_DR 只有一个,是一个 32 位的寄存器,低 16 位在单 ADC时使用
,高 16 位是在 ADC1 中双模式下保存 ADC2 转换的规则数据,双模式就是 ADC1 和ADC2 同时使用,在单模式下,ADC1/2/3 都不使用高 16 位。因为 ADC 的精度是 12 位,无论 ADC_DR 的高 16 或者低 16 位都放不满,只能左对齐或者右对齐,具体是以哪一种方式存放,由 ADC_CR2 的 11 位 ALIGN 设置。
2)注入数据寄存器
ADC 注入组最多有 4 个通道,刚好注入数据寄存器也有 4 个,每个通道对应着自己的寄存器,不会跟规则寄存器那样产生数据覆盖的问题。ADC_JDRx 是 32 位的,低 16 位有效,高 16 位保留,数据同样分为左对齐和右对齐,具体是以哪一种方式存放,由ADC_CR2 的 11 位 ALIGN 设置。
规则通道可以有 16 个这么多,但规则数据寄存器只有一个,如果使用多通道转换,前一个通道采集转化数据,会被下一个通道采集转化数据覆盖掉。
所以当一个通道转换完成后就应该把数据取走,或者开启 DMA 模式,把一个一个通道采集转化的数据传输到内存里面(一般定义一个数组来保存一数组的一个元素就保存一个通道采集转换的数据),不然就会造成数据的覆盖。最常用的做法就是开启 DMA 传输。
6.DMA请求
对DMA 外设不熟的请看---->《DMA外设超详解》
因为规则通道转换的值储存在一个仅有的数据寄存器中,所以当转换多个规则通道时需要使用DMA,这可以避免丢失已经存储在ADC_DR寄存器中的数据。只有在规则通道的转换结束时才产生DMA请求,并将转换的数据从ADC_DR寄存器传输到用户指定的目的地址。
注意: 只有ADC1和ADC3拥有DMA功能。由ADC2转化的数据可以通过双ADC模式,利用ADC1的DMA功能传输。
这里举两个例子
1)独立模式(只有一个ADC外设)单通道(连续转换)
2)独立模式(只有一个ADC外设)多通道(扫描模式加连续转换)
3)同步规则模式(ADC1与ADC2同时转换)多通道(扫描模式加连续转换)
同步规则模式
此模式在规则通道组上执行。
外部触发来自ADC1的规则组多路开关(由ADC1_CR2寄存器的EXTSEL[2:0]选择),它同时给ADC2提供同步触发。
ADC1为主ADC,ADC2为从ADC,ADC1可以是外部触发也可以是软件触发,而ADC2只要开启激活外部触发就可以,ADC2可以通过ADC1的规则组多路开关同步触发。
注意: 不要在2个ADC上转换相同的通道
((两个ADC在同一个通道上的采样时间不能重叠)。
在ADC1或ADC2的一个通道转换结束时:
● 产生一个32位DMA传输请求(如果设置了DMA位),32位的ADC1_DR寄存器内容传输到SRAM中,它上半个字包含ADC2的转换数据,低半个字包含ADC1的转换数据。
● 当所有ADC1/ADC2规则通道都被转换完时,产生EOC中断(若任一ADC接口开放了中断)。
这里是借助ADC1DMA传输功能将数据寄存器(32位高16位是ADC2采集通道的转换数据,低16位是ADC1采集通道的转换数据)中的内容传输到SRAM中定义的一个数组变量中。
校准:
关于校准:了解即可STM32有自动校准的功能我们只需知道如何操作
7.ADC中断
规则和注入组转换结束时能产生中断,当模拟看门狗状态位被设置时也能产生中断。它们都有独立的中断使能位。
注意: ADC1和ADC2的中断映射在同一个中断向量上,而ADC3的中断有自己的中断向量
也就说ADC1与ADC2共用一个中断服务函数,ADC3单独一个中断服务函数
ADC_SR寄存器中有2个其他标志,但是它们没有相关联的中断:
● JSTRT(注入组通道转换的启动)
● STRT(规则组通道转换的启动)
模拟看门狗中断
先设置好要在单一通道使用,还是全部通道
第二设置规则通道或注入通道是否使用看门狗
第三设置好上限和下限(例如:ADC能采集的电压范围是0~3.3V 我们可以涉设置一个上限2.5V设置一个下限1.5V)
当通道的电压超过2.5V或低于1.5V时就会触发模拟看门狗中断,此时我们就可以在中断服务函数里面做出对应操作(例如亮个红灯)
不过这里不是直接写入上限2.5V,它是要写入2.5V模拟量对应的12位的数字量 利用前面的公式应该写入上限电压 2.5/3.3*4095
8.双ADC模式
在双ADC模式里,根据ADC1_CR1寄存器中DUALMOD[2:0]位所选的模式,转换的启动可以是ADC1主和ADC2从的交替触发或同步触发
前面已经DMA请求时已经讲解了规则同步模式,而注入同步模式与规则同步模式类似只不过通道是注入通道
- 同步注入模式 ADC1 和 ADC2 同时转换一个注入通道组,其中 ADC1 为主,ADC2 为从。转换的数据存储在每个 ADC 接口的
ADC_JDRx 寄存器中。 - 同步规则模式 ADC1 和 ADC2 同时转换一个规则通道组,其中 ADC1 为主,ADC2 为从。ADC1 转换的结果放在 ADC1_DR 的低 16 位,ADC2 转换的结果放在 ADC1_DR 的高十六位。
-
快速交叉模式
此模式只适用于规则通道组(通常为一个通道)。外部触发来自ADC1的规则通道多路开关。
外部触发产生后:
● ADC2立即启动并且
● ADC1在延迟7个ADC时钟周期后启动
如果同时设置了ADC1和ADC2的CONT位,所选的两个ADC规则通道将被连续地转换。ADC1产生一个EOC中断后(由EOCIE使能),产生一个32位的DMA传输请求(如果设置了DMA位),ADC1_DR寄存器的32位数据被传输到SRAM,ADC1_DR的上半个字包含ADC2的转换数据,低半个字包含ADC1的转换数据。
注意: 最大允许采样时间<7个ADCCLK周期,避免ADC1和ADC2转换相同通道时发生两个采样周期的重叠
其实这种模式与独立ADC模式相比就是采样通道的速度加快,因为在ADC2采样通道0转换未完成时,ADC1又对通道0进行采样,相当于大大加快了采样通道的速率
慢速交叉模式只是ADC1在延迟14个ADC时钟周期后启动,其他都一样,只是采样的速率降低了而已但是比独立模式(单个ADC)还是要快的多
-
交替触发模式
此模式只适用于注入通道组。外部触发源来自ADC1的注入通道多路开关。
● 当第一个触发产生时,ADC1上的所有注入组通道被转换。
● 当第二个触发到达时,ADC2上的所有注入组通道被转换。
● 如此循环……
如果允许产生JEOC中断,在所有ADC1注入组通道转换后产生一个JEOC中断。
如果允许产生JEOC中断,在所有ADC2注入组通道转换后产生一个JEOC中断。
这些模式最常用的还是独立模式,同步规则模式(像示波器一样同时采样两个通道的模拟信号),还是快速交叉模式(能加快采样速率),这些仔细看看手册还是不难理解。
ADC初始化结构体:
成员变量前面已经全部讲过不在赘言
三.独立模式单通道采集(中断读取)实验
实验目的
单通道循环采集 AD 转换完成中断,在中断服务函数中读取数据,在将读取到数字量通过公式转化电压值,然后通过串口打印到屏幕上
实验原理
开启ADC1通道11——》引脚PC1通过一个滑动变阻器连接到3.3V的电压,所以转动滑动变阻器阻值发送变化,则PC1引脚的分压发生改变。
电压转换
模拟电压经过 ADC 转换后,是一个 12 位的数字值,如果通过串口以 16 进制打印出来的话,可读性比较差,那么有时候我们就需要把数字电压转换成模拟电压,也可以跟实际的模拟电压(用万用表测)对比,看看转换是否准确。
ADC 的输入电压范围设定在:0~3.3v,因为 ADC是 12 位的,那么 12 位满量程对应的就是 3.3V,12 位满量程(FFF)对应的数字值是:2^12-1=4095。数值0 对应的就是 0V。如果转换后的数值为 Y,Y对应的模拟电压为 X,那么会有这么一个等式成立: Y =4095/3.3*X
上代码:
adc.h
#ifndef __ADC_H
#define __ADC_H
#include "stm32f10x.h"
// ADC编号选择
// 可以是ADC1/2,如果使用ADC3,中断相关的要改成ADC3的
#define ADC_APBxClock_FUN RCC_APB2PeriphClockCmd
#define ADC_x ADC1
#define ADC_CLK RCC_APB2Periph_ADC1
// ADC GPIO宏定义
// 注意:用作ADC采集的IO必须没有复用,否则采集电压会有影响
#define ADC_GPIO_APBxClock_FUN RCC_APB2PeriphClockCmd
#define ADC_GPIO_CLK RCC_APB2Periph_GPIOC
#define ADC_PORT GPIOC
#define ADC_PIN GPIO_Pin_1
// ADC 通道宏定义
#define ADC_CHANNEL ADC_Channel_11
// ADC 中断相关宏定义
#define ADC_IRQ ADC1_2_IRQn
#define ADC_IRQHandler ADC1_2_IRQHandler
void ADCx_Init(void);
#endif /*__ADC_H*/
adc.c
#include "adc.h"
static void ADC_NVIC_Config(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitStructure.NVIC_IRQChannel =ADC_IRQ;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority =1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority =1;
NVIC_InitStructure.NVIC_IRQChannelCmd =ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
static void ADC_GPIO_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
ADC_GPIO_APBxClock_FUN(ADC_GPIO_CLK,ENABLE);
//GPIO模式:模拟输入
GPIO_InitStructure.GPIO_Mode =GPIO_Mode_AIN;
GPIO_InitStructure.GPIO_Pin =ADC_PIN;
GPIO_Init(ADC_PORT, &GPIO_InitStructure);
}
static void ADCx_Mode_Config(void)
{
ADC_InitTypeDef ADC_InitStructure;
//开启ADC1的时钟
ADC_APBxClock_FUN(RCC_APB2Periph_ADC1,ENABLE);
// ADC 模式配置
// 只使用一个ADC,属于独立模式
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
// 禁止扫描模式,多通道才要,单通道不需要
ADC_InitStructure.ADC_ScanConvMode = DISABLE ;
// 连续转换模式
ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;
// 不用外部触发转换,软件开启即可
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
// 转换结果右对齐
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
// 转换通道1个
ADC_InitStructure.ADC_NbrOfChannel = 1;
// 初始化ADC
ADC_Init(ADCx, &ADC_InitStructure);
//配置ADC时钟-6分频12MHZ
RCC_ADCCLKConfig(RCC_PCLK2_Div6);
//配置ADC通道的转换顺序,转换时间
ADC_RegularChannelConfig(ADC_x, ADC_CHANNEL, 1, ADC_SampleTime_55Cycles5);
//使能ADC转换完成中断
ADC_ITConfig(ADC_x, ADC_IT_EOC,ENABLE);
//使能ADC外设
ADC_Cmd(ADC_x,ENABLE);
// 初始化ADC 校准寄存器
ADC_ResetCalibration(ADC_x);
// 等待校准寄存器初始化完成
while(ADC_GetResetCalibrationStatus(ADC_x));
// ADC开始校准
ADC_StartCalibration(ADC_x);
// 等待校准完成
while(ADC_GetCalibrationStatus(ADC_x));
//软件触发
ADC_SoftwareStartConvCmd(ADC_x,ENABLE);
}
void ADCx_Init(void)
{
ADC_NVIC_Config();
ADC_GPIO_Config();
ADCx_Mode_Config();
}
main.c
#include "stm32f10x.h"
#include "bsp_usart.h"
#include "adc.h"
// 软件延时
void Delay(__IO uint32_t nCount)
{
for(; nCount != 0; nCount--);
}
__IO uint16_t ADC_ConvertedValue;
// 局部变量,用于保存转换计算后的电压值
float ADC_ConvertedValueLocal;
int main(void)
{
//初始化USART
USART_Config();
//初始化ADC
ADCx_Init();
while(1)
{
printf("ADC电压采集-单通道-中断读取");
ADC_ConvertedValueLocal =(float) ADC_ConvertedValue/4095*3.3;
printf("\r\n The current AD value = 0x%04X \r\n",ADC_ConvertedValue);
printf("\r\n The current AD value = %f V \r\n",ADC_ConvertedValueLocal);
printf("\r\n\r\n");
Delay(0xfffff);
}
}
中断服务函数
实验效果
四.独立模式多通道采集(DMA读取)实验
实验目的
ADC1循环采集转换各个通道的模拟信号,每转换完成一个通道就通过DMA外设将数据寄存器中的数据传输到存储器SRAM中定义的一个数组中,然后分别打印出各个通道的电压值。
实验原理
这次用了6个通道,不过有一个通道是串行FLASH的片选引脚,默认接的是3.3V.
原理与下图一样只不过通道变多了几个
adc.h
#ifndef __ADC_H
#define __ADC_H
#include "stm32f10x.h"
// ADC 编号选择
// 可以是 ADC1/2,如果使用ADC3,中断相关的要改成ADC3的
#define ADC_APBxClock_FUN RCC_APB2PeriphClockCmd
#define ADC_x ADC1
#define ADC_CLK RCC_APB2Periph_ADC1
// ADC GPIO宏定义
// 注意:用作ADC采集的IO必须没有复用,否则采集电压会有影响
#define ADC_GPIO_APBxClock_FUN RCC_APB2PeriphClockCmd
#define ADC_GPIO_CLK RCC_APB2Periph_GPIOC
#define ADC_PORT GPIOC
//通道数
#define NbrOfChannel 6
// ADC 通道宏定义
#define ADC_PIN_0 GPIO_Pin_0
#define ADC_CHANNEL_0 ADC_Channel_10
#define ADC_PIN_1 GPIO_Pin_1
#define ADC_CHANNEL_1 ADC_Channel_11
#define ADC_PIN_2 GPIO_Pin_2
#define ADC_CHANNEL_2 ADC_Channel_12
#define ADC_PIN_3 GPIO_Pin_3
#define ADC_CHANNEL_3 ADC_Channel_13
#define ADC_PIN_4 GPIO_Pin_4
#define ADC_CHANNEL_4 ADC_Channel_14
#define ADC_PIN_5 GPIO_Pin_5
#define ADC_CHANNEL_5 ADC_Channel_15
void ADCx_Init(void);
#endif /*__ADC_H*/
adc.c
#include "adc.h"
extern __IO uint16_t ADC_ConvertedValue[6];
static void ADC_GPIO_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
ADC_GPIO_APBxClock_FUN(ADC_GPIO_CLK,ENABLE);
//GPIO模式:模拟输入
GPIO_InitStructure.GPIO_Mode =GPIO_Mode_AIN;
GPIO_InitStructure.GPIO_Pin =ADC_PIN_0 ;
GPIO_Init(ADC_PORT, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin =ADC_PIN_1|ADC_PIN_2 | ADC_PIN_3 |ADC_PIN_4 |ADC_PIN_5 ;
GPIO_Init(ADC_PORT, &GPIO_InitStructure);
}
static void ADCx_Mode_Config(void)
{
DMA_InitTypeDef DMA_InitStructure;
//开启DMA时钟,注意DMA挂载在AHB总线上
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
/*以下配置DMA传输参数*/
//设置DMA外设地址为ADC的DR寄存器地址
DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)(&(ADC1->DR));
//设置内存地址(要传输变量的指针)为数组首地址
DMA_InitStructure.DMA_MemoryBaseAddr = (u32)(ADC_ConvertedValue);
//配置传输方向:外设到内存
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
//配置要传输的数目
DMA_InitStructure.DMA_BufferSize = NbrOfChannel;
//开启内存地址递增
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
//禁用外设地址递增
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
//配置外设数据单位为两个字节(16位)
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
//配置内存数据单位为两个字节(16位)
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
//配置DMA模式为循环模式(循环采集各个通道的电压)
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
//配置请求的优先级为High
DMA_InitStructure.DMA_Priority = DMA_Priority_High;
//不开启内存到内存之间的传输
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
//初始化使用到的DMA通道:ADC1对应DMA1的通道1
DMA_Init(DMA1_Channel1, &DMA_InitStructure);
//使能DMA通道
DMA_Cmd(DMA1_Channel1, ENABLE);
//记得开启C99标准
ADC_InitTypeDef ADC_InitStructure;
ADC_APBxClock_FUN(ADC_CLK,ENABLE);
// ADC 模式配置
// 只使用一个ADC,属于单模式
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 = NbrOfChannel;
// 初始化ADC
ADC_Init(ADC_x, &ADC_InitStructure);
//使能ADC1的DMA接收
ADC_DMACmd(ADC_x,ENABLE);
//配置ADC时钟-6分频12MHZ
RCC_ADCCLKConfig(RCC_PCLK2_Div6);
//配置ADC通道的转换顺序,转换时间
ADC_RegularChannelConfig(ADC_x, ADC_CHANNEL_0, 1, ADC_SampleTime_55Cycles5);
ADC_RegularChannelConfig(ADC_x, ADC_CHANNEL_1, 2, ADC_SampleTime_55Cycles5);
ADC_RegularChannelConfig(ADC_x, ADC_CHANNEL_2, 3, ADC_SampleTime_55Cycles5);
ADC_RegularChannelConfig(ADC_x, ADC_CHANNEL_3, 4, ADC_SampleTime_55Cycles5);
ADC_RegularChannelConfig(ADC_x, ADC_CHANNEL_4, 5, ADC_SampleTime_55Cycles5);
ADC_RegularChannelConfig(ADC_x, ADC_CHANNEL_5, 6, ADC_SampleTime_55Cycles5);
//使能ADC外设
ADC_Cmd(ADC_x,ENABLE);
// 初始化ADC 校准寄存器
ADC_ResetCalibration(ADC_x);
// 等待校准寄存器初始化完成
while(ADC_GetResetCalibrationStatus(ADC_x));
// ADC开始校准
ADC_StartCalibration(ADC_x);
// 等待校准完成
while(ADC_GetCalibrationStatus(ADC_x));
//软件触发
ADC_SoftwareStartConvCmd(ADC_x,ENABLE);
}
void ADCx_Init(void)
{
ADC_GPIO_Config();
ADCx_Mode_Config();
}
main.c
#include "stm32f10x.h"
#include "bsp_usart.h"
#include "adc.h"
// 软件延时
void Delay(__IO uint32_t nCount)
{
for(; nCount != 0; nCount--);
}
__IO uint16_t ADC_ConvertedValue[6]={0};
// 用于保存转换计算后的电压值
float ADC_ConvertedValueLocal[6]={0};
int main(void)
{
//初始化USART
USART_Config();
//初始化ADC
ADCx_Init();
while(1)
{
printf("ADC电压采集-单通道-中断读取");
ADC_ConvertedValueLocal[0] =(float) ADC_ConvertedValue[0]/4095*3.3;
ADC_ConvertedValueLocal[1] =(float) ADC_ConvertedValue[1]/4095*3.3;
ADC_ConvertedValueLocal[2] =(float) ADC_ConvertedValue[2]/4095*3.3;
ADC_ConvertedValueLocal[3] =(float) ADC_ConvertedValue[3]/4095*3.3;
ADC_ConvertedValueLocal[4] =(float) ADC_ConvertedValue[4]/4095*3.3;
ADC_ConvertedValueLocal[5] =(float) ADC_ConvertedValue[5]/4095*3.3;
printf("\r\n The ADC_CHANNEL10_PC0 AD value = %f V \r\n",ADC_ConvertedValueLocal[0]);
printf("\r\n The ADC_CHANNEL11_PC1 AD value = %f V \r\n",ADC_ConvertedValueLocal[1]);
printf("\r\n The ADC_CHANNEL12_PC2 AD value = %f V \r\n",ADC_ConvertedValueLocal[2]);
printf("\r\n The ADC_CHANNEL13_PC3 AD value = %f V \r\n",ADC_ConvertedValueLocal[3]);
printf("\r\n The ADC_CHANNEL14_PC4 AD value = %f V \r\n",ADC_ConvertedValueLocal[4]);
printf("\r\n The ADC_CHANNEL15_PC5 AD value = %f V \r\n",ADC_ConvertedValueLocal[5]);
printf("\r\n\r\n");
Delay(0xfffff1);
}
}
实验效果
将依次将各个通道(引脚)用杜邦线接到0V,看看各个通道的电压是否会变为0V
实在是不好上传视频,自己去实验问题不大
五.双ADC同步规则模式(DMA读取)
实验目的
双ADC同时转换各自的通道
实验原理
前面DMA请求那里已经讲过
但有两个要注意的点:
1.不要在两路 ADC 上转换相同的通道(两路 ADC 在同一通道转换时采样时间不可重叠)
2.在同步模式下, ADC1 和 ADC2 同步采样的两个通道的需要设置为准确的相同采样时间
adc.h
#ifndef __ADC_H
#define __ADC_H
#include "stm32f10x.h"
// 双模式时,ADC1和ADC2转换的数据都存放在ADC1的数据寄存器,
// ADC1的在低十六位,ADC2的在高十六位
// 双ADC模式的第一个ADC,必须是ADC1
#define ADCx_1 ADC1
#define ADCx_1_APBxClock_FUN RCC_APB2PeriphClockCmd
#define ADCx_1_CLK RCC_APB2Periph_ADC1
#define ADCx_1_GPIO_APBxClock_FUN RCC_APB2PeriphClockCmd
#define ADCx_1_GPIO_CLK RCC_APB2Periph_GPIOC
#define ADCx_1_PORT GPIOC
#define ADCx_1_PIN_1 GPIO_Pin_0
#define ADCx_1_PIN_2 GPIO_Pin_1
#define ADCx_1_CHANNEL_1 ADC_Channel_10
#define ADCx_1_CHANNEL_2 ADC_Channel_11
// 双ADC模式的第二个ADC,必须是ADC2
#define ADCx_2 ADC2
#define ADCx_2_APBxClock_FUN RCC_APB2PeriphClockCmd
#define ADCx_2_CLK RCC_APB2Periph_ADC2
#define ADCx_2_GPIO_APBxClock_FUN RCC_APB2PeriphClockCmd
#define ADCx_2_GPIO_CLK RCC_APB2Periph_GPIOC
#define ADCx_2_PORT GPIOC
#define ADCx_2_PIN_1 GPIO_Pin_3
#define ADCx_2_PIN_2 GPIO_Pin_4
#define ADCx_2_CHANNEL_1 ADC_Channel_13
#define ADCx_2_CHANNEL_2 ADC_Channel_14
#define NbrOfChannel 2
// ADC1 对应 DMA1通道1,ADC3对应DMA2通道5,ADC2没有DMA功能
#define ADC_DMA_CHANNEL DMA1_Channel1
void ADCx_Init(void);
#endif /*__ADC_H*/
adc.c
#include "adc.h"
extern __IO uint32_t ADC_ConvertedValue[NbrOfChannel];
static void ADCx_GPIO_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
//ADC1 GPIO初始化
ADCx_1_GPIO_APBxClock_FUN(ADCx_1_GPIO_CLK,ENABLE);
//GPIO模式:模拟输入
GPIO_InitStructure.GPIO_Mode =GPIO_Mode_AIN;
//PC0、PC1
GPIO_InitStructure.GPIO_Pin =ADCx_1_PIN_1 | ADCx_1_PIN_2 ;
GPIO_Init(ADCx_1_PORT, &GPIO_InitStructure);
//ADC2 GPIO初始化
ADCx_2_GPIO_APBxClock_FUN(ADCx_2_GPIO_CLK,ENABLE);
//GPIO模式:模拟输入
GPIO_InitStructure.GPIO_Mode =GPIO_Mode_AIN;
//PC4、PC5
GPIO_InitStructure.GPIO_Pin =ADCx_2_PIN_1 | ADCx_2_PIN_2;
GPIO_Init(ADCx_2_PORT, &GPIO_InitStructure);
}
static void ADCx_Mode_Config(void)
{
ADC_InitTypeDef ADC_InitStructure;
DMA_InitTypeDef DMA_InitStructure;
//开启DMA时钟,注意DMA挂载在AHB总线上
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
/*以下配置DMA传输参数*/
//设置DMA外设地址为ADC的DR寄存器地址
DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)(&(ADC1->DR));
//设置内存地址(要传输变量的指针)为数组首地址
DMA_InitStructure.DMA_MemoryBaseAddr = (u32)(ADC_ConvertedValue);
//配置传输方向:外设到内存
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
//配置要传输的数目
DMA_InitStructure.DMA_BufferSize = NbrOfChannel;
//开启内存地址递增
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
//禁用外设地址递增
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
//配置外设数据单位为4个字节(32位)
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word;
//配置内存数据单位为4个字节(32位)
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Word;
//配置DMA模式为循环模式(持续采集2个ADC各个通道的电压)
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
//配置请求的优先级为High
DMA_InitStructure.DMA_Priority = DMA_Priority_High;
//不开启内存到内存之间的传输
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
//初始化使用到的DMA通道:ADC1对应DMA1的通道1
DMA_Init(ADC_DMA_CHANNEL, &DMA_InitStructure);
//使能DMA通道
DMA_Cmd(ADC_DMA_CHANNEL, ENABLE);
//ADC1模式配置
ADCx_1_APBxClock_FUN(ADCx_1_CLK,ENABLE);
ADC_InitStructure.ADC_Mode =ADC_Mode_RegSimult;
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 =NbrOfChannel;
ADC_Init(ADCx_1, &ADC_InitStructure);
//配置ADC通道的转换顺序,转换时间
ADC_RegularChannelConfig(ADCx_1, ADCx_1_CHANNEL_1, 1, ADC_SampleTime_239Cycles5);
//配置ADC通道的转换顺序,转换时间
ADC_RegularChannelConfig(ADCx_1, ADCx_1_CHANNEL_2, 2, ADC_SampleTime_239Cycles5);
//使能ADC1的DMA接收
ADC_DMACmd(ADCx_1,ENABLE);
//配置ADC时钟-6分频12MHZ
RCC_ADCCLKConfig(RCC_PCLK2_Div8);
//使能ADC1外设
ADC_Cmd(ADCx_1,ENABLE);
// 初始化ADC1 校准寄存器
ADC_ResetCalibration(ADCx_1);
// 等待校准寄存器初始化完成
while(ADC_GetResetCalibrationStatus(ADCx_1));
// ADC1开始校准
ADC_StartCalibration(ADCx_1);
// 等待校准完成
while(ADC_GetCalibrationStatus(ADCx_1));
//ADC2模式配置
ADCx_2_APBxClock_FUN(ADCx_2_CLK,ENABLE);
ADC_InitStructure.ADC_Mode =ADC_Mode_RegSimult;
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 =NbrOfChannel;
ADC_Init(ADCx_2, &ADC_InitStructure);
//配置ADC通道的转换顺序,转换时间
ADC_RegularChannelConfig(ADCx_2, ADCx_2_CHANNEL_1, 1, ADC_SampleTime_239Cycles5);
//配置ADC通道的转换顺序,转换时间
ADC_RegularChannelConfig(ADCx_2, ADCx_2_CHANNEL_2, 2, ADC_SampleTime_239Cycles5);
//使能ADC2外设
ADC_Cmd(ADCx_2,ENABLE);
// 初始化ADC2校准寄存器
ADC_ResetCalibration(ADCx_2);
// 等待校准寄存器初始化完成
while(ADC_GetResetCalibrationStatus(ADCx_2));
// ADC2开始校准
ADC_StartCalibration(ADCx_2);
// 等待校准完成
while(ADC_GetCalibrationStatus(ADCx_2));
//ADC2外部触发
ADC_ExternalTrigConvCmd(ADCx_2, ENABLE);
// //ADC2软件触发
// ADC_SoftwareStartConvCmd(ADCx_2,ENABLE);
//ADC1软件触发(一定要写到ADC2触发前面)
ADC_SoftwareStartConvCmd(ADCx_1,ENABLE);
}
void ADCx_Init(void)
{
ADCx_GPIO_Config();
ADCx_Mode_Config();
}
main.c
#include "stm32f10x.h"
#include "bsp_usart.h"
#include "adc.h"
// 软件延时
void Delay(__IO uint32_t nCount)
{
for(; nCount != 0; nCount--);
}
__IO uint32_t ADC_ConvertedValue[NbrOfChannel]={0};
// 用于保存转换计算后的电压值
float ADC_ConvertedValueLocal[NbrOfChannel*2]={0};
int main(void)
{
uint16_t tmp1=0;
uint16_t tmp2=0;
uint16_t tmp3=0;
uint16_t tmp4=0;
//初始化USART
USART_Config();
//初始化ADC
ADCx_Init();
while(1)
{
printf("ADC电压采集-双ADC-同步规则");
//ADC1 通道10
tmp1=(ADC_ConvertedValue[0]& 0x0000FFFF);
//ADC1 通道11
tmp2=(ADC_ConvertedValue[1]& 0x0000FFFF);
//ADC2 通道13
tmp3=(ADC_ConvertedValue[0]& 0xFFFF0000)>>16;
//ADC2 通道14
tmp4=(ADC_ConvertedValue[1]& 0xFFFF0000)>>16;
ADC_ConvertedValueLocal[0] =(float)tmp1/4095*3.3;
ADC_ConvertedValueLocal[1] =(float)tmp2/4095*3.3;
ADC_ConvertedValueLocal[2] =(float)tmp3/4095*3.3;
ADC_ConvertedValueLocal[3] =(float)tmp4/4095*3.3;
printf("\r\n The ADC1_CHANNEL10_PC0 AD value = %f V \r\n",ADC_ConvertedValueLocal[0]);
printf("\r\n The ADC1_CHANNEL12_PC1 AD value = %f V \r\n",ADC_ConvertedValueLocal[1]);
printf("\r\n The ADC2_CHANNEL13_PC3 AD value = %f V \r\n",ADC_ConvertedValueLocal[2]);
printf("\r\n The ADC2_CHANNEL14_PC4 AD value = %f V \r\n",ADC_ConvertedValueLocal[3]);
printf("\r\n\r\n");
Delay(0xfffff5);
}
}
实验效果
六.单通道内部温度传感器实验
实验目的
温度传感器在内部和ADC1_IN16输入通道相连接,此通道把传感器输出的电压转换成数字值,然后通过公式转换成电压值,然后再通过公式转换成温度值,这样就能检测内容芯片温度
实验原理
度传感器可以用来测量器件周围的温度(TA)。
温度传感器在内部和ADC1_IN16输入通道相连接,此通道把传感器输出的电压转换成数字值。
温度传感器模拟输入推荐采样时间是17.1μs。
当没有被使用时,传感器可以置于关电模式
必须设置TSVREFE位激活内部通道:ADC1_IN16(温度传感器)和ADC1_IN17(VREFINT)的转换。
温度传感器输出电压随温度线性变化,由于生产过程的变化,温度变化曲线的偏移在不同芯片上会有不同(最多相差45°C)。
内部温度传感器更适合于检测温度的变化,而不是测量绝对的温度。如果需要测量精确的温度,应该使用一个外置的温度传感器
这句话的意思是不能用来测量温度的精确值,因为误差较大,但是可以监测温度的变化值。
读温度
为使用传感器:
- 选择ADC1_IN16输入通道
- 选择采样时间为17.1 μs
- 设置ADC控制寄存器2(ADC_CR2)的TSVREFE位,以唤醒关电模式下的温度传感器
- 通过设置ADON位启动ADC转换(或用外部触发)
- 读ADC数据寄存器上的VSENSE 数据结果
-
利用下列公式得出温度**
温度(°C) = ( (V25 - VSENSE) / Avg_Slope ) + 25
VSENSE为当前测量到的电压值
V25 = VSENSE在25°C时对应的电压值
Avg_Slope = 温度与VSENSE曲线的平均斜率(单位为mV/ °C 或 μV/ °C)
采样时间要到达大于17.1us,直接看你设置的设置的采样时间周期*1/时钟频率看看是否达到就可以啦
adc.h
#ifndef __ADC_H
#define __ADC_H
#include "stm32f10x.h"
#define ADC_APBxClock_FUN RCC_APB2PeriphClockCmd
#define ADC_x ADC1
#define ADC_CLK RCC_APB2Periph_ADC1
void T_ADCx_Init(void);
uint16_t T_Get_Adc_Average(uint8_t channl,uint8_t times);
#endif /*__ADC_H*/
adc.c
#include "adc.h"
extern void Delay(__IO uint32_t nCount);
static void ADCx_Mode_Config(void)
{
ADC_InitTypeDef ADC_InitStructure;
ADC_APBxClock_FUN(ADC_CLK,ENABLE);
ADC_InitStructure.ADC_Mode =ADC_Mode_Independent;
ADC_InitStructure.ADC_ScanConvMode =DISABLE;
ADC_InitStructure.ADC_ContinuousConvMode =DISABLE;
ADC_InitStructure.ADC_ExternalTrigConv =ADC_ExternalTrigConv_None;
ADC_InitStructure.ADC_DataAlign =ADC_DataAlign_Right;
ADC_InitStructure.ADC_NbrOfChannel =1;
ADC_Init(ADC_x, &ADC_InitStructure);
//开启内部温度传感器
ADC_TempSensorVrefintCmd(ENABLE);
//配置ADC时钟-6分频12MHZ
RCC_ADCCLKConfig(RCC_PCLK2_Div6);
//使能ADC外设
ADC_Cmd(ADC_x,ENABLE);
// 初始化ADC 校准寄存器
ADC_ResetCalibration(ADC_x);
// 等待校准寄存器初始化完成
while(ADC_GetResetCalibrationStatus(ADC_x));
// ADC开始校准
ADC_StartCalibration(ADC_x);
// 等待校准完成
while(ADC_GetCalibrationStatus(ADC_x));
}
uint16_t T_Get_Adc(uint8_t channel )
{
//配置ADC通道,转换顺序,转换时间
ADC_RegularChannelConfig(ADC_x, channel, 1, ADC_SampleTime_239Cycles5);
//软件触发
ADC_SoftwareStartConvCmd(ADC_x,ENABLE);
while( !ADC_GetFlagStatus(ADC_x,ADC_FLAG_EOC) );
return ADC_GetConversionValue(ADC_x);
}
//得到ADC采样内部温度传感器的值
//取10次,然后平均
uint16_t T_Get_Temp(void)
{
uint16_t temp_val=0;
uint8_t i;
for(i=0;i<10;i++)
{
temp_val+=T_Get_Adc(ADC_Channel_16);
Delay(0xffff);
}
return temp_val/10;
}
//获取通channel转换值
//取times次,取平均值
uint16_t T_Get_Adc_Average(uint8_t channel,uint8_t times)
{
uint32_t temp_val=0;
uint8_t i;
for(i=0;i<times;i++)
{
temp_val+=T_Get_Adc(channel);
Delay(0xffff);
}
return temp_val/times;
}
void T_ADCx_Init(void)
{
ADCx_Mode_Config();
}
main.c
#include "stm32f10x.h"
#include "bsp_usart.h"
#include "adc.h"
// 软件延时
void Delay(__IO uint32_t nCount)
{
for(; nCount != 0; nCount--);
}
int main(void)
{
// 用于保存转换计算后的温度值
float ADC_ConvertedValue;
float ADC_ConvertedValueLocal;
//初始化USART
USART_Config();
//初始化ADC
T_ADCx_Init();
while(1)
{
printf("ADC电压采集-单通道-温度传感器");
ADC_ConvertedValue =(float)T_Get_Adc_Average(ADC_Channel_16,10)/4096*3.3;
ADC_ConvertedValueLocal=(1.43-ADC_ConvertedValue)/0.0043+25;
printf("\r\n 当前温度: %f \r\n",ADC_ConvertedValueLocal);
printf("\r\n\r\n");
Delay(0xfffff2);
}
}
实验效果
七.单通道采集DMA读取模拟看门狗
实验目的
检测一个通道的电压(电压范围0~3.3V),并设置一个上限电压2.5V,一个下限电压1.5V,当超过2.5V或小于1.5V时亮红灯警告。
实验原理
根据上面选择通道。
我们这里只选择规则通道11, 对应引脚为PC1.
然后开启中断
通过滑动变阻器改变电压值,触发模拟看门狗中断,在中断服务函数里面点亮一个LED红灯.
adc.h
#ifndef __ADC_H
#define __ADC_H
#include "stm32f10x.h"
// ADC 编号选择
// 可以是 ADC1/2,如果使用ADC3,中断相关的要改成ADC3的
#define ADC_APBxClock_FUN RCC_APB2PeriphClockCmd
#define ADC_x ADC1
#define ADC_CLK RCC_APB2Periph_ADC1
// ADC GPIO宏定义
// 注意:用作ADC采集的IO必须没有复用,否则采集电压会有影响
#define ADC_GPIO_APBxClock_FUN RCC_APB2PeriphClockCmd
#define ADC_GPIO_CLK RCC_APB2Periph_GPIOC
#define ADC_PORT GPIOC
#define ADC_PIN GPIO_Pin_1
// ADC 通道宏定义
#define ADC_CHANNEL ADC_Channel_11
// ADC 中断相关宏定义
#define ADC_IRQ ADC1_2_IRQn
#define ADC_IRQHandler ADC1_2_IRQHandler
void ADCx_Init(void);
#endif /*__ADC_H*/
adc.c
#include "adc.h"
extern __IO uint16_t ADC_ConvertedValue;
static void ADC_NVIC_Config(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitStructure.NVIC_IRQChannel =ADC_IRQ;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority =1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority =1;
NVIC_InitStructure.NVIC_IRQChannelCmd =ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
static void ADCx_GPIO_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
ADC_GPIO_APBxClock_FUN(ADC_GPIO_CLK,ENABLE);
//GPIO模式:模拟输入
GPIO_InitStructure.GPIO_Mode =GPIO_Mode_AIN;
GPIO_InitStructure.GPIO_Pin =ADC_PIN;
GPIO_Init(ADC_PORT, &GPIO_InitStructure);
}
static void ADCx_Mode_Config(void)
{
//ADC初始化结构体
ADC_InitTypeDef ADC_InitStructure;
//DMA初始化结构体
DMA_InitTypeDef DMA_InitStructure;
//开启DMA时钟,注意DMA挂载在AHB总线上
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
/*以下配置DMA传输参数*/
//设置DMA外设地址为ADC的DR寄存器地址
DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)(&(ADC1->DR));
//设置内存地址(要传输变量的指针(地址))
DMA_InitStructure.DMA_MemoryBaseAddr = (u32)(&ADC_ConvertedValue);
//配置传输方向:外设到内存
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
//配置要传输的数目
DMA_InitStructure.DMA_BufferSize = 1;
//禁用内存地址递增
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Disable;
//禁用外设地址递增
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
//配置外设数据单位为两个字节(16位)
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
//配置内存数据单位为两个字节(16位)
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
//配置DMA模式为循环模式(持续采集电压)
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
//配置请求的优先级为High
DMA_InitStructure.DMA_Priority = DMA_Priority_High;
//不开启内存到内存之间的传输
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
//初始化使用到的DMA通道:ADC1对应DMA1的通道1
DMA_Init(DMA1_Channel1, &DMA_InitStructure);
//使能DMA通道
DMA_Cmd(DMA1_Channel1, ENABLE);
//开启ADC时钟
ADC_APBxClock_FUN(ADC_CLK,ENABLE);
ADC_InitStructure.ADC_Mode =ADC_Mode_Independent;
ADC_InitStructure.ADC_ScanConvMode =DISABLE;
ADC_InitStructure.ADC_ContinuousConvMode =ENABLE;
ADC_InitStructure.ADC_ExternalTrigConv =ADC_ExternalTrigConv_None;
ADC_InitStructure.ADC_DataAlign =ADC_DataAlign_Right;
ADC_InitStructure.ADC_NbrOfChannel =1;
ADC_Init(ADC_x, &ADC_InitStructure);
//使能ADC1的DMA接收
ADC_DMACmd(ADC_x,ENABLE);
//配置ADC时钟-6分频12MHZ
RCC_ADCCLKConfig(RCC_PCLK2_Div6);
//配置ADC通道,转换顺序,转换时间
ADC_RegularChannelConfig(ADC_x, ADC_CHANNEL, 1, ADC_SampleTime_55Cycles5);
//使能ADC转换完成中断
ADC_ITConfig(ADC_x, ADC_IT_AWD,ENABLE);
//选择单个规则通道
ADC_AnalogWatchdogCmd(ADC_x, ADC_AnalogWatchdog_SingleRegEnable);
//设置上限电压2.5V,下限电压1.5V
ADC_AnalogWatchdogThresholdsConfig(ADC_x,2.5/3.3*4095, 1.5/3.3*4095);
//选择单个通道
ADC_AnalogWatchdogSingleChannelConfig(ADC_x,ADC_Channel_11);
//使能ADC外设
ADC_Cmd(ADC_x,ENABLE);
// 初始化ADC 校准寄存器
ADC_ResetCalibration(ADC_x);
// 等待校准寄存器初始化完成
while(ADC_GetResetCalibrationStatus(ADC_x));
// ADC开始校准
ADC_StartCalibration(ADC_x);
// 等待校准完成
while(ADC_GetCalibrationStatus(ADC_x));
//软件触发
ADC_SoftwareStartConvCmd(ADC_x,ENABLE);
}
void ADCx_Init(void)
{
ADC_NVIC_Config();
ADCx_GPIO_Config();
ADCx_Mode_Config();
}
main.c
#include "stm32f10x.h"
#include "bsp_usart.h"
#include "adc.h"
#include "bsp_led.h"
// 软件延时
void Delay(__IO uint32_t nCount)
{
for(; nCount != 0; nCount--);
}
__IO uint16_t ADC_ConvertedValue;
// 局部变量,用于保存转换计算后的电压值
float ADC_ConvertedValueLocal;
int main(void)
{
LED_GPIO_Config();
//初始化USART
USART_Config();
//初始化ADC
ADCx_Init();
while(1)
{
printf("ADC电压采集-单通道-DMA读取-看门狗触发");
ADC_ConvertedValueLocal =(float) ADC_ConvertedValue/4096*3.3;
printf("\r\n The current AD value = 0x%04X \r\n",ADC_ConvertedValue);
printf("\r\n The current AD value = %f V \r\n",ADC_ConvertedValueLocal);
printf("\r\n\r\n");
Delay(0xfffff);
}
}
中断服务函数
实验效果
八.按键外部触发ADC转换
实验目的
按下按键外部触发ADC开始转换,不按按键ADC不进行转换
实验原理
输入一个有效信号 1 时就会产生一个脉冲,如果输入端是无效信号就不会输出脉冲。这个脉冲信号可以给其他外设电路使用,比如定时器 TIM、模拟数字转换器 ADC 等等,这样的脉冲信号一般用来触发 TIM 或者 ADC 开始转换
外部事件有不懂的看————》《中断和EXTI外设详解》
ADC1要配置成外部触发(EXTI_11)模式
然后开启外部触发
GPIO模式配置成上拉输入按键按下产生一个下降沿,事件触发模式要选择下降沿触发
直接上代码:
adc.h
#ifndef __ADC_H
#define __ADC_H
#include "stm32f10x.h"
// ADC 编号选择
// 可以是 ADC1/2,如果使用ADC3,中断相关的要改成ADC3的
#define ADC_APBxClock_FUN RCC_APB2PeriphClockCmd
#define ADC_x ADC1
#define ADC_CLK RCC_APB2Periph_ADC1
// ADC GPIO宏定义
// 注意:用作ADC采集的IO必须没有复用,否则采集电压会有影响
#define ADC_GPIO_APBxClock_FUN RCC_APB2PeriphClockCmd
#define ADC_GPIO_CLK RCC_APB2Periph_GPIOA
#define ADC_PORT GPIOA
#define ADC_PIN GPIO_Pin_1
// ADC 通道宏定义
#define ADC_CHANNEL ADC_Channel_1
#define EXTI_Key1_GPIO_PIN GPIO_Pin_11
#define EXTI_Key1_GPIO_POTR GPIOA
#define EXTI_Key1_GPIO_CLK RCC_APB2Periph_GPIOA
void ADCx_Init(void);
#endif /*__ADC_H*/
adc.c
#include "adc.h"
extern __IO uint16_t ADC_ConvertedValue;
static void ADC_GPIO_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
ADC_GPIO_APBxClock_FUN(ADC_GPIO_CLK,ENABLE);
//GPIO模式:模拟输入
GPIO_InitStructure.GPIO_Mode =GPIO_Mode_AIN;
GPIO_InitStructure.GPIO_Pin =ADC_PIN;
GPIO_Init(ADC_PORT, &GPIO_InitStructure);
}
void EXTI_Key_Config(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
EXTI_InitTypeDef EXTI_InitStruct;
RCC_APB2PeriphClockCmd(EXTI_Key1_GPIO_CLK,ENABLE);
//上拉输入
GPIO_InitStruct.GPIO_Mode=GPIO_Mode_IPU;
GPIO_InitStruct.GPIO_Pin= EXTI_Key1_GPIO_PIN ;
GPIO_Init(EXTI_Key1_GPIO_POTR,&GPIO_InitStruct);
//一定要使能外设AFIO外设的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
//选择信号源
GPIO_EXTILineConfig(GPIO_PortSourceGPIOA,GPIO_PinSource11);
//选择事件线
EXTI_InitStruct.EXTI_Line = EXTI_Line11;
//选择模式这里选择事件模式
EXTI_InitStruct.EXTI_Mode = EXTI_Mode_Event;
//下降沿触发模式
EXTI_InitStruct.EXTI_Trigger = EXTI_Trigger_Falling;
//使能事件
EXTI_InitStruct.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStruct);
}
static void ADCx_Mode_Config(void)
{
DMA_InitTypeDef DMA_InitStructure;
//开启DMA时钟,注意DMA挂载在AHB总线上
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
/*以下配置DMA传输参数*/
//设置DMA外设地址为ADC的DR寄存器地址
DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)(&(ADC1->DR));
//设置内存地址(要传输变量的指针(地址))
DMA_InitStructure.DMA_MemoryBaseAddr = (u32)(&ADC_ConvertedValue);
//配置传输方向:外设到内存
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
//配置要传输的数目
DMA_InitStructure.DMA_BufferSize = 1;
//禁用内存地址递增
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Disable;
//禁用外设地址递增
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
//配置外设数据单位为两个字节(16位)
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
//配置内存数据单位为两个字节(16位)
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
//配置DMA模式为循环模式(持续采集电压)
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
//配置请求的优先级为High
DMA_InitStructure.DMA_Priority = DMA_Priority_High;
//不开启内存到内存之间的传输
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
//初始化使用到的DMA通道:ADC1对应DMA1的通道1
DMA_Init(DMA1_Channel1, &DMA_InitStructure);
//使能DMA通道
DMA_Cmd(DMA1_Channel1, ENABLE);
//记得开启C99标准
ADC_InitTypeDef ADC_InitStructure;
ADC_APBxClock_FUN(ADC_CLK,ENABLE);
ADC_InitStructure.ADC_Mode =ADC_Mode_Independent;
ADC_InitStructure.ADC_ScanConvMode =DISABLE;
ADC_InitStructure.ADC_ContinuousConvMode =ENABLE;
ADC_InitStructure.ADC_ExternalTrigConv =ADC_ExternalTrigConv_Ext_IT11_TIM8_TRGO;
ADC_InitStructure.ADC_DataAlign =ADC_DataAlign_Right;
ADC_InitStructure.ADC_NbrOfChannel =1;
ADC_Init(ADC_x, &ADC_InitStructure);
//使能ADC1的DMA接收
ADC_DMACmd(ADC_x,ENABLE);
//配置ADC时钟-6分频12MHZ
RCC_ADCCLKConfig(RCC_PCLK2_Div6);
//配置ADC通道,转换顺序,转换时间
ADC_RegularChannelConfig(ADC_x, ADC_CHANNEL, 1, ADC_SampleTime_55Cycles5);
//使能ADC外设
ADC_Cmd(ADC_x,ENABLE);
// 初始化ADC 校准寄存器
ADC_ResetCalibration(ADC_x);
// 等待校准寄存器初始化完成
while(ADC_GetResetCalibrationStatus(ADC_x));
// ADC开始校准
ADC_StartCalibration(ADC_x);
// 等待校准完成
while(ADC_GetCalibrationStatus(ADC_x));
// //软件触发
// ADC_SoftwareStartConvCmd(ADC_x,ENABLE);
//
ADC_ExternalTrigConvCmd(ADC_x, ENABLE);
}
void ADCx_Init(void)
{
EXTI_Key_Config();
ADC_GPIO_Config();
ADCx_Mode_Config();
main.c文章来源:https://www.toymoban.com/news/detail-806652.html
#include "stm32f10x.h"
#include "bsp_usart.h"
#include "adc.h"
// 软件延时
void Delay(__IO uint32_t nCount)
{
for(; nCount != 0; nCount--);
}
__IO uint16_t ADC_ConvertedValue;
// 局部变量,用于保存转换计算后的电压值
float ADC_ConvertedValueLocal;
/**
* @brief 主函数
* @param 无
* @retval 无
*/
int main(void)
{
//初始化USART
USART_Config();
//初始化ADC
ADCx_Init();
while(1)
{
printf("ADC电压采集-单通道-中断读取");
ADC_ConvertedValueLocal =(float) ADC_ConvertedValue/4096*3.3;
printf("\r\n The current AD value = 0x%04X \r\n",ADC_ConvertedValue);
printf("\r\n The current AD value = %f V \r\n",ADC_ConvertedValueLocal);
printf("\r\n\r\n");
Delay(0xfffff);
}
}
实验效果
文章来源地址https://www.toymoban.com/news/detail-806652.html
到了这里,关于STM32ADC模拟/数字转换详解的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!