STM32

发布于:2025-07-23 ⋅ 阅读:(14) ⋅ 点赞:(0)

目录

#ADC模数转换器#

1.ADC简介

2.ADC工作步骤:

1.采样 (Sampling):

2.量化 (Quantization):

3.编码 (Encoding):

总结工作流程:

3.ADC具体工作流程:

 4.逐次逼近型(SAR)ADC(了解即可)

1.SAR ADC 核心组件:

2.工作步骤详解

3.总结理解:

4.注意点

5.STM32ADC框图

规则组 vs 注入组

6.ADC基本架构

7.输入通道

8.触发控制(规则组的触发源)

9.数据对齐

10.转换时间

11.校准

12.硬件电路

13.AD单通道

1.ADC初始化

2.AD库函数

一、ADC库函数(基本功能和规则组配置)(adc.h)带有Inject就是相关注入组配置

二、模拟看门狗函数配置

三、俩个内部通道

四、有关标志位函数

五、有关中断函数

3.AD.c代码编写

4.AD.h代码编写

5.main.c代码编写

6.实际显示现象问题解决:

14.AD多通道

1.AD.c部分代码修改

2.main.c代码


#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简介

  • ADCAnalog-Digital Converter)模拟-数字转换器
  • ADC可以将引脚上连续变化的模拟电压转换为内存中存储的数字变量,建立模拟电路到数字电路的桥梁连续变化的模拟电压信号(比如温度传感器输出的电压、麦克风采集的声音电压、电位器调节的电压),转换成STM32能够理解和处理的离散数字值(一串0和1组成的二进制数)

联想:

使用DAC就可以将数字变量转化为模拟电压。还有一个数字-模拟的桥梁是PWM。我们使用PWM来控制led的亮度,电机的速度,与DAC的功能相似。PWM只有完全导通和完全断开两种状态。在这两种状态上都没有功率损耗,所以在直流电机调速这种大功率的应用场景,使用PWM来等效模拟量是比DAC更好的选择。PWM电路更加简单,更加常用。DAC的应用主要是在波形生成这些领域。PWM主要应用在信号发生器、音频解码芯片等这些领域。

  • 12位逐次逼近型ADC1us转换时间
  • 输入电压范围: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资源:ADC1ADC210个外部输入通道

①有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位 ADC2^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 模块。其工作流程和原理与上述通用原理一致,但包含更多细节和可配置项:

  1. 模拟输入通道选择: STM32 的 ADC 通常有多个输入通道(如 ADC_IN0, ADC_IN1, ...)。需要配置 ADC 使用哪个(或哪些)引脚来接收模拟信号。这些引脚通常标记为带有 ADCx_INy 功能的 GPIO。
  2. 时钟配置: ADC 模块需要一个时钟源 (ADCCLK)。这个时钟频率需要根据 ADC 的性能要求和芯片手册的限制来设置(不能超过最大允许频率)。
  3. 采样时间配置: 对于每个通道,需要设置采样时间。这是指 ADC 内部采样保持电路对输入电压进行充电稳定的时间。信号源阻抗越高,需要的采样时间越长,以确保电容充电到准确的输入电压。时间太短会导致采样不准确。
  4. 分辨率选择: 配置 ADC 的输出位数(如 12位、10位、8位、6位)。选择更高的分辨率得到更精细的结果,但转换时间可能稍长。
  5. 转换模式:
  • 单次转换 (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);
	}
}


网站公告

今日签到

点亮在社区的每一天
去签到