你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

【经验分享】STM32实例-ADC 模数转换实验

[复制链接]
STMCU小助手 发布时间:2022-6-28 18:33
    本文我们来学**STM32F1 的 ADC 模数转换功能。要实现的功能是:通过 ADC1 通道 1 采样外部电压值, 将采样的 AD 值和转换后的电压值通过串口打印出来, 同时 D1 指示灯闪烁, 提示系统正常运行。学习时可以参考 《STM32F10x中文参考手册》-11 模数转换器(ADC)章节,特别是寄存器介绍部分。
    需要《STM32F10x中文参考手册》的小伙伴请通过以下方式获取。

/ D. B7 v! n# c1 U. M! q7 z3 h
STM32F1 ADC 简介
    ADC(analog to digital converter)即模数转换器,它可以将模拟信号转换为数字信号。按照其转换原理主要分为逐次逼近型、双积分型、电压频率转换型三种。STM32F1 的 ADC就是逐次逼近型的模拟数字转换器。
    STM32F103 系列一般都有 3 个 ADC,这些 ADC 可以独立使用,也可以使用双重/三重模式(提高采样率)。STM32F1 的 ADC 是 12 位逐次逼近型的模拟数字转换器。它具有多达 18 个复用通道,可测量来自16 个外部源、2 个内部信号源。这些通道的 A/D 转换可以单次、连续、扫描或间断模式执行。ADC 的结果可以左对齐或右对齐方式存储在 16 位数据寄存器中。ADC 具有模拟看门狗特性,允许应用程序检测输入电压是否超出用户定义的阀值上限或者下限。
    STM32F1 ADC 主要特性:
● 12 位分辨率
● 转换结束、注入转换结束和发生模拟看门狗事件时产生中断
● 单次和连续转换模式
● 从通道 0 到通道 n的自动扫描模式
● 自校准
● 带内嵌数据一致性的数据对齐
● 采样间隔可以按通道分别编程
● 规则转换和注入转换均有外部触发选项
● 间断模式
● 双重模式(带 2 个或以上ADC 的器件)
● ADC 转换时间:
─ STM32F103xx增强型产品:时钟为56MHz时为1μs(时钟为72MHz为1.17
μs)
─ STM32F101xx基本型产品:时钟为28MHz时为1μs(时钟为36MHz为1.55
μs)
─ STM32F102xxUSB型产品:时钟为 48MHz 时为 1.2μs
─ STM32F105xx和STM32F107xx产品:时钟为56MHz时为1μs(时钟为72MHz
为 1.17μs)
● ADC 供电要求:2.4V到3.6V
● ADC 输入范围:VREF- ≤ VIN ≤ VREF+
● 规则通道转换期间有DMA 请求产生。

2 ?& a9 J7 D$ `
STM32F1 ADC 结构框图
    STM32F1 ADC 拥有这么多功能,是由 ADC 内部结构所决定。要更好的理解STM32F1 的 ADC,就需要了解它内部的结构。如下图所示:(大家也可以查看《STM32F10x 中文参考手册》-11 模数转换器(ADC)章-ADC 功能说明)。
微信图片_20220627223433.jpg

3 [" h% }8 }2 e; {2 a( ]. R7 {
微信图片_20220627223429.jpg
) E  w' A6 D* ^) {
    我们把 ADC 结构框图分成7个子模块,按照顺序依次进行简单介绍。
(1)标号 1:电压输入引脚
    ADC 输入电压范围为:VREF- ≤ VIN ≤ VREF+。由 VREF-、 VREF+ 、
VDDA 、 VSSA 这四个外部引脚决定。通常我们把 VSSA 和 VREF-接地,把 VREF+和 VDDA 接 3.3V,因此 ADC 的输入电压范围为:0~3.3V。我们使用的开发板 ADC输入电压范围为 0~3.3V。
    如果我们想让 ADC 测试负电压或者更高的正电压, 可以在外部加一个电压调理电路,把需要转换的电压抬升或者降压到 0~3.3V,这样 ADC 就可以测量了。但一定记住,不要直接将高于 3.3V 的电压接到 ADC 管脚上,那样将可能烧坏芯片。
(2)标号 2:输入通道
    STM32 的 ADC 的输入通道多达 18 个,其中外部的 16 个通道就是框图中的 ADCx_IN0、ADCx_IN1...ADCx_IN5(x=1/2/3,表示 ADC 数),通过这16 个外部通道可以采集模拟信号。这 16 个通道对应着不同的 IO 口, 具体是哪一个IO 口可以从数据手册查询到,也可以从下图查看,同样我们在开发板芯片原理图内也给大家标注了。其中 ADC1 还有 2 个内部通道:ADC1 的通道16 连接到了芯片内部的温度传感器,通道 17 连接到了内部参考电压 VREFINT。ADC2 和ADC3的通道 16、 17 全部连接到了内部的 VSS。

, b$ g5 F2 p! I
微信图片_20220627223420.jpg

: |  `; g5 F. O( M: H! }
(3)标号 3:通道转换顺序
    外部的 16 个通道在转换的时候可分为 2 组通道:规则通道组和注入通道组,其中规则通道组最多有 16路,注入通道组最多有 4 路。
    规则通道组:从名字来理解,规则通道就是一种规规矩矩的通道,类似于正常执行的程序。通常我们使用的都是这个通道。
    注入通道组:从名字来理解,注入即为插入,是一种不安分的通道,类似于中断。当程序正常往下执行时,中断可以打断程序的执行。同样如果在规则通道转换过程中,有注入通道插队,那么就要先转换完注入通道,等注入通道转换完成后,再回到规则通道的转换流程。
    每个组包含一个转换序列,该序列可按任意顺序在任意通道上完成。例如,可按以下顺序对序列进行转换:ADC_IN3、ADC_IN8、 ADC_IN2、 ADC_IN2、ADC_IN0、 ADC_IN2、 ADC_IN2、 ADC_IN15。
    规则通道组序列寄存器有 3 个,分别是 SQR3、 SQR2、 SQR1。SQR3 控制着规则序列中的第一个到第六个转换,对应的位为:SQ1[4:0]~SQ6[4:0],第一次转换的是位 4:0 SQ1[4:0],如果通道 3 想第一次转换,那么在 SQ1[4:0]写 3即可。SQR2 控制着规则序列中的第 7 到第 12 个转换,对应的位为:SQ7[4:0]~SQ12[4:0],如果通道 1 想第 8 个转换,则 SQ8[4:0]写 1 即可。
    SQR1 控 制 着 规 则 序 列 中 的 第 13 到 第 16 个 转 换 , 对 应 位 为 :
SQ13[4:0]~SQ16[4:0],如果通道 6 想第 10 个转换,则 SQ10[4:0]写 6 即可。具体使用多少个通道,由 SQR1 的位 L[3:0]决定,最多 16 个通道。注入通道组序列寄存器只有一个,是 JSQR。它最多支持 4 个通道,具体多
少个由 JSQR 的 JL[2:0]决定。注意:当 JL[1:0] = 3(有 4 次注入转换)时,     ADC 将按以下顺序转换通道:
    JSQ1[4:0]、JSQ2[4:0]、 JSQ3[4:0] 和 JSQ4[4:0]。
    当 JL = 2 (有 3 次注入转换) 时,ADC 将按以下顺序转换通道:
    JSQ2[4:0]、JSQ3[4:0] 和 JSQ4[4:0]。
    当 JL = 1 (有 2 次注入转换) 时,ADC 转换通道的顺序为:先是 JSQ3[4:0],而后是 JSQ4[4:0]。
    当 JL = 0(有 1 次注入转换)时, ADC 将仅转换 JSQ4[4:0] 通道。如果在转换期间修改 ADC_SQRx 或 ADC_JSQR 寄存器, 将复位当前转换并向
ADC 发送一个新的启动脉冲,以转换新选择的通道组。
(4)标号 4:触发源
    选择好输入通道,设置好转换顺序,接下来就可以开始转换。要开启 ADC转换,可以直接设置 ADC 控制寄存器 ADC_CR2 的 ADON 位为 1,即使能 ADC。当然 ADC还支持外部事件触发转换, 触发源有很多, 具体选择哪一种触发源, 由 ADC控制寄存器 2:ADC_CR2 的 EXTSEL[2:0]和 JEXTSEL[2:0]位来控制。EXTSEL[2:0]用于选择规则通道的触发源,JEXTSEL[2:0]用于选择注入通道的触发源。选定好触发源之后, 触发源是否要激活, 则由 ADC 控制寄存器 ADC_CR2 的 EXTTRIG 和JEXTTRIG 这两位来激活。
    如果使能了外部触发事件,我们还可以通过设置 ADC 控制寄存器2:ADC_CR2 的 EXTEN[1:0]和 JEXTEN[1:0]来控制触发极性,可以有 4 种状态,分别是:禁止触发检测、上升沿检测、下降沿检测以及上升沿和下降沿均检测。
(5)标号 5:ADC 时钟
    ADC 输入时钟 ADC_CLK 由 APB2 经过分频产生,最大值是 14MHz,分频因子由 RCC 时钟配置寄存器 RCC_CFGR 的位 15:14 ADCPRE[1:0]设置,可以是2/4/6/8 分频,注意这里没有 1 分频。我们知道 APB2 总线时钟为72M,而ADC最大工作频率为 14M,所以一般设置分频因子为 6,这样 ADC 的输入时钟为12M。
    ADC 要完成对输入电压的采样需要若干个 ADC_CLK 周期,采样的周期数可通过 ADC 采样时间寄存器 ADC_SMPR1 和 ADC_SMPR2 中的 SMP[2:0]位设置,ADC_SMPR2 控制的是通道 0~9, ADC_SMPR1 控制的是通道 10~17。每个通道可以分别用不同的时间采样。其中采样周期最小是 1.5 个,即如果我们要达到最快的采样,那么应该设置采样周期为 1.5 个周期,这里说的周期就是1/ADC_CLK。
    ADC 的总转换时间跟ADC 的输入时钟和采样时间有关,其公式如下:
  1. Tconv = 采样时间 + 12.5 个周期
复制代码
: n+ ?! c8 X7 W3 g9 E2 z( U
    其中 Tconv 为 ADC 总转换时间,当 ADC_CLK=14Mhz 的时候,并设置 1.5 个周期的采样时间,则 Tcovn=1.5+12.5=14 个周期=1us。通常经过 ADC 预分频器能分频到最大的时钟只能是 12M,采样周期设置为 1.5 个周期,算出最短的转换时间为 1.17us,这个才是最常用的。
(6)标号 6:数据寄存器
    ADC 转换后的数据根据转换组的不同,规则组的数据放在 ADC_DR 寄存器内,注入组的数据放在 JDRx内。
    因为 STM32F1 的 ADC 是 12 位转换精度,而数据寄存器是 16 位,所以 ADC在存放数据的时候就有左对齐和右对齐区分。如果是左对齐,AD 转换完成数据存放在 ADC_DR 寄存器的[4:15]位内;如果是右对齐,则存放在 ADC_DR 寄存器的[0:11]位内。具体选择何种存放方式, 需通过 ADC_CR2 的 11 位 ALIGN 设置。
    在规则组中,含有 16 路通道,对应着存放规则数据的寄存器只有 1 个,如果使用多通道转换,那么转换后的数据就全部挤在 ADC_DR 寄存器内,前一个时间点转换的通道数据,就会被下一个时间点的另外一个通道转换的数据覆盖掉,所以当通道转换完成后就应该把数据取走,或者开启 DMA 模式,把数据传输到内存里面,不然就会造成数据的覆盖。最常用的做法就是开启 DMA 传输。如果没有使用 DMA 传输, 我们一般通过 ADC 状态寄存器 ADC_SR 获取当前 ADC 转换的进度状态,进而进行程序控制。
    而在注入组中,最多含有 4 路通道,对应着存放注入数据的寄存器正好有4个,不会跟规则寄存器那样产生数据覆盖的问题。
(7)标号 7:中断
    当发生如下事件且使能相应中断标志位时,ADC 能产生中断。
1.转换结束(规则转换)与注入转换结束
    数据转换结束后,如果使能中断转换结束标志位,转换一结束就会产生转换结束中断。
2.模拟看门狗事件
    当被 ADC 转换的模拟电压低于低阈值或者高于高阈值时,就会产生中断,
前提是我们开启了模拟看门狗中断,其中低阈值和高阈值由 ADC_LTR 和ADC_HTR 设置。
3.DMA 请求
    规则和注入通道转换结束后,除了产生中断外,还可以产生 DMA 请求,把转换好的数据直接存储在内存里面。要注意的是只有 ADC1 和 ADC3 可以产生DMA 请求。有关 DMA 请求需要配合《STM32F10x 中文参考手册》 DMA 控制器这一章节来学习。一般我们在使用 ADC 的时候都会开启 DMA 传输。
    我们知道 STM32F1 ADC转换模式有单次转换与连续转换区分。在单次转换模式下,ADC 执行一次转换。可以通过 ADC_CR2 寄存器的SWSTART 位(只适用于规则通道)启动,也可以通过外部触发启动(适用于规则通道和注入通道),这时 CONT 位为 0。以规则通道为例,一旦所选择的通道转换完成,转换结果将被存在 ADC_DR 寄存器中,EOC(转换结束)标志将被置位,如果设置了 EOCIE,则会产生中断。然后 ADC 将停止,直到下次启动。
在连续转换模式下,ADC 结束一个转换后立即启动一个新的转换。CONT 位
为 1 时,可通过外部触发或将 ADC_CR2 寄存器中的 SWSTRT 位置 1 来启动此模式(仅适用于规则通道)。需要注意的是:此模式无法连续转换注入通道。连续模式下唯一的例外情况是,注入通道配置为在规则通道之后自动转换(使用JAUTO 位)。
    本实验程序中我们使用的就是规则通道组的单次转换模式。由于篇幅限制,本章并没有对 ADC 相关寄存器进行介绍,大家可以参考《STM32F10x 中文参考手册》-11 模数转换器(ADC)-ADC 寄存器章节,里面有详细的讲解。如果看不懂的可以暂时放下,因为我们使用的是库函数开发。
6 H/ {( I9 C) N% Y9 j
STM32F1 ADC 配置步骤
    接下来我们介绍下如何使用库函数对 ADC 进行配置。这个也是在编写程序中必须要了解的。具体步骤如下:(ADC 相关库函数在 stm32f10x_adc.c 和stm32f10x_adc.h 文件中)
(1)使能端口时钟和ADC时钟,设置引脚模式为模拟输入
    我们知道 ADCx_IN0-ADCx_IN15 属于外部通道,每个通道都会对应芯片的一个引脚, 比如 ADC1_IN1 对应 STM32F103ZET6 的 PA1 引脚, 所以首先要使能GPIOA端口时钟和 ADC1 时钟,如下:
  1. RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_ADC1,ENABLE);
复制代码
2 Z4 o! r, U- U2 }
    然后将 PA1 引脚配置为模拟输入模式,代码如下:
  1. GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AN; //模拟输入模式
复制代码
1 r# \2 H# |0 e. A, F$ {
    GPIO 的初始化在前面很多章节中都介绍过,这里就不多说。
& e9 m1 C: q* ?8 _' x5 L% P
(2)设置 ADC 的分频因子
    开启 ADC1 时钟之后,我们就可以通过 RCC_CFGR 设置 ADC 的分频因子。分频因子要确保 ADC 的时钟( ADCCLK)不要超过 14Mhz。这个我们设置分频因子为 6, 因此 ADC 时钟为 72/6=12MHz,库函数的实现方法如下:
  1. RCC_ADCCLKConfig(RCC_PCLK2_Div6);
复制代码
2 l; q3 M9 A" Z
(3)初始化 ADC 参数,包括 ADC 工作模式、规则序列等; i% g  E( }' M) `
    我们知道要使用 ADC,需要配置 ADC 的转换模式、触发方式、数据对齐方式、规则序列等参数,这些参数是通过库函数 ADC_Init 函数实现。函数原型如下:
  1. void ADC_Init(ADC_TypeDef* ADCx, ADC_InitTypeDef* ADC_InitStruct);
复制代码
% x  O! J0 o2 `( g
    函数中第一个参数是用来选择 ADC,例如 ADC1;第二个参数是一个结构体指针变量,结构体类型是 ADC_InitTypeDef,其内包含了 ADC 初始化的成员变量。
    下面我们就来简单介绍下这个结构体:
  1. typedef struct- r, H  e0 M: @  b0 R* j# n( f
  2. {
    4 ~  X  V$ Z) l, Z
  3.   uint32_t ADC_Mode; // ADC 工作模式选择
    3 N4 ~* `. q# Q3 q6 E
  4.   FunctionalState ADC_ScanConvMode; /* ADC 扫描(多通道)或者单次(单通道)模式选择 */
    % f% t0 Y' a! a4 I8 t! S: N
  5.   FunctionalState ADC_ContinuousConvMode; // ADC 单次转换或者连续转换选择
    4 D2 ]) v& U3 s4 J% p. A4 h# Q+ a
  6.   uint32_t ADC_ExternalTrigConv; // ADC 转换触发信号选择
    1 g7 T4 m3 A9 f8 B: o% I' x* y8 g
  7.   uint32_t ADC_DataAlign; // ADC 数据寄存器对齐格式+ S: o, W6 ]( \+ k; V( v
  8.   uint8_t ADC_NbrOfChannel; // ADC 采集通道数
    + {) e7 }# {& x, o) M5 M8 f
  9. } ADC_InitTypeDef;
复制代码

% }! T, C' Q6 s0 Q& ~! ]
    ADC_Mode:ADC 模式选择,有独立模式、双重模式,在双重模式下还有很多细分模式可选,具体由 ADC_CR1UALMOD 位配置。1 l8 r" p/ w6 i' K, H
    ADC_ScanConvMode:ADC 扫描模式选择。可选参数为 ENABLE 或DISABLE,用来设置是否打开 ADC 扫描模式。如果是单通道 AD 转换,选择 DISABLE;如果是多通道AD 转换,选择 ENABLE。
    ADC_ContinuousConvMode:ADC 连续转换模式选择。可选参数为ENABLE 或DISABLE,用来设置是连续转换还是单次转换模式。如果为 ENABLE,则选择连续转换模式;如果为 DISABLE,则选择单次转换模式,转换一次后停止,需要手动控制才能重新启动转换。
    ADC_ExternalTrigConv:ADC 外部触发选择。ADC 外部触发条件有很多,在前面介绍框图时已列出,根据需要选择对应的触发条件,通常我们使用软件自动触发,所以此成员可以不用配置。
    ADC_DataAlign :ADC 数 据 对 齐 方 式 。可 选 参 数 为 右 对 齐
ADC_DataAlign_Right 和左对齐 ADC_DataAlign_Left。
    ADC_NbrOfChannel:AD 转换通道数目,根据实际设置。具体的通道数和通道的转换顺序是配置规则序列或注入序列寄存器。
    了解结构体成员功能后,就可以进行配置,本章实验 ADC 初始化配置代码如下:
  1. ADC_InitTypeDef ADC_InitStructure;
    1 `9 O2 Q, r" w9 Y0 ^7 [) P
  2. ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
    7 Q8 \) o' {) k- ?
  3. ADC_InitStructure.ADC_ScanConvMode = DISABLE;//非扫描模式3 ?5 [6 ^. y  |' \) O& G0 C. S0 q
  4. ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;//关闭连续转换
    3 U! d5 ?: p. V. N3 e' d. _2 Q
  5. ADC_InitStructure.ADC_ExternalTrigConv =  ^* _+ o: M$ x6 x6 D
  6. ADC_ExternalTrigConv_None;//禁止触发检测,使用软件触发0 z7 @+ [% c  {2 W( s, T$ X1 |1 R
  7. ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;//右对齐" w! s7 v0 H+ M0 d, h# Y" t
  8. ADC_InitStructure.ADC_NbrOfChannel = 1;//1 个转换在规则序列中 也就是只转换规则序列 1
    8 ?" _2 ^( b( A# W: j
  9. ADC_Init(ADC1, &ADC_InitStructure);//ADC 初始化
复制代码

7 G# i2 ^2 E" m# u+ z( C
(4)使能 ADC 并校准$ q' W; |: c1 B$ @5 \0 T# s: i
    前面几个步骤已经将ADC配置好,但还不能正常使用,只有开启 ADC并且复位校准了才能让它正常工作,开启 ADC 的库函数如下:
  1. void ADC_Cmd(ADC_TypeDef* ADCx, FunctionalState NewState);
复制代码

. ~6 r8 x$ C7 n9 e! {
    开启 ADC1 代码如下:
  1. ADC_Cmd(ADC1, ENABLE);//开启 AD 转换器
复制代码
3 r$ \$ i: G! D+ ]) P7 e; N( X1 \
    执行复位校准的方法是:
  1. ADC_ResetCalibration(ADC1);
复制代码
: ]0 E) g+ C  z1 x" h
    执行 ADC 校准的方法是:
  1. ADC_StartCalibration(ADC1); //开始指定 ADC1 的校准状态
复制代码
$ }. W" }  U* H4 q
    记住,每次进行校准之后要等待校准结束。这里是通过获取校准状态来判
( W' u: r" c/ V/ C( G$ f
断是否校准是否结束。下面我们一一列出复位校准和 AD 校准的等待结束方法:
  1. while(ADC_GetResetCalibrationStatus(ADC1)); //等待复位校准结束8 [5 W. W3 o7 T
  2. while(ADC_GetCalibrationStatus(ADC1)); //等待校准结束
复制代码

# y0 ^' e/ J+ O6 y
(5)读取 ADC 转换值5 N4 c( ^% M5 \2 M$ p$ B  e. B
    通过上面几步配置,ADC 就算准备好了,接下来我们要做的就是设置规则序列里面的通道,采样顺序以及通道的采样周期,然后启动 ADC 转换。在转换结束后,读取转换结果值就可以了。
    设置规则序列通道以及采样周期的库函数是:
  1. void ADC_RegularChannelConfig(ADC_TypeDef* ADCx, uint8_t! a! a, _! q( {- B, i. @
  2. ADC_Channel,uint8_t Rank, uint8_t ADC_SampleTime);
复制代码

# v% \- ]) m  I8 |, ?& t: m& }
    参数 1 用来选择 ADC,参数 2 用来选择规则序列里面的通道,参数 3用来设置转换通道的数量,参数 4 用来设置采样周期。例如本实验中 ADC1_IN1 单次转换,采样周期为 239.5,代码如下:
  1. ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 1,ADC_SampleTime_239Cycles5 );
复制代码

1 s. H) E* W9 R2 D+ s3 ?( Z
    设置好规则序列通道及采样周期,接下来就要开启转换,由于我们采用的是软件触发,库函数如下:
  1. void ADC_SoftwareStartConvCmd(ADC_TypeDef* ADCx, FunctionalStateNewState);
复制代码

  n  O9 j/ `# `, x
    例如要开启 ADC1 转换,调用函数为:
  1. ADC_SoftwareStartConvCmd(ADC1, ENABLE); //使能指定的 ADC1 的软件转换启动功能
复制代码

$ ?- I" k4 K  S& F9 M2 @( L
    开启转换之后,就可以获取 ADC 转换结果数据,调用的库函数是:
  1. uint16_t ADC_GetConversionValue(ADC_TypeDef* ADCx);
复制代码
$ E( u/ m5 s8 x6 m
    同样如果要获取 ADC1转换结果,调用函数是:
  1. ADC_GetConversionValue(ADC1);
复制代码
7 i7 y1 {% }9 z7 ?. }+ f% B
    同时在 AD 转换中, 我们还要根据状态寄存器的标志位来获取 AD 转换的各个状态信息。获取 AD 转换的状态信息的库函数是:
  1. FlagStatus ADC_GetFlagStatus(ADC_TypeDef* ADCx, uint8_t ADC_FLAG);
复制代码

, r8 G, V, U7 i/ w3 D7 [
    例如我们要判断 ADC1 的转换是否结束,方法是:
  1. while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC ));//等待转换结束
复制代码

7 g! f: @! H$ o( g# Z
    将以上几步全部配置好后,我们就可以正常使用 ADC 执行转换操作了。
& H: q1 k" A, `$ s( k
    本实验使用到硬件资源如下:
(1)D1 指示灯
(2)串口 1
(3)ADC1_IN5
(4)电位器
    D1指示灯、串口 1 电路在前面章节都介绍过,这里就不多说,至于ADC_IN1它属于STM32F1 芯片内部的资源,对应芯片的 PA1 引脚,对于 PZ6806L开发板,该引脚直接连接到电位器上。对于 PZ6806D 开发板,该引脚连接到一个 3Pin 插针上,需要使用短接片将开发板丝印上的 A1 与 AD 短接才可检测电位器电压,这个在电容触摸按键实验章节已介绍),调节电位器即可改变电压,通过 ADC转换即可检测此电压值。电路如下图所示:
微信图片_20220627223408.png
微信图片_20220627223412.png
    D1指示灯用来提示系统运行状态,电位器 AD1 用来调节电压(0-3.3V) (注意不能在 ADC 引脚上直接连接高于 3.3V 的电压,否则可能烧坏芯片),调节电位器即可改变 ADC1_IN1输入的电压,通过串口 1 将转换的电压值打印出来。
    要实现的功能是:通过 ADC1 通道 1 采样外部电压值,将采样的 AD值和转换后的电压值通过串口打印出来,同时 D1 指示灯闪烁,提示系统正常运
行。程序框架如下:
(1)初始化 ADC1_IN1相关参数,开启 ADC1
(2)编写获取 ADC1_IN1的AD 转换值函数
(3)编写主函数
    前面介绍 ADC 配置步骤时, 就已经讲解如何初始化 ADC。下面我们打开“ADC 模数转换实验”工程,在 APP 工程组中可以看到添加了 adc.c 文件(里面包含了 ADC1 驱动程序),在 StdPeriph_Driver 工程组中添加了stm32f10x_adc.c库文件。ADC 操作的库函数都放在 stm32f10x_adc.c和stm32f10x_adc.h 文件中,所以使用到 ADC 就必须加入 stm32f10x_adc.c文件,同时还要包含对应的头文件路径。
    这里我们分析几个重要函数,其他部分程序大家可以打开工程查看。

; C1 Y& U6 @  w
ADC1 初始化函数
    要使用 ADC,我们必须先对它进行配置。ADC1_IN1 初始化代码如下:
  1. /****************************************************************
    * Y' N& ^/ R4 E- h" }3 P
  2. * 函 数 名 : ADCx_Init
    % H# c  n% B6 c- a; s$ a
  3. * 函数功能 : ADC 初始化1 f. r+ Z7 r  r' \2 E
  4. * 输 入 : 无
    # T7 f/ {: Q* K: h: F: y
  5. * 输 出 : 无
    / B! q% s5 n$ d$ I( Z7 ~" d( E" m$ z
  6. *****************************************************************/
    / K9 F2 k% |! n, P
  7. void ADCx_Init(void)
    , g* o6 C  e3 Y
  8. {9 |. j$ m. q$ v  w7 n/ N1 d
  9.   GPIO_InitTypeDef GPIO_InitStructure; //定义结构体变量
    # ?: j) C$ [- W6 Z( T* o  R; `
  10.   ADC_InitTypeDef ADC_InitStructure;
    " U: S; d# l3 }" ]
  11.   RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_ADC1,ENABLE);5 E/ ?% F/ y- N6 Z! N% I" d4 U
  12.   RCC_ADCCLKConfig(RCC_PCLK2_Div6);// 设 置 ADC 分 频 因 子 672M/6=12,ADC 最大时间不能超过14M9 M6 Q  ]) V6 S2 Z) c! k. J/ b
  13.   GPIO_InitStructure.GPIO_Pin=GPIO_Pin_1;//ADC
    7 v" {! E0 j9 `" }% G0 _: z) E
  14.   GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AIN; //模拟输入, `. s3 }8 \! p) s
  15.   GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;: q" ]; q9 e4 w8 z  @! [
  16.   GPIO_Init(GPIOA,&GPIO_InitStructure);1 C" s; p$ w5 [% e3 a6 o" T9 q  E
  17.   ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
    ! ]8 e3 }( Y8 z7 M4 R( \
  18.   ADC_InitStructure.ADC_ScanConvMode = DISABLE;//非扫描模式
      \& T, H% \7 ~& f$ o
  19.   ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;//关闭连续转换: \7 ]. ]% K# [
  20.   ADC_InitStructure.ADC_ExternalTrigConv =ADC_ExternalTrigConv_None;//禁止触发检测,使用软件触发
    ' d1 C; o8 u9 X0 O6 \) D1 G; C
  21.   ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;//右对齐5 z5 ~* n( m6 U
  22.   ADC_InitStructure.ADC_NbrOfChannel = 1;//1 个转换在规则序列中也就是只转换规则序列 1
    3 R' d/ O0 h+ I. _9 w
  23.   ADC_Init(ADC1, &ADC_InitStructure);//ADC 初始化
    " B, K+ b( _# F
  24.   ADC_Cmd(ADC1, ENABLE);//开启 AD 转换器
    1 P7 f0 k6 X' N  j8 s
  25.   ADC_ResetCalibration(ADC1);//重置指定的 ADC 的校准寄存器
    ' ~$ _/ L# F( a9 q- n
  26.   while(ADC_GetResetCalibrationStatus(ADC1));//获取 ADC 重置校准寄存器的状态
    8 G) w1 |, ]+ H2 J! K
  27.   ADC_StartCalibration(ADC1);//开始指定 ADC 的校准状态
    4 l' T% f! _7 h- [" [
  28.   while(ADC_GetCalibrationStatus(ADC1));//获取指定 ADC的校准程序" p8 G2 ]0 U8 W  C( |
  29.   ADC_SoftwareStartConvCmd(ADC1, ENABLE);//使能或者失能指定的ADC的软件转换启动功能0 ^9 v0 z4 v; r9 E: v! @1 b( Z
  30. }
复制代码
3 H  ]6 d& B* @9 z+ Q1 R& E
    在 ADCx_Init()函数中,首先使能 GPIOA 端口和 ADC1 时钟,并配置 PA1 为模拟输入模式。然后初始化 ADC_InitStructure 结构体。最后开启 ADC1。这一过程在前面步骤介绍中已经提了。其实如果你会使用 ADC1_IN1,那么其他 ADC通道都一样。. A4 P4 E: y& T$ x

1 ?; Y2 K3 I% ^( r1 L) d
获取 ADC1_IN1 转换值函数
    配置好 ADC1_IN1 后,就可以开始读取 AD 转换值了,代码如下:
  1. /*************************************************************************
    & O+ M7 b- b; y* ^+ E* }2 `
  2. * 函 数 名 : Get_ADC_Value7 u  ]) |: b2 I0 v
  3. * 函数功能 : 获取通道 ch 的转换值,取 times 次,然后平均
    7 b7 G2 H$ B' B' c8 F3 P
  4. * 输 入 : ch:通道编号 times:获取次数
    4 N2 Q8 s+ Z9 P; T4 k8 k
  5. * 输 出 : 通道 ch 的 times 次转换结果平均值
    ! L2 `6 C# s1 g0 r1 R7 j) _% c* R
  6. *****************************************************************/; k# K, [. I% K1 {  j( O
  7. u16 Get_ADC_Value(u8 ch,u8 times). ^# M: z/ j. q" J4 @* }
  8. {( L* ?9 R% x' a! o, P) \% Y
  9.   u32 temp_val=0;
    1 u6 j3 @, o" I3 M! t
  10.   u8 t;7 t% e$ n5 t6 j7 E6 v- P
  11.   //设置指定 ADC的规则组通道,一个序列,采样时间
    ) e( s* O2 R+ C9 _& d- Z- f
  12.   ADC_RegularChannelConfig(ADC1, ch, 1,
    8 {! Y" E/ G# p* I$ j1 Y; q2 k# L
  13.   ADC_SampleTime_239Cycles5); //ADC1,ADC 通道,239.5 个周期,提高采样时间可以提高精确度3 h% H% K. f8 K5 G5 l: R$ X9 ^
  14.   for(t=0;t<times;t++)) z3 [7 e. Z; P
  15.   {2 I* @5 D9 ?1 ~: V# K
  16.     ADC_SoftwareStartConvCmd(ADC1, ENABLE);//使能指定的 ADC1 的软件转换启动功能
    : u0 F) R" T' p' _* d" m# Z
  17.     while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC ));//等待转换结束& p# y& K+ M% l2 z7 w
  18.     temp_val+=ADC_GetConversionValue(ADC1);
    ; Q# K7 h, F8 Q+ j8 @0 W1 a4 I
  19.     delay_ms(5);
    2 m. t5 m7 u8 A* U
  20.   }
    ; Q1 T" }3 s6 P: _
  21.   return temp_val/times;
    + [/ b8 z/ u. {) c& _0 h# E8 `# l7 K
  22. }
复制代码

$ d- }2 s8 f+ Q4 E% ~% I
    Get_ADC_Value 函数有两个参数,ch 表示 ADC1 转换的通道,times 表示转换 次 数 , 用 于 取 平 均 , 提 高 数 据 准 确 性 。函 数 内 首 先 调 用ADC_RegularChannelConfig 函数,指定 ADC规则组通道、规则序号、采样周期。
! I' ~; Z  p; z0 t5 `
    然后调用 ADC_SoftwareStartConvCmd 函数启动 ADC1 转换,等待转换完成后,读取 ADC1的转换值, 最后将 AD 转换值取平均后返回。由于ADC1 最大为 12位精度,所以返回值类型为 u16 即可。

" n3 v& Z  V7 A/ D
主函数
    编写好 ADC1初始化和获取转换值函数后,接下来就可以编写主函数了,代码如下:
  1. /****************************************************************! f  Z7 R+ a- W$ ?
  2. * 函 数 名 : main
    3 u+ `& v1 y& t7 l( w
  3. * 函数功能 : 主函数8 U' e* q7 N8 Z4 k6 O! t6 I
  4. * 输 入 : 无
      }: V* @) e% F8 j0 n/ w
  5. * 输 出 : 无" b4 u. ?* H7 m
  6. *****************************************************************/
    . ]% Z. X4 o! n. N/ E+ k1 ^1 H( P
  7. int main()/ Y( Z# t6 y. n
  8. {/ ?1 @1 a& x% l- ?1 B/ }
  9.   u8 i=0;7 h" m6 ^3 h/ d! E& I8 `5 A  W" u
  10.   u16 value=0;
    : [; x0 o7 H$ g8 c
  11.   float vol;
    , Y" p* O* ^2 I6 {6 T& C
  12.   SysTick_Init(72);! M' G  z( x: D% l, W0 `) h( w# J5 x
  13.   NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //中断优先级分组 分2 组3 d9 t: r4 x3 I/ Y
  14.   LED_Init();$ S$ b' |$ k& r: a& a& u
  15.   USART1_Init(9600);
    " ^0 ^- f; m* V: z4 N. ^
  16.   ADCx_Init();: }2 k% ~' M- M+ |: q! I
  17.   while(1)  q) d% t% G5 B% v5 E$ Q
  18.   {
    5 h+ s( q# E* M( d; m$ \: V  r% J% s
  19.     i++;
    9 b8 [# B  d; p
  20.     if(i%20==0)- z( y7 Q  n( k& w+ f, O% H) g
  21.     {
    2 B- X- N) c( \; Q" r( |% P
  22.       led1=!led1;8 _7 @& v) k7 X: g
  23.     }* q/ `2 O) {6 i7 b1 O; O# o0 `5 z; k
  24.     if(i%50==0)
    7 m& S1 G3 n% y$ S5 \' `/ ?
  25.     {8 i$ k, e, ~- v7 q: Z
  26.       value=Get_ADC_Value(ADC_Channel_1,20);
    $ m  z2 \5 t) e
  27.       printf("检测AD 值为:%d\r\n",value);9 V) a* T  ~) a+ d, w
  28.       vol  =(float)value*(3.3/4096);, o4 T, o- m5 }( T0 ^% u: ]; }
  29.       printf("检测电压值为:%.2fV\r\n",vol);
    * d0 v( k2 B$ Z
  30.     }7 h+ \% A5 C# `, H
  31.     delay_ms(10);+ X. y' _6 y5 |: c
  32.   }! E# s' j; e2 X4 L9 Z3 z2 G. H  w
  33. }
复制代码

+ `) i- `' i( ^% z0 y* U$ m
    主函数实现的功能很简单,首先调用之前编写好的硬件初始化函数,包括SysTick系统时钟, 中断分组, LED初始化等。然后调用我们前面编写的ADCx_Init函数初始化 ADC1_IN1,最后进入 while 循环,间隔 500ms 读取一次通道 1 的转换值,将 AD 转换值 value*(3.3/4096)转换为电压值输出,这里为什么要*(3.3/4096)?因为我们使用的ADC1 为 12 位转换精度,最大值为 2^12 即4096,而 ADC的参考电压 VREF+为3.3V, 所以知道 AD 转换值就可以计算对应的电压值,这里要注意,最后计算结果要强制转换为浮点类型,否则得不到小数点后面的数据。D1指示灯会间隔 200ms闪烁,提示系统正常运行。4 M7 b0 x- J$ `$ F
    将工程程序编译后下载到开发板内,可以看到 D1 指示灯不断闪烁,表示程序正常运行。当调节电位器时,获取的 AD 转换值和电压值将发生变化,并通过串口打印出来。如果想在串口调试助手上看到输出信息,可以打开“串口调试助手”,首先勾选下标号 1 DTR框,然后再取消勾选。这是因为此串口助手启动时会把系统复位住,通过 DTR 状态切换下即可。然后设置好波特率等参数后,串口助手上即会收到 printf 发送过来的信息。如下图所示:

+ t& J* @$ ]9 X1 R% ]
微信图片_20220627223352.jpg

: b5 b' P& b) u/ a
微信图片_20220627223350.jpg
; y; f( |8 y. P. x9 z
    实验说明:如果之前下载待机唤醒实验程序后,若使用普中 ARM 仿真器下载此程序会出现报警,这是因为处于低功耗模式时,所有外设时钟都已关闭,所以需要在下载程序前先复位下系统。

( c8 u4 m/ y& ]- }' G3 a: ?/ j
赞 收藏 评论0 发布时间:2022-6-28 18:33

举报

0个回答

所属标签

相似技术帖

官网相关资源

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
人形机器人运动控制、感知与智能配电
半导体创新技术与应用方向
EE架构与软件定义汽车
12V/48V 汽车智能配电(SPD)
区域控制单元(ZCU)与分区架构
关注我们
st-img 微信公众号
st-img 手机版