目录
一、ADC库函数(基本功能和规则组配置)(adc.h)带有Inject就是相关注入组配置
#ADC模数转换器#
用电位器(滑动变阻器)产生0~3.3V连续变化的模拟电压信号,接入STM32,用其内部的ADC读取电压数据,显示在OLED上
STM32ADC是12位,即AD结果最大时2^12-1=4095
AD最小0,对应电压0V
AD最大4095,对应电压3.3V
STM32的GPIO口,只能读取引脚的高低电平(1/0)+ADC(电压表:可对高低电平之间的任意电压进行量化)
电位器:左拧数值减小,右拧数值增大
光敏电阻:光线减小AD值增大,光线增强AD值减小
热敏电阻:温度升高AD值减小,温度降低AD值增大
反射红外传感器:靠近AD值减小,远离AD值增大
1.ADC简介
- ADC(Analog-Digital Converter)模拟-数字转换器
- ADC可以将引脚上连续变化的模拟电压转换为内存中存储的数字变量,建立模拟电路到数字电路的桥梁(把连续变化的模拟电压信号(比如温度传感器输出的电压、麦克风采集的声音电压、电位器调节的电压),转换成STM32能够理解和处理的离散数字值(一串0和1组成的二进制数))
联想:
使用DAC就可以将数字变量转化为模拟电压。还有一个数字-模拟的桥梁是PWM。我们使用PWM来控制led的亮度,电机的速度,与DAC的功能相似。PWM只有完全导通和完全断开两种状态。在这两种状态上都没有功率损耗,所以在直流电机调速这种大功率的应用场景,使用PWM来等效模拟量是比DAC更好的选择。PWM电路更加简单,更加常用。DAC的应用主要是在波形生成这些领域。PWM主要应用在信号发生器、音频解码芯片等这些领域。
- 12位逐次逼近型ADC,1us转换时间
- 输入电压范围:0~3.3V,转换结果范围:0~4095
①12位ADC分辨率是2^12=4096等级,AD值范围0~2^12-1(量化范围),位数越高,分辨率越高
②ADC的输入电压一般要求是在芯片供电的负极和正极之间变化,最低电压为负极0V,最高电压为正极3.3V,经过ADC转换之后,最小值0,最大值4095。0V-0,3.3V-4095,中间是一一对应的线性关系。
③转换时间就是转换频率。AD转换是需要时间。1us表示从AD转换开始到产生结果,需要花1us的时间,对应AD转换的频率就是1MHz(STM32的ADC的最快转换频率)。
④逐次逼近型(SAR) ADC 的工作原理就是“二分搜索”算法的硬件实现,不过它“猜”的是模拟电压值
- (最多)18个输入通道,可测量16个外部和2个内部信号源
①外部信号源有16个GPIO口,可在引脚直接模拟信号(测电压),不需要任何额外的电路
②俩个内部信号源是内部温度传感器和内部参考电压
1)温度传感器可测量CPU的温度(电脑显示CPU是用ADC读取温度传感器测量)
2)内部参考电压是1.2V左右的基准电压(不随外部供电电压变化而变化),因此,若芯片的供电标准不是3.3V,测量外部引脚电压可能不对,此时可读取内部基准电压进行校准
- 规则组和注入组两个转换单元
STM32 ADC增强的功能。普通的AD转换流程是启动一次转换读一次AD值,再启动再读AD值这样的流程。
但STM32 ADC可列一个组,一次性启动一个组,连续转换多个值。拥有俩个组,一个是用于常规使用的规则组,一个是用于突发事件的注入组。
- 模拟看门狗自动监测输入电压范围
①ADC一般用于测量光线强度,温度值,常伴随一些比较(数值高于某个阈值或者低于某个阈值)后进行的操作。
②模拟看门狗执行判断高于或低于某个阈值。模拟看门狗可以检测指定的通道。当AD值高于或低于阈值时,会申请中断,后在中断函数执行操作(不需要不断手动续值再用if判断)
- STM32F103C8T6 ADC资源:ADC1、ADC2,10个外部输入通道
①有2个ADC外设,10个外部输入通道,即最多测量10个外部引脚的模拟信号。
②前文有说16个外部信号源时STM32系列最多16个外部信号源。
2.ADC工作步骤:
ADC 的工作过程可以想象成用一把标尺去测量电压的高度,主要经过三个关键步骤:
1.采样 (Sampling):
- 想象:每隔一小段时间(比如 1 毫秒),用一个高速开关快速“瞥一眼”当前的模拟电压值,并把这个瞬间的电压值抓取(捕获)下来。
- 作用:把连续的时间信号,变成一系列在离散时间点上的电压值(样本)。
- 关键参数:采样率 (Sampling Rate): 每秒采样的次数(单位:SPS - Samples Per Second)。根据奈奎斯特采样定理,采样率必须至少是信号最高频率的两倍,才能无失真地还原原始信号。采样率越高,能捕获的信号变化细节越多。
2.量化 (Quantization):
- 想象:你有一把刻度有限的尺子(比如最小刻度是 1cm)。当你测量一个长度(比如 3.7cm)时,你必须把它“归类”到最接近的刻度上(4cm 或 3cm)。这个过程就是量化。
- 作用:把采样抓到的连续电压值(理论上可以是任意精度的数值),近似转换为某个离散的量化电平。ADC 的测量范围 (比如 0V 到 3.3V) 被划分成有限多个量化等级 (Levels)。
- 关键参数:分辨率 (Resolution): 表示 ADC 能把参考电压范围划分得多细。通常用位数 (Bits) 表示:
- 8位 ADC: 能把范围分成 2^8 = 256 个等级。
- 10位 ADC: 2^10 = 1024 个等级。
- 12位 ADC (STM32常见): 2^12 = 4096 个等级。
- 16位 ADC: 2^16 = 65536 个等级。
- 位数越高,分辨率越高: 每个量化等级代表的电压值越小 (Vref / (2^N - 1)),测量结果越精细,越接近真实的模拟电压值。
3.编码 (Encoding):
- 想象:你测量得到 3.7cm,用你的尺子量化后确定为 4cm。然后你需要把这个结果用数字记录下来(比如写成 “4”)。
- 作用:把量化后得到的量化电平值,转换成一个对应的二进制数字,这个数字就是最终输出给 CPU 的结果。
- 例如:一个 3.3V 参考电压的 12 位 ADC:
0V 输入 -> 数字值 0 (二进制 0000 0000 0000)
1.65V 输入 (一半) -> 数字值 2048 (二进制 1000 0000 0000)
3.3V 输入 -> 数字值 4095 (二进制 1111 1111 1111)
总结工作流程:
连续模拟信号 -> [采样] -> 离散时间点上的电压样本 -> [量化] -> 离散的量化电平值 -> [编码] -> 离散的二进制数字输出。
3.ADC具体工作流程:
STM32 内部集成了一个或多个高性能的 ADC 模块。其工作流程和原理与上述通用原理一致,但包含更多细节和可配置项:
- 模拟输入通道选择: STM32 的 ADC 通常有多个输入通道(如 ADC_IN0, ADC_IN1, ...)。需要配置 ADC 使用哪个(或哪些)引脚来接收模拟信号。这些引脚通常标记为带有 ADCx_INy 功能的 GPIO。
- 时钟配置: ADC 模块需要一个时钟源 (ADCCLK)。这个时钟频率需要根据 ADC 的性能要求和芯片手册的限制来设置(不能超过最大允许频率)。
- 采样时间配置: 对于每个通道,需要设置采样时间。这是指 ADC 内部采样保持电路对输入电压进行充电稳定的时间。信号源阻抗越高,需要的采样时间越长,以确保电容充电到准确的输入电压。时间太短会导致采样不准确。
- 分辨率选择: 配置 ADC 的输出位数(如 12位、10位、8位、6位)。选择更高的分辨率得到更精细的结果,但转换时间可能稍长。
- 转换模式:
- 单次转换 (Single Conversion): 启动一次转换,转换完一个通道就停止。
Eg.规则组的“菜单”,共16个序列。非扫描模式,只有序列1有效,因此菜单同时选中一组的方式就退化为简单的选中一个的方式了。
在序列1指定想转换的通道,例如通道2,ADC对通道2进行模数转换,转换完成,转换结果放在数据寄存器,同时给EOC标志位置1。读取标志位,判断是否转换完成。若想再转换,需要再触发一次。若想换通道,在转换之前将序列1的通道改成其他通道再启动转换。
- 连续转换 (Continuous Conversion): 启动后,ADC 会不停地自动进行转换(转换完立即开始下一次)。
非扫描模式:菜单列表就只用第一个。它与上一种单次转换不同的地方是它在一次转换结束后不会停止,而是立刻开始下一轮的转换,一直持续下去。此模式只需要最开始的触发,之后可以一直转换。
这个模式的好处是开始转换后,不需要等待一段时间再次触发ADC转换。(因为它一直在转换,所以不需要手动开始转换,也不用判断是否结束)。读AD值直接从数据寄存器读取。
- 扫描模式 (Scan Mode): 使能多个通道时,ADC 会自动按顺序转换一组预先配置好的通道。
单次转换,每触发一次转换结束后会停下来,下次转换得再触发才能开始。
扫描模式,会用到菜单列表。(可以在菜单点菜,若第一个菜是通道二,第二个菜是通道五……)序列中是通道几,可以任意指定,并且可以重复。
初始化结构体会有一个参数,通道数目。因为菜单有16个序列,可以不用完16个序列,只用前几个序列,就需要再给一个通道数目的参数,告诉它有几个通道,并且只在这指定的序列里指定通道。
Eg.指定通道数目为7,它就只看前7个位置。在每次触发后,依次对这前7个位置进行AD转换,并将结果放在数据寄存器。为了防止数据被覆盖,需要DMA,及时将数据挪走。
在单次转换扫描模式里增加功能:不中断,立即执行下一次的转换。
在扫描模式的情况下,还有一种模式叫间断模式。
它的作用是在扫描过程中,每隔几个转换就暂停一次。需要再次触发才能继续。
- 注入通道 (Injected Channels): 具有更高优先级,可以中断规则通道的转换序列。
6.触发源选择: ADC 转换可以由软件触发(调用库函数启动转换)或硬件触发(如定时器的某个事件、外部引脚信号等)。硬件触发对于需要精确同步采样的应用(如电机控制、音频)非常重要。
7.数据对齐: 配置转换结果在数据寄存器中是左对齐还是右对齐(主要影响读取时的移位操作)。
8.中断/DMA:
- 中断 (Interrupt): 当转换完成(或发生错误)时,产生中断,CPU 在中断服务程序中读取转换结果。适合低速或非频繁转换。
- DMA (Direct Memory Access): 对于高速、连续、多通道采样,配置 DMA 让 ADC 转换结果自动传输到内存数组中,无需 CPU 干预。这是高效处理大量 ADC 数据的关键技术。
9.校准 (Calibration): STM32 ADC 通常提供自校准功能。上电后执行一次校准有助于减小内部误差(如偏移误差),提高精度。建议在初始化 ADC 后执行校准
10.读取结果: 转换完成后,从指定的 ADC 数据寄存器 (ADCx->DR) 中读取转换得到的数字值。
11.举例说明:
假设:
- Vref = 3.3V
- ADC 分辨率 = 12位 (4096级)
- 你测量一个 1.65V 的电压。
1.采样: ADC 在某个时刻捕获到输入电压为 1.65V。
2.量化: ADC 将 0-3.3V 分成 4096 级。每级代表 3.3V / 4095 ≈ 0.000806V (约 0.8mV)。1.65V 大约位于 1.65V / 0.000806V ≈ 2047.5 级。根据 ADC 的设计,它会归入 2047 或 2048 级(通常是四舍五入或截断)。
3.编码: 假设量化结果为 2048 级。
4.输出: ADC 输出数字值 2048 (二进制 1000 0000 0000) 给 STM32。
5.程序计算: 你的程序读取到这个值 2048。
6.还原电压: 程序计算实际电压: Vin = (2048 / 4095) * 3.3V ≈ 1.650V (实际计算通常用 Vin = (ADC_Value * Vref) / (2^12 - 1) = (2048 * 3.3) / 4095 ≈ 1.650V)。
4.逐次逼近型(SAR)ADC(了解即可)
1.SAR ADC 核心组件:
①采样保持电路 (Sample and Hold, S&H): 在转换开始瞬间,“抓拍”并锁定住当前输入的模拟电压 (Vin),并在整个转换周期内保持这个电压值恒定不变。(猜数字游戏中确定的数字)
②数模转换器 (Digital-to-Analog Converter, DAC): 它的作用是根据一个数字输入码,产生一个对应的模拟电压 (Vdac)。(猜数字)
③电压比较器 (Comparator): 它有两个输入端:
- 一个接锁定的输入电压 (Vin_held)。
- 一个接 DAC 产生的猜测电压 (Vdac)。
- 输出结果:
1)Vdac > Vin_held,输出 1 (高电平)。
2)Vdac < Vin_held,输出 0 (低电平) 。
④逐次逼近寄存器 (Successive Approximation Register, SAR): 整个 ADC 的控制中心。
- 存储着当前正在“猜测”的数字值(最终结果也会存在这里)。
- 按照从最高有效位 (MSB) 到最低有效位 (LSB) 的顺序,逐位进行猜测和决策。
- 根据比较器的结果 (0 或 1),决定当前位是保留为 1 还是清除为 0,并设置下一位进行猜测
2.工作步骤详解
(以 4 位 SAR ADC 为例,参考电压 Vref = 1.0V,假设输入电压 Vin = 0.625V):
①采样 (Sampling): S&H 电路捕获并锁定当前 Vin = 0.625V。
②初始化 SAR: SAR 寄存器清零 -> 0000。设置当前位为 MSB (Bit3, 权重 0.5V = Vref/2)。
③循环开始 (Bit3 - MSB):
1)猜测 (Set): SAR 将 Bit3 置 1 -> 1000 (二进制)。
2)DAC 转换: DAC 收到 1000 (二进制 8),输出 Vdac = (8/16) * 1.0V = 0.5V。
3)比较: 比较器比较 Vdac (0.5V) 和 Vin_held (0.625V)。
4)0.5V < 0.625V -> 比较器输出 0 (“猜小了”)。
5)决策 (Decide): 因为猜小了,SAR 保留 Bit3 为 1 (因为实际电压比 0.5V 大,所以最高位必须是 1)。SAR 值保持 1000。
④下一位 (Bit2 - 权重 0.25V = Vref/4):
1)猜测 (Set): SAR 将 Bit2 置 1 -> 1100 (二进制 12)。
2)DAC 转换: Vdac = (12/16) * 1.0V = 0.75V。
3)比较: 0.75V > 0.625V -> 比较器输出 1 (“猜大了”)。
4)决策 (Decide): 因为猜大了,SAR 清除 Bit2 为 0。SAR 值回退到 1000,然后设置 Bit2=0 -> 1000 (但 Bit2 状态现在是 0)。
⑤下一位 (Bit1 - 权重 0.125V = Vref/8):
1)猜测 (Set): SAR 将 Bit1 置 1 -> 1010 (二进制 10)。
2)DAC 转换: Vdac = (10/16) * 1.0V = 0.625V。
3)比较: 0.625V == 0.625V -> 比较器输出 0 (通常设计为小于等于输出 0,或者视为“猜大了”的临界点。实践中由于噪声和精度,完全相等很少见,这里假设输出0)。
4)决策 (Decide): 比较器输出 0 (“猜小了” 或 “相等”)。SAR 保留 Bit1 为 1。SAR 值 1010。
⑥下一位 (Bit0 - LSB - 权重 0.0625V = Vref/16):
1)猜测 (Set): SAR 将 Bit0 置 1 -> 1011 (二进制 11)。
2)DAC 转换: Vdac = (11/16) * 1.0V = 0.6875V。
3)比较: 0.6875V > 0.625V -> 比较器输出 1 (“猜大了”)。
4)决策 (Decide): 因为猜大了,SAR 清除 Bit0 为 0。SAR 最终值 1010 (二进制 10)。
⑦转换完成: 所有位 (4位) 都已确定。SAR 寄存器中的值 1010 (十进制 10) 就是本次 ADC 转换的数字输出。
1)计算实际电压: Vin_calculated = (10 / 15) * 1.0V(Vref) ≈ 0.666V (理论值应为 (10/16)*1.0V=0.625V,但数字值 10 对应的范围是 10 * (Vref/16) = 10*0.0625V = 0.625V。15 是 2^4 - 1 = 15,所以 10/15 ≈ 0.666V 是量化后的表示值范围。实际应用中通常用 (ADC_Value * Vref) / (2^N - 1) 计算。
3.总结理解:
把 SAR ADC 想象成一个用二分搜索法玩猜电压游戏的高速电子天平:
- 锁定的输入电压 (Vin_held) 是要称重的“物体”。
- SAR 寄存器是“砝码盒”和“记录员”。
- DAC 是根据“砝码盒”指示(数字码)往天平上加/减标准“砝码”(产生 Vdac)的“操作员”。
- 比较器就是“天平指针”,告诉“记录员”当前砝码总和比物体重了还是轻了(Vdac > Vin_held?)。
- “记录员” (SAR) 根据“天平指针”的结果,从最大砝码(MSB)开始,依次决定每个砝码是留下(对应位=1)还是拿走(对应位=0)。
- 当最小砝码(LSB)也决定完后,“记录员”记下的砝码组合(二进制数字)就是物体重量的数字化表示。
4.注意点
①位逐次逼近 (Bit-by-Bit): 从最高位 (MSB) 开始,到最低位 (LSB) 结束,一位一位地确定。每一位需要一个时钟周期进行比较和决策。
②转换时间固定且可预测: 对于一个 N 位的 SAR ADC,完成一次转换精确地需要 N 个时钟周期 (每个位占一个周期),再加上少量的采样和设置时间。这使得 SAR ADC 的转换时间非常容易计算和预测。例如,一个 12 位 SAR ADC 至少需要 12 个 ADC 时钟周期完成转换。
③中等速度,高精度: 相对于超高速的 Flash ADC (需要大量比较器) 和超低速高精度的 Delta-Sigma ADC,SAR ADC 在速度(典型范围从几十 kSPS 到几 MSPS)和精度(常见 12 位、14 位、16 位)之间取得了极佳的平衡,非常适合微控制器应用(如 STM32)。
④低功耗: SAR ADC 在转换间隙(非采样转换期间)可以进入低功耗模式。而且其核心组件(比较器、DAC、SAR 逻辑)在运行时功耗也相对可控。
⑤核心依赖 DAC 和比较器的精度: SAR ADC 的最终精度高度依赖于内部 DAC 的线性度、精度和稳定性,以及比较器的分辨率和噪声性能。参考电压 (Vref) 的精度和稳定性也至关重要。
⑥采样保持 (S&H) 是关键: 必须在转换开始瞬间精确捕捉并在整个转换期间稳定保持输入电压。任何在转换期间输入电压的变化(或 S&H 电容的电荷泄漏)都会导致转换错误。这就是为什么需要设置足够的“采样时间”让 S&H 电容充分充电到输入电压。
⑦DAC 的实现: 在单片集成的 SAR ADC(如 STM32 内部 ADC)中,DAC 几乎总是采用电荷再分配开关电容式 DAC (Charge-Redistribution Switched-Capacitor DAC)。它利用精密的电容阵列(权重电容:C, C/2, C/4, C/8…)和开关来实现二进制权重的电压输出。这种结构非常适合 CMOS 工艺集成。理解这个细节需要更深入的模拟电路知识,但对于理解 SAR 原理,知道 DAC 能根据数字码产生精确的比较电压即可。
⑧量化误差: 和其他 ADC 一样,SAR ADC 也存在固有的量化误差(±0.5 LSB)。
5.STM32ADC框图
普通ADC,多路开关一般只选择一个,在选中的通道转换再取出结果
高级ADC,多路凯旋可同时选择多个,在转换的过程中分成俩组(规则组+注入组)
举个例子,这就像是去餐厅点菜,普通的ADC是你指定一个菜,老板给做,然后做好了送给你。
而这里高级ADC就是指定一个菜单,这个菜单最多可以填十六个菜,然后直接递个菜单给老板,老板就按照菜单的顺序依次做好,一次性给端上来,这样的话就可以大大提高效率。
当然你的菜单也可以只写一个菜,这样这个菜单就简化成了普通的模式
规则组可以一次性最多选十六个通道,注入组最多选4个通道
理解:
- 规则组 (Regular Group): 想象成规则组菜单,可以同时上十六个菜,但是它有个尴尬的地方,就是在这里这个规则组只有一个数据寄存器,就是这个桌子比较小,最多只能放一个菜。如果上十六个菜,前十五个菜都会被挤掉,只能得到第十六个菜。所以对于规则组转换来说,如果使用这个菜单的话,最好配合DMA来实现,DAM是一个数据转运小帮手,它可以在每上一个菜之后,把这个菜挪到其它地方去,防止被覆盖(被挤掉)。(优先级低,可被注入组打断+软件/硬件启动)
规则组虽然可以同时转换十六个通道,但是数据寄存器只能存一个结果。如果不想之前的结果被覆盖,在转换完成之后,就要尽快把结果拿走。
- 注入组 (Injected Group): 相当于是餐厅的vip座位,在这个座位上,一次性最多可以点四个菜。并且这里数据寄存器有四个,是可以同时上四个菜的。对于注入组而言,就不用担心数据覆盖的问题了。(优先级高+硬件触发事件启动)
规则组 vs 注入组
特性 | 规则组 (Regular Group) | 注入组 (Injected Group) |
---|---|---|
最大通道数 | 16 | 4 |
优先级 | 低 | 高 (可打断规则组) |
启动方式 | 软件启动 或 硬件触发 | 仅硬件事件触发 |
结果寄存器 | 单一共享寄存器 (ADCx->DR ) |
4个独立寄存器 (ADCx->JDR1 - JDR4 ) |
DMA 支持 | 完美支持 (主要数据流) | 不支持 (需 CPU 读取独立寄存器) |
上下文保存/恢复 | 无 | 自动保存/恢复被打断的规则组状态 |
典型应用 | 周期性多通道数据采集 (传感器, 电池电压等) | 关键实时事件 (过流保护, 同步电流采样等) |
类比 | 常规待办事项列表 | 紧急中断任务 |
一般情况下,使用规则组就足够,注意搭配DMA转运数据,防止数据被覆盖。
①(绿色线框)图1-1的触发转换相当于图1-2的start信号开始转换,触发ADC开始转换的信号有俩种:
1.软件触发
2.硬件触发((绿色上框)ADCx-ETRGINJ_REMAP控制位为注入组触发源;(绿色下框)ADCx-ETRGREG_REM控制位为规则组触发源)触发源主要来自定时器(各个通道,TRGO定时器主模式输出)
定时器可通过ADC/DAC外设用于触发转换。由于ADC需要时间转换,正常思路时通过定时器每隔时间申请中断,在中断手动开启一次转换,但频繁进入中断对程序有影响。
因此频繁中断+中断函数完成简单工作→硬件触发
比如,给TIM3定一个1ms的时间,将TIM3的更新事件选择为TRGO输出。再ADC选择开始触发信号TIM3_TRGO,由此TIM3的更新事件可通过硬件自动触发ADC转换。
整个过程不需要进中断,节省中断资源——定时器触发
②(红色线框)vref+,vref-是ADC的参考电压,决定ADC输入电压的范围。VDDA和VSSA是ADC的的共电引脚。
一般情况下,vref+接VDDA,vref-接VSSA。但STM32F103C8T6没有vref+和vref-的引脚。其内部已经将VDDA和VSSA连接。
VDDA和VSSA在引脚定义表可知,VDDA和VSSA是内部模拟部分的电源,比如ADC、RC振荡器,锁相环等。
在这里VDDA接3.3V,VSSA接GND,所以ADC的输入电压范围就是0到3.3V。
③(黄色图层)图1-1的ADC_CLK是ADC时钟同时使图1-2的clock,驱动内部逐次比较时钟(来自ADC预分频器)
预分频器来自RCC
APB2时钟72MHz,通过ADC预分频器分频,得到ADC_CLK(最大14MHz)
这个预分频器选择2/4/6/8分频。如果选择2分频,72M/2=36M,超出允许范围。4分频之后是18M也超。所以对于ADC预分频器只能选择6分频,结果是12M,8分频结果是9M两个值。在程序中注意。
④(蓝色线框)模拟看门狗(存放阈值高限和低限),若启动模拟看门狗,并且指定看门通道,看门狗会关注看门通道,超过阈值范围,会鸣叫且申请一个模拟看门狗中断指向NVIC
⑤规则组和注入组转换完成,产生EOC/JEOC转换完成信号。俩个信号会在状态寄存器置一个标志位(通过读取标志位可知转换是否结束),同时俩个标志位EOC/JEOC可以到达NVIC,开启NVIC,触发中断。
6.ADC基本架构
7.输入通道
通过引脚定义表发现ADC12_IN1……ADC12_IN9(10个通道),ADC1和ADC2都是相同的通道
STM32是双ADC模式
双ADC模式是ADC1和ADC2一起工作。它俩可以配合组成同步模式、交叉模式等模式。
Eg.交叉模式:ADC1和ADC2交叉的对一个通道进行采样,可以进一步提高采样率。(就像打拳一样,左手打一拳,右手打一拳,左手打一拳,右手打一拳,快速交叉的打拳,打击的频率肯定就比一个拳头打的快。)
ADC1和ADC2也可以分开使用,可以分别对不同的引脚进行采样。
通道 |
ADC1 |
ADC2 |
ADC3 |
通道0 |
PA0 |
PA0 |
PA0 |
通道1 |
PA1 |
PA1 |
PA1 |
通道2 |
PA2 |
PA2 |
PA2 |
通道3 |
PA3 |
PA3 |
PA3 |
通道4 |
PA4 |
PA4 |
PF6 |
通道5 |
PA5 |
PA5 |
PF7 |
通道6 |
PA6 |
PA6 |
PF8 |
通道7 |
PA7 |
PA7 |
PF9 |
通道8 |
PB0 |
PB0 |
PF10 |
通道9 |
PB1 |
PB1 |
|
通道10 |
PC0 |
PC0 |
PC0 |
通道11 |
PC1 |
PC1 |
PC1 |
通道12 |
PC2 |
PC2 |
PC2 |
通道13 |
PC3 |
PC3 |
PC3 |
通道14 |
PC4 |
PC4 |
|
通道15 |
PC5 |
PC5 |
|
通道16 |
温度传感器 |
||
通道17 |
内部参考电压 |
所列通道表格有0到17共18个通道。通道16对应ADC1的温度传感器。通道17对应ADC1的内部参考电压。只有ADC1有通道16和17,ADC2和ADC3是没有的。
(我们使用的芯片没有ADC3且总共10个通道)
8.触发控制(规则组的触发源)
9.数据对齐
ADC是12位,转换结果是一个12位数据
但数据寄存器是16位,所以存在一个数据对齐的问题
一般选择右对齐,其数据准确
左对齐数据比实际大,因为数据在高位,直接读取数据时实际值的16倍
左对齐的用途:降低分辨率。
正常位数是0~4095,选择左对齐+提取高八位数值(舍弃后四位精度),降低分辨率,位数变成8位(0~255)
10.转换时间
- AD转换的步骤:采样,保持,(量化,编码)-逐次比较过程
- STM32 ADC的总转换时间为:
TCONV = 采样时间(采样保持) + 12.5个ADC周期(量化编码)
12位ADC,至少花费12个周期量化编码
ADC周期是从RCC分频过来的ADC_CLK
- 例如:当ADCCLK=14MHz,采样时间为1.5个ADC周期
TCONV = 1.5 + 12.5 = 14个ADC周期 = 1μs(最快转换时间)
11.校准
- ADC有一个内置自校准模式。校准可大幅减小因内部电容器组的变化而造成的准精度误差。校准期间,在每个电容器上都会计算出一个误差修正码(数字值),这个码用于消除在随后的转换中每个电容器上产生的误差
- 建议在每次上电后执行一次校准
- 启动校准前, ADC必须处于关电状态超过至少两个ADC时钟周期
12.硬件电路
①电位器可调电压电路(上滑电压↑下滑电压↓)
②传感器输出电压电路(传感器阻值↓下拉作用变强,输出端电压↓,传感器阻值↑,下拉作用变弱,输出端受上拉作用电压↑)固定电阻一般选择和传感器阻值相近的,可得到一个位于中间电压区域比较好的输出。
③电压转换电路:若想测一个0~5V的VIN电压,但ADC只能接收0~3.3V的电压,因此可以搭建一个简易转换电路,使用电阻分压。上面阻值17k,下面阻值33k加一起50k。根据分压公式,中间的电压是VIN/50k*33k,得到的电压范围0~3.3V,可进行ADC转换。
高电压采集最好使用专用采集芯片(eg隔离放大器)
13.AD单通道
1.ADC初始化
①开启RCC时钟,包括ADC和GPIO时钟,配置RCC内部时钟的ADC_CLK
②配置GPIO口,将其配置成模拟输入模式,PA0
③配置多路开关,将左边通道接入右边规则组列表
④配置AD转换器和AD数据寄存器,用结构体配置参数,包括单次/连续转换,(非)扫描,通道数量,触发源和数据对齐模式
⑤若需要数据比较,即配置模拟看门狗(配置阈值和监测通道)
⑥若开启中断,需在中断输出控制配置ITConflg函数开启对应中断输出,再配置NVIC优先级
⑦配置开关控制,调用ADC_Cmd函数,开启ADC
开启ADC后,还可对ADC进行校准,进行减小误差
ADC工作时,若需要软件触发转换,可进行函数触发;若想读取转换结果,可进行函数读取
2.AD库函数
一、ADC库函数(基本功能和规则组配置)(adc.h)
带有Inject就是相关注入组配置
1.ADCCLK配置函数(rcc.h)
void RCC_ADCCLKConfig(uint32_t RCC_PCLK2);
配置ADCCLK分频器2.Delint恢复省配置:指将某个系统、设备或软件的设置恢复到默认的初始状态。
void ADC_DeInit(ADC_TypeDef* ADCx);3.Init初始化
void ADC_Init(ADC_TypeDef* ADCx, ADC_InitTypeDef* ADC_InitStruct);4.Structlint结构体初始化
void ADC_StructInit(ADC_InitTypeDef* ADC_InitStruct);5.上电ADC(开关控制)
void ADC_Cmd(ADC_TypeDef* ADCx, FunctionalState NewState);6.开启DMA输出信号(使用DMA转运数据)
void ADC_DMACmd(ADC_TypeDef* ADCx, FunctionalState NewState);7.中断输出控制(控制某个中断是否通入NVIC)
void ADC_ITConfig(ADC_TypeDef* ADCx, uint16_t ADC_IT, FunctionalState NewState);8.校准相关配置(ADC初始化后依次调用即可)
①复位校准
void ADC_ResetCalibration(ADC_TypeDef* ADCx);
②获取复位校准状态
FlagStatus ADC_GetResetCalibrationStatus(ADC_TypeDef* ADCx);
③开始校准
void ADC_StartCalibration(ADC_TypeDef* ADCx);
④获取开始校准状态
FlagStatus ADC_GetCalibrationStatus(ADC_TypeDef* ADCx);
9.触发控制中软件控制
①ADC软件开始转换控制(用于软件触发),给SWSTART位置1,开始转换
void ADC_SoftwareStartConvCmd(ADC_TypeDef* ADCx, FunctionalState NewState);
②(一般不用)ADC获取软件开始转换状态,返回SWSTART状态,由于SWSTART在转换后立刻清零,因此与转换是否结束无关
FlagStatus ADC_GetSoftwareStartConvStatus(ADC_TypeDef* ADCx);
③判断转换是否结束(获取标志位EOC状态)
FlagStatus ADC_GetFlagStatus(ADC_TypeDef* ADCx, uint8_t ADC_FLAG);
10.配置间断模式
①每隔几个通道间断一次
void ADC_DiscModeChannelCountConfig(ADC_TypeDef* ADCx, uint8_t Number);
②是否启用间断模式
void ADC_DiscModeCmd(ADC_TypeDef* ADCx, FunctionalState NewState);11.ADC规则组通道配置(给序列每隔位置填写指定通道)
void ADC_RegularChannelConfig(ADC_TypeDef* ADCx, uint8_t ADC_Channel, uint8_t Rank, uint8_t ADC_SampleTime);12.ADC外部触发转换控制(是否允许外部触发转换)
void ADC_ExternalTrigConvCmd(ADC_TypeDef* ADCx, FunctionalState NewState);13.获取转换值
①ADC获取转换值(获取AD转换数据寄存器,读取转换结果)
uint16_t ADC_GetConversionValue(ADC_TypeDef* ADCx);
②ADC获取双模式转换值(双ADC模式读取转换结果)
uint32_t ADC_GetDualModeConversionValue(void);
二、模拟看门狗函数配置
1.启动看门狗
void ADC_AnalogWatchdogCmd(ADC_TypeDef* ADCx, uint32_t ADC_AnalogWatchdog);
2.配置高低阈值
void ADC_AnalogWatchdogThresholdsConfig(ADC_TypeDef* ADCx, uint16_t HighThreshold, uint16_t LowThreshold);
3.配置看门通道
void ADC_AnalogWatchdogSingleChannelConfig(ADC_TypeDef* ADCx, uint8_t ADC_Channel);
三、俩个内部通道
1.ADC温度传感器,内部参考电压控制(开启内部俩个通道)
void ADC_TempSensorVrefintCmd(FunctionalState NewState);
四、有关标志位函数
1.获取标志位状态
FlagStatus ADC_GetFlagStatus(ADC_TypeDef* ADCx, uint8_t ADC_FLAG);
2.清楚标志位
void ADC_ClearFlag(ADC_TypeDef* ADCx, uint8_t ADC_FLAG);
五、有关中断函数
1.获取中断状态
ITStatus ADC_GetITStatus(ADC_TypeDef* ADCx, uint16_t ADC_IT);
2.清除中断挂起位
void ADC_ClearITPendingBit(ADC_TypeDef* ADCx, uint16_t ADC_IT);
3.AD.c代码编写
void AD_init(void)
{
//①开启RCC时钟,包括ADC和GPIO时钟,配置RCC内部时钟的ADC_CLK
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE);
RCC_ADCCLKConfig(RCC_PCLK2_Div6);//6分频 72/6=12MHz
//②配置GPIO口,将其配置成模拟输入模式,PA0
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AIN;//模拟输入
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_0;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
//③配置多路开关,将左边通道接入右边规则组列表
ADC_RegularChannelConfig(ADC1,ADC_Channel_0,1,ADC_SampleTime_55Cycles5);
//非扫描模式,指定通道0(PA0),放在第一个序列,采样时间根据需求决定(快转换小参数,稳定转换大参数)
//若想在其他序列填写其他通道,复制函数即可,每隔通道也可以设置不同的采样时间
//④配置AD转换器和AD数据寄存器,用结构体配置参数,包括单次/连续转换,(非)扫描,通道数量,触发源和数据对齐模式
//用结构体初始化ADC
ADC_InitTypeDef ADC_InitStructure;
ADC_InitStructure.ADC_ContinuousConvMode=DISABLE;//单次转换
ADC_InitStructure.ADC_DataAlign=ADC_DataAlign_Right ;
ADC_InitStructure.ADC_ExternalTrigConv=ADC_ExternalTrigConv_None ;//外部触发源选择,不选择,使用软件触发
ADC_InitStructure.ADC_Mode=ADC_Mode_Independent;
ADC_InitStructure.ADC_NbrOfChannel=1;
ADC_InitStructure.ADC_ScanConvMode=DISABLE;//非扫描模式
ADC_Init(ADC1,&ADC_InitStructure);
//⑤配置ADC电源
ADC_Cmd(ADC1,ENABLE);
//⑥ADC校准
//复位校准
ADC_ResetCalibration(ADC1);//开始复位校准置1
//获取复位校准状态
while(ADC_GetResetCalibrationStatus(ADC1)== SET );//若为1,一直复位校准;若为0,复位校准完成跳出循环
//set通常表示 “设置” 或 “使能” 状态。在逻辑上,它的值一般被定义为 1,用来表示某个功能或标志被激活、启用或处于高电平状态。
//reset通常表示 “复位” 或 “清除” 状态。在逻辑上,它的值一般被定义为 0,用来表示某个功能或标志被禁用、清除或处于低电平状态。
//开始校准
ADC_StartCalibration(ADC1);
//获取开始校准状态
while(ADC_GetCalibrationStatus(ADC1)== SET );//等待校准标志位完成
}
//启动转换,获取结果
uint16_t ADC_Getvalue(void)
{
//①ADC软件开始转换控制(用于软件触发),给SWSTART位置1,开始转换
ADC_SoftwareStartConvCmd(ADC1, ENABLE);
//②判断转换是否结束(获取标志位EOC状态)
while(ADC_GetFlagStatus(ADC1,ADC_FLAG_EOC)==RESET);//转换未完成,条件为真,执行空循环
//获取AD值
return ADC_GetConversionValue(ADC1);//不需手动清除标志位
}
4.AD.h代码编写
#ifndef __AD_D_
#define __AD_H_
void AD_init(void);
uint16_t ADC_Getvalue(void);
#endif
5.main.c代码编写
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "AD.h"
uint16_t AD_value;
int main(void)
{
OLED_Init();
AD_init();
OLED_ShowString(1,1,"AD:");
while(1)
{
AD_value=ADC_Getvalue();
OLED_ShowSignedNum(1,4,AD_value,5);
}
}
若想显示实际电压:
//显示实际电压,对数据进行线性转换
float Volf ;
uint16_t AD_value;
int main(void)
{
OLED_Init();
AD_init();
OLED_ShowString(1,1,"AD:");
OLED_ShowString(2,1,"Volf:0.00V");
while(1)
{
AD_value=ADC_Getvalue();
Volf=((float)AD_value/4095*3.3)*100;//数值扩大100倍
OLED_ShowSignedNum(1,4,AD_value,5);
OLED_ShowNum(2,6,Volf,1);//第一位
OLED_ShowNum(2,8,(uint16_t)Volf%100,2);//第2和第3位
Delay_ms(100);
}
}
若想连续转换
修改几个函数
① ADC_InitStructure.ADC_ContinuousConvMode=ENABLE;//连续转换
② ADC_SoftwareStartConvCmd(ADC1, ENABLE);//连续触发只需触发一次,,因此放在初始化最后
③ while(ADC_GetFlagStatus(ADC1,ADC_FLAG_EOC)==RESET);//转换未完成,条件为真,执行空循环
因为数据器不断刷新最新转换结果,因此判断转换是否结束(获取标志位EOC状态)不需要
6.实际显示现象问题解决:
#观察现象,OLED中数值末位一直跳动,若想根据阈值判断些内容,会现象跳动#
解决方法:
设置俩个阈值
低于下阈值,操作
高于上阈值,操作
#若想数值平滑#
方法:
①增加滤波器,读取几个数值取平均值,作为滤波AD值
②裁剪分辨率,把数据尾数裁剪(比如数据左对齐+提取高八位数值/16)
14.AD多通道
多通道,第一个想到是扫描模式,但是必须配置DMA使用,可能想到扫描完一个通道,手动获取数据,但是扫描每一个通道,不会产生标志位,也不会产生中断,对于一个通道的扫描情况未知;只有扫描所有通道后,才产生一个EOC标志位,会出中断,但是这种情况就会数据丢失。
多通道,第二个就是间断模式。扫描时,每转换一个通道就暂停一次,手动获取数据后,再触发,再转换……但是每隔通道转换完成后没有标志位,因此启动转换后,只能Delay延时来确保转换完成。这种方式延长时间,效率不高。
多通道,第三个就是单次转换,非扫描方式。在每次出发之前,手动更改列表第一个位置的通道。比如,第一次转换,先写入通道0,之后触发,等待,读值;第二次转换,将通道0改成通道1,之后触发,等待,读值;第三次转换,将通道1改成通道2……
1.AD.c部分代码修改
//初始化函数中,GPIO引脚初始化
//②配置GPIO口,将其配置成模拟输入模式,PA0,PA1,PA2
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AIN;//模拟输入
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
//启动转换,获取结果
uint16_t ADC_Getvalue(uint8_t ADC_Channel)
{
ADC_RegularChannelConfig(ADC1,ADC_Channel,1,ADC_SampleTime_55Cycles5);
//在转换之前,指定通道
//①ADC软件开始转换控制(用于软件触发),给SWSTART位置1,开始转换
ADC_SoftwareStartConvCmd(ADC1, ENABLE);
//②判断转换是否结束(获取标志位EOC状态)
while(ADC_GetFlagStatus(ADC1,ADC_FLAG_EOC)==RESET);//转换未完成,条件为真,执行空循环
//获取AD值
return ADC_GetConversionValue(ADC1);//不需手动清除标志位
}
2.main.c代码
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "AD.h"
uint16_t AD0,AD1,AD2,AD3;
int main(void)
{
OLED_Init();
AD_init();
OLED_ShowString(1,1,"AD0:");
OLED_ShowString(2,1,"AD1:");
OLED_ShowString(3,1,"AD2:");
OLED_ShowString(4,1,"AD3:");
while(1)
{
AD0=ADC_Getvalue(ADC_Channel_0);
AD1=ADC_Getvalue(ADC_Channel_1);
AD2=ADC_Getvalue(ADC_Channel_2);
AD3=ADC_Getvalue(ADC_Channel_3);
OLED_ShowNum(1,5,AD0,4);
OLED_ShowNum(2,5,AD1,4);
OLED_ShowNum(3,5,AD2,4);
OLED_ShowNum(4,5,AD3,4);
Delay_ms(100);
}
}