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

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

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

; a! d2 @# C( }1 R8 W9 o
STM32F1 ADC 结构框图
    STM32F1 ADC 拥有这么多功能,是由 ADC 内部结构所决定。要更好的理解STM32F1 的 ADC,就需要了解它内部的结构。如下图所示:(大家也可以查看《STM32F10x 中文参考手册》-11 模数转换器(ADC)章-ADC 功能说明)。
微信图片_20220627223433.jpg
5 X+ h- T) I1 E$ T% j
微信图片_20220627223429.jpg
/ _( T& \5 K# \& E. W* q
    我们把 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。

) @" b6 W- E2 U/ g$ z" X. r
微信图片_20220627223420.jpg
% {/ D7 P# O- \, ^/ o. N- V' l8 g
(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 个周期
复制代码

9 w1 ^/ N$ |6 J( K2 Q
    其中 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 寄存器章节,里面有详细的讲解。如果看不懂的可以暂时放下,因为我们使用的是库函数开发。

% w  G" b" L; X0 }, b) q" E! ^9 e
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);
复制代码

+ r  c& ^! ]$ @4 m7 r
    然后将 PA1 引脚配置为模拟输入模式,代码如下:
  1. GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AN; //模拟输入模式
复制代码
% U6 u9 W' K. }9 M. b. _
    GPIO 的初始化在前面很多章节中都介绍过,这里就不多说。& M4 e; B9 @4 L+ u: M/ V
(2)设置 ADC 的分频因子
    开启 ADC1 时钟之后,我们就可以通过 RCC_CFGR 设置 ADC 的分频因子。分频因子要确保 ADC 的时钟( ADCCLK)不要超过 14Mhz。这个我们设置分频因子为 6, 因此 ADC 时钟为 72/6=12MHz,库函数的实现方法如下:
  1. RCC_ADCCLKConfig(RCC_PCLK2_Div6);
复制代码
  D% v1 T" s* r8 x9 Z7 v1 E
(3)初始化 ADC 参数,包括 ADC 工作模式、规则序列等
" x$ ~8 U4 ~9 p; c
    我们知道要使用 ADC,需要配置 ADC 的转换模式、触发方式、数据对齐方式、规则序列等参数,这些参数是通过库函数 ADC_Init 函数实现。函数原型如下:
  1. void ADC_Init(ADC_TypeDef* ADCx, ADC_InitTypeDef* ADC_InitStruct);
复制代码

; d2 W5 o9 z  O
    函数中第一个参数是用来选择 ADC,例如 ADC1;第二个参数是一个结构体指针变量,结构体类型是 ADC_InitTypeDef,其内包含了 ADC 初始化的成员变量。
    下面我们就来简单介绍下这个结构体:
  1. typedef struct  o2 r2 [) a& \( ~
  2. {
    2 }9 P  |7 `8 R0 Z$ P: x9 j
  3.   uint32_t ADC_Mode; // ADC 工作模式选择4 n3 ~; }8 h- [2 i0 ]. [
  4.   FunctionalState ADC_ScanConvMode; /* ADC 扫描(多通道)或者单次(单通道)模式选择 */5 D$ p# Q' _, ]5 I) A7 _4 u
  5.   FunctionalState ADC_ContinuousConvMode; // ADC 单次转换或者连续转换选择. x; X; {% C4 W. v' ^- h+ ]
  6.   uint32_t ADC_ExternalTrigConv; // ADC 转换触发信号选择
    . C+ ~& ^- l  v) {# e
  7.   uint32_t ADC_DataAlign; // ADC 数据寄存器对齐格式
    1 U, f/ z- H: [8 x
  8.   uint8_t ADC_NbrOfChannel; // ADC 采集通道数
    * w, H7 q0 O* m" y3 u7 b0 u
  9. } ADC_InitTypeDef;
复制代码

. C6 z, u! a! [9 g+ Y! @5 k# {
    ADC_Mode:ADC 模式选择,有独立模式、双重模式,在双重模式下还有很多细分模式可选,具体由 ADC_CR1UALMOD 位配置。
6 ~0 R) U4 n$ g& ~" k5 O" w9 S
    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;
    - Z: n* j+ m/ G7 `8 ]! I
  2. ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
    6 f( c; p- O( H# @
  3. ADC_InitStructure.ADC_ScanConvMode = DISABLE;//非扫描模式
    6 i! j7 e. s9 x! A4 I
  4. ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;//关闭连续转换
    - L( a: i  _# T7 c0 V  K, ^
  5. ADC_InitStructure.ADC_ExternalTrigConv =
    2 Y: Z% l$ w: F, q9 G4 J1 _) `
  6. ADC_ExternalTrigConv_None;//禁止触发检测,使用软件触发6 k$ x$ ]" o, _3 x* g! l; o; B) C+ C
  7. ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;//右对齐
    * u  `' l  h4 P
  8. ADC_InitStructure.ADC_NbrOfChannel = 1;//1 个转换在规则序列中 也就是只转换规则序列 15 Z& H! v: D2 _- }" Z; I4 s6 h
  9. ADC_Init(ADC1, &ADC_InitStructure);//ADC 初始化
复制代码

$ ]8 l5 _) v" j. W
(4)使能 ADC 并校准
6 x5 I: ~! b% h' y) `. N8 q) x0 y6 {
    前面几个步骤已经将ADC配置好,但还不能正常使用,只有开启 ADC并且复位校准了才能让它正常工作,开启 ADC 的库函数如下:
  1. void ADC_Cmd(ADC_TypeDef* ADCx, FunctionalState NewState);
复制代码
$ T3 `6 I7 A( ~$ @& r
    开启 ADC1 代码如下:
  1. ADC_Cmd(ADC1, ENABLE);//开启 AD 转换器
复制代码
9 c6 _* C  i! Z+ l
    执行复位校准的方法是:
  1. ADC_ResetCalibration(ADC1);
复制代码

+ f1 Y- a4 I* {4 Y/ R5 J
    执行 ADC 校准的方法是:
  1. ADC_StartCalibration(ADC1); //开始指定 ADC1 的校准状态
复制代码

) E6 E6 P+ {2 h' I
    记住,每次进行校准之后要等待校准结束。这里是通过获取校准状态来判$ h  T4 ^3 U6 B, y
断是否校准是否结束。下面我们一一列出复位校准和 AD 校准的等待结束方法:
  1. while(ADC_GetResetCalibrationStatus(ADC1)); //等待复位校准结束
      {5 k6 l- b6 D: c# ^7 l6 z8 N
  2. while(ADC_GetCalibrationStatus(ADC1)); //等待校准结束
复制代码
$ ?+ |- N3 n0 x. [
(5)读取 ADC 转换值+ X6 `0 C- J$ ^
    通过上面几步配置,ADC 就算准备好了,接下来我们要做的就是设置规则序列里面的通道,采样顺序以及通道的采样周期,然后启动 ADC 转换。在转换结束后,读取转换结果值就可以了。
    设置规则序列通道以及采样周期的库函数是:
  1. void ADC_RegularChannelConfig(ADC_TypeDef* ADCx, uint8_t& d2 w* z% {- {1 u& S
  2. ADC_Channel,uint8_t Rank, uint8_t ADC_SampleTime);
复制代码
& T* `* \. z' P  d* u. V/ a. q
    参数 1 用来选择 ADC,参数 2 用来选择规则序列里面的通道,参数 3用来设置转换通道的数量,参数 4 用来设置采样周期。例如本实验中 ADC1_IN1 单次转换,采样周期为 239.5,代码如下:
  1. ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 1,ADC_SampleTime_239Cycles5 );
复制代码
% ]. m  L8 d6 F
    设置好规则序列通道及采样周期,接下来就要开启转换,由于我们采用的是软件触发,库函数如下:
  1. void ADC_SoftwareStartConvCmd(ADC_TypeDef* ADCx, FunctionalStateNewState);
复制代码
- k8 c* [& t4 L* X% B7 ^
    例如要开启 ADC1 转换,调用函数为:
  1. ADC_SoftwareStartConvCmd(ADC1, ENABLE); //使能指定的 ADC1 的软件转换启动功能
复制代码

; O; B4 J/ H, J* }
    开启转换之后,就可以获取 ADC 转换结果数据,调用的库函数是:
  1. uint16_t ADC_GetConversionValue(ADC_TypeDef* ADCx);
复制代码
0 o# a1 Y9 x; c) Z7 N" u
    同样如果要获取 ADC1转换结果,调用函数是:
  1. ADC_GetConversionValue(ADC1);
复制代码
/ x/ H4 d& q5 e/ o9 ]
    同时在 AD 转换中, 我们还要根据状态寄存器的标志位来获取 AD 转换的各个状态信息。获取 AD 转换的状态信息的库函数是:
  1. FlagStatus ADC_GetFlagStatus(ADC_TypeDef* ADCx, uint8_t ADC_FLAG);
复制代码

0 z! u$ u, [& x
    例如我们要判断 ADC1 的转换是否结束,方法是:
  1. while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC ));//等待转换结束
复制代码
9 i" u: Y% E5 u2 @( Q
    将以上几步全部配置好后,我们就可以正常使用 ADC 执行转换操作了。6 ^5 c. \; _/ V7 _# y5 o. y. x
    本实验使用到硬件资源如下:
(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文件,同时还要包含对应的头文件路径。
    这里我们分析几个重要函数,其他部分程序大家可以打开工程查看。

" c2 O9 R; `( F4 @
ADC1 初始化函数
    要使用 ADC,我们必须先对它进行配置。ADC1_IN1 初始化代码如下:
  1. /****************************************************************
    / I* ^4 o7 e' R+ o* v& Z% b
  2. * 函 数 名 : ADCx_Init* O8 w: Q  I# p) k
  3. * 函数功能 : ADC 初始化: W# \# P; G& U+ Q
  4. * 输 入 : 无
    % E- q) z7 m/ u/ M
  5. * 输 出 : 无
    5 f) M+ O/ A/ k7 b8 C( F
  6. *****************************************************************/
    5 \. a2 z0 e" q( K# Z# R9 G$ _
  7. void ADCx_Init(void)
    5 c7 f, I: E5 p* |
  8. {* d1 ]8 k& }7 b
  9.   GPIO_InitTypeDef GPIO_InitStructure; //定义结构体变量
    8 A. J9 [5 {( t$ R7 B
  10.   ADC_InitTypeDef ADC_InitStructure;" u& O0 P4 Q( m; V3 `9 ^
  11.   RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_ADC1,ENABLE);' p8 H& M5 L3 m3 r  z3 m1 ~
  12.   RCC_ADCCLKConfig(RCC_PCLK2_Div6);// 设 置 ADC 分 频 因 子 672M/6=12,ADC 最大时间不能超过14M1 ^. s3 D( N( L" Q: q8 V
  13.   GPIO_InitStructure.GPIO_Pin=GPIO_Pin_1;//ADC: S. A; N! H8 L$ y- {# k! E2 Y& [
  14.   GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AIN; //模拟输入
    $ u* P; A7 h6 q  o
  15.   GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;7 t$ W) K0 H1 v
  16.   GPIO_Init(GPIOA,&GPIO_InitStructure);' m& k9 n9 Q; Q  z4 V) I' m7 E7 C
  17.   ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
    : W& A6 `1 H! W5 _
  18.   ADC_InitStructure.ADC_ScanConvMode = DISABLE;//非扫描模式! I, L0 D' n6 k5 \
  19.   ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;//关闭连续转换* u) J: E- M# o3 M. p
  20.   ADC_InitStructure.ADC_ExternalTrigConv =ADC_ExternalTrigConv_None;//禁止触发检测,使用软件触发
    2 w4 j& h0 W8 ^/ j9 n3 P. h+ b
  21.   ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;//右对齐3 }) l! p: K, j  k. z: B" g
  22.   ADC_InitStructure.ADC_NbrOfChannel = 1;//1 个转换在规则序列中也就是只转换规则序列 1
    % N* C/ k% z& X0 ?9 p
  23.   ADC_Init(ADC1, &ADC_InitStructure);//ADC 初始化6 [8 q  L# \9 s5 ~# t, J
  24.   ADC_Cmd(ADC1, ENABLE);//开启 AD 转换器' V5 I  x) {" b5 h4 P1 H
  25.   ADC_ResetCalibration(ADC1);//重置指定的 ADC 的校准寄存器  g( V9 L! J4 l, }9 ]' {3 a6 s
  26.   while(ADC_GetResetCalibrationStatus(ADC1));//获取 ADC 重置校准寄存器的状态
    & o0 U+ C0 X7 q
  27.   ADC_StartCalibration(ADC1);//开始指定 ADC 的校准状态. z7 z2 D$ i! l/ b
  28.   while(ADC_GetCalibrationStatus(ADC1));//获取指定 ADC的校准程序
    $ X) y6 W6 n4 `/ {: @, C
  29.   ADC_SoftwareStartConvCmd(ADC1, ENABLE);//使能或者失能指定的ADC的软件转换启动功能
    % g/ b9 R* q' |4 Q2 C! J
  30. }
复制代码
, q$ a% R! [$ k, l1 e4 M$ z
    在 ADCx_Init()函数中,首先使能 GPIOA 端口和 ADC1 时钟,并配置 PA1 为模拟输入模式。然后初始化 ADC_InitStructure 结构体。最后开启 ADC1。这一过程在前面步骤介绍中已经提了。其实如果你会使用 ADC1_IN1,那么其他 ADC通道都一样。
* h. T; G& e" q$ Y! L3 u

. m. f5 q% K2 Q5 g* v8 e+ i
获取 ADC1_IN1 转换值函数
    配置好 ADC1_IN1 后,就可以开始读取 AD 转换值了,代码如下:
  1. /*************************************************************************7 ?7 [2 G) d- b: C
  2. * 函 数 名 : Get_ADC_Value6 J% n6 w! _* \4 q$ ?
  3. * 函数功能 : 获取通道 ch 的转换值,取 times 次,然后平均( [% A1 [- h6 t7 T9 B  u" s4 ^
  4. * 输 入 : ch:通道编号 times:获取次数
    ; |2 a7 `; m' S7 d
  5. * 输 出 : 通道 ch 的 times 次转换结果平均值+ S+ l) ^& u. X) v9 Z
  6. *****************************************************************/3 T8 t) R0 \& s$ Q+ x% [
  7. u16 Get_ADC_Value(u8 ch,u8 times)$ H; C- V( o  @- M9 E0 q7 G, l
  8. {
    5 S6 l1 {1 O$ G5 c. I. S% z5 P: d
  9.   u32 temp_val=0;
      W% \5 V- X; m9 `  j% o
  10.   u8 t;
    1 N5 g2 L1 B  F3 f- \
  11.   //设置指定 ADC的规则组通道,一个序列,采样时间
    7 g/ O3 M" n# F
  12.   ADC_RegularChannelConfig(ADC1, ch, 1,; K9 z. t# \5 d4 A. C) K
  13.   ADC_SampleTime_239Cycles5); //ADC1,ADC 通道,239.5 个周期,提高采样时间可以提高精确度
    / A, k9 w% t0 u" T* t- m( k; q
  14.   for(t=0;t<times;t++)
    ' N- j- H0 G+ L7 ^6 J
  15.   {
    ; [( ?6 C8 @+ l7 {: k5 A
  16.     ADC_SoftwareStartConvCmd(ADC1, ENABLE);//使能指定的 ADC1 的软件转换启动功能
    0 O  m: ?" b# B+ g
  17.     while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC ));//等待转换结束
    2 T5 c; Z" p3 o- ~% c# n$ d
  18.     temp_val+=ADC_GetConversionValue(ADC1);( y1 A2 S6 }: @6 q# v% ^
  19.     delay_ms(5);
    0 B" ^/ P# Y7 K! V) R( f
  20.   }( B+ s0 Q  y; M
  21.   return temp_val/times;
    % X1 i/ L9 B0 x! R. R( G/ v
  22. }
复制代码

, @" `' n4 k8 N0 n
    Get_ADC_Value 函数有两个参数,ch 表示 ADC1 转换的通道,times 表示转换 次 数 , 用 于 取 平 均 , 提 高 数 据 准 确 性 。函 数 内 首 先 调 用ADC_RegularChannelConfig 函数,指定 ADC规则组通道、规则序号、采样周期。
$ k# W% W* T5 d5 ]+ M
    然后调用 ADC_SoftwareStartConvCmd 函数启动 ADC1 转换,等待转换完成后,读取 ADC1的转换值, 最后将 AD 转换值取平均后返回。由于ADC1 最大为 12位精度,所以返回值类型为 u16 即可。
$ h* k' ?* j2 S7 S' p* ]. F5 e2 \
主函数
    编写好 ADC1初始化和获取转换值函数后,接下来就可以编写主函数了,代码如下:
  1. /****************************************************************& m1 u4 s  i4 h4 ?, G! d$ t+ k
  2. * 函 数 名 : main
    3 [, Q4 s& j% K+ |5 o/ m2 h
  3. * 函数功能 : 主函数
    0 v3 p- F0 L$ w; U( g6 ?
  4. * 输 入 : 无, f. V7 v- y( ^
  5. * 输 出 : 无' p, v; t/ H/ s( Y/ E8 B8 L8 L. _
  6. *****************************************************************/
    1 g' [' b/ S6 U6 `
  7. int main()( {' U( T0 Z0 a$ Y2 x, `
  8. {# U# {5 ]1 A) N: `- L
  9.   u8 i=0;
    & S# R. @5 Z  n7 b
  10.   u16 value=0;# U# p" f+ _% @' M# v- j
  11.   float vol;
    2 o. D$ R4 x* q+ I& w1 h
  12.   SysTick_Init(72);
    % u  [/ O1 o: s& E' N/ F+ E
  13.   NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //中断优先级分组 分2 组# r) S0 z8 n% S( `% w
  14.   LED_Init();
    " v$ g& v# v- d
  15.   USART1_Init(9600);
    2 ~5 z, b# _3 k0 a. }2 O! ^
  16.   ADCx_Init();
    9 G- A5 [) `& b  h# t9 F# K" r
  17.   while(1)2 X) W# V: e' x% h
  18.   {
    1 [2 X! Q( H3 r; Z
  19.     i++;. t3 H0 R: F2 W! v, s: v$ `+ O
  20.     if(i%20==0)7 b) Y" Q% \, \9 }
  21.     {) J& h. Z9 [/ w
  22.       led1=!led1;1 y! G2 X6 I2 v2 ^
  23.     }4 D/ W! Q0 [; _3 L
  24.     if(i%50==0)
    5 O4 c: g9 n* ]% v4 v( G, H! p& Y
  25.     {
    & b& l* V* M( X  W+ J+ l
  26.       value=Get_ADC_Value(ADC_Channel_1,20);, Q9 _) ^$ m" ]
  27.       printf("检测AD 值为:%d\r\n",value);
    $ Y8 s2 h' m; \
  28.       vol  =(float)value*(3.3/4096);$ K3 G: o9 Q. H, S5 ~
  29.       printf("检测电压值为:%.2fV\r\n",vol);# ~5 U1 F7 J& k# F
  30.     }  r3 F2 U3 M* C& H. z  J
  31.     delay_ms(10);! t0 s7 t! U7 z% m
  32.   }
      h. W  o) a* L% o; o: Z
  33. }
复制代码

. P4 l% L& S: X
    主函数实现的功能很简单,首先调用之前编写好的硬件初始化函数,包括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闪烁,提示系统正常运行。9 n  m) t- i( H/ \
    将工程程序编译后下载到开发板内,可以看到 D1 指示灯不断闪烁,表示程序正常运行。当调节电位器时,获取的 AD 转换值和电压值将发生变化,并通过串口打印出来。如果想在串口调试助手上看到输出信息,可以打开“串口调试助手”,首先勾选下标号 1 DTR框,然后再取消勾选。这是因为此串口助手启动时会把系统复位住,通过 DTR 状态切换下即可。然后设置好波特率等参数后,串口助手上即会收到 printf 发送过来的信息。如下图所示:
2 `* y/ M' L$ f. k% }
微信图片_20220627223352.jpg
4 |1 U. D3 ]" }! t/ Q
微信图片_20220627223350.jpg
; @: ~2 y6 B7 C& a7 {
    实验说明:如果之前下载待机唤醒实验程序后,若使用普中 ARM 仿真器下载此程序会出现报警,这是因为处于低功耗模式时,所有外设时钟都已关闭,所以需要在下载程序前先复位下系统。

8 b& v  e3 x) B, C9 ]' f1 O, X
收藏 评论0 发布时间:2022-6-28 18:33

举报

0个回答

所属标签

相似分享

官网相关资源

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版