
本文我们来学**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 请求产生。 STM32F1 ADC 结构框图 STM32F1 ADC 拥有这么多功能,是由 ADC 内部结构所决定。要更好的理解STM32F1 的 ADC,就需要了解它内部的结构。如下图所示:(大家也可以查看《STM32F10x 中文参考手册》-11 模数转换器(ADC)章-ADC 功能说明)。 ![]() ![]() 我们把 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。 ![]() (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 的输入时钟和采样时间有关,其公式如下:
其中 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 寄存器章节,里面有详细的讲解。如果看不懂的可以暂时放下,因为我们使用的是库函数开发。 STM32F1 ADC 配置步骤 接下来我们介绍下如何使用库函数对 ADC 进行配置。这个也是在编写程序中必须要了解的。具体步骤如下:(ADC 相关库函数在 stm32f10x_adc.c 和stm32f10x_adc.h 文件中) (1)使能端口时钟和ADC时钟,设置引脚模式为模拟输入 我们知道 ADCx_IN0-ADCx_IN15 属于外部通道,每个通道都会对应芯片的一个引脚, 比如 ADC1_IN1 对应 STM32F103ZET6 的 PA1 引脚, 所以首先要使能GPIOA端口时钟和 ADC1 时钟,如下:
然后将 PA1 引脚配置为模拟输入模式,代码如下:
GPIO 的初始化在前面很多章节中都介绍过,这里就不多说。& M4 e; B9 @4 L+ u: M/ V (2)设置 ADC 的分频因子 开启 ADC1 时钟之后,我们就可以通过 RCC_CFGR 设置 ADC 的分频因子。分频因子要确保 ADC 的时钟( ADCCLK)不要超过 14Mhz。这个我们设置分频因子为 6, 因此 ADC 时钟为 72/6=12MHz,库函数的实现方法如下:
(3)初始化 ADC 参数,包括 ADC 工作模式、规则序列等 我们知道要使用 ADC,需要配置 ADC 的转换模式、触发方式、数据对齐方式、规则序列等参数,这些参数是通过库函数 ADC_Init 函数实现。函数原型如下:
函数中第一个参数是用来选择 ADC,例如 ADC1;第二个参数是一个结构体指针变量,结构体类型是 ADC_InitTypeDef,其内包含了 ADC 初始化的成员变量。 下面我们就来简单介绍下这个结构体:
ADC_Mode:ADC 模式选择,有独立模式、双重模式,在双重模式下还有很多细分模式可选,具体由 ADC_CR1 ![]() 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 初始化配置代码如下:
(4)使能 ADC 并校准 前面几个步骤已经将ADC配置好,但还不能正常使用,只有开启 ADC并且复位校准了才能让它正常工作,开启 ADC 的库函数如下:
开启 ADC1 代码如下:
执行复位校准的方法是:
执行 ADC 校准的方法是:
记住,每次进行校准之后要等待校准结束。这里是通过获取校准状态来判$ h T4 ^3 U6 B, y 断是否校准是否结束。下面我们一一列出复位校准和 AD 校准的等待结束方法:
$ ?+ |- N3 n0 x. [ (5)读取 ADC 转换值+ X6 `0 C- J$ ^ 通过上面几步配置,ADC 就算准备好了,接下来我们要做的就是设置规则序列里面的通道,采样顺序以及通道的采样周期,然后启动 ADC 转换。在转换结束后,读取转换结果值就可以了。 设置规则序列通道以及采样周期的库函数是:
参数 1 用来选择 ADC,参数 2 用来选择规则序列里面的通道,参数 3用来设置转换通道的数量,参数 4 用来设置采样周期。例如本实验中 ADC1_IN1 单次转换,采样周期为 239.5,代码如下:
设置好规则序列通道及采样周期,接下来就要开启转换,由于我们采用的是软件触发,库函数如下:
例如要开启 ADC1 转换,调用函数为:
开启转换之后,就可以获取 ADC 转换结果数据,调用的库函数是:
同样如果要获取 ADC1转换结果,调用函数是:
同时在 AD 转换中, 我们还要根据状态寄存器的标志位来获取 AD 转换的各个状态信息。获取 AD 转换的状态信息的库函数是:
例如我们要判断 ADC1 的转换是否结束,方法是:
将以上几步全部配置好后,我们就可以正常使用 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转换即可检测此电压值。电路如下图所示: ![]() ![]() 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文件,同时还要包含对应的头文件路径。 这里我们分析几个重要函数,其他部分程序大家可以打开工程查看。 ADC1 初始化函数 要使用 ADC,我们必须先对它进行配置。ADC1_IN1 初始化代码如下:
, q$ a% R! [$ k, l1 e4 M$ z 在 ADCx_Init()函数中,首先使能 GPIOA 端口和 ADC1 时钟,并配置 PA1 为模拟输入模式。然后初始化 ADC_InitStructure 结构体。最后开启 ADC1。这一过程在前面步骤介绍中已经提了。其实如果你会使用 ADC1_IN1,那么其他 ADC通道都一样。 获取 ADC1_IN1 转换值函数 配置好 ADC1_IN1 后,就可以开始读取 AD 转换值了,代码如下:
Get_ADC_Value 函数有两个参数,ch 表示 ADC1 转换的通道,times 表示转换 次 数 , 用 于 取 平 均 , 提 高 数 据 准 确 性 。函 数 内 首 先 调 用ADC_RegularChannelConfig 函数,指定 ADC规则组通道、规则序号、采样周期。 然后调用 ADC_SoftwareStartConvCmd 函数启动 ADC1 转换,等待转换完成后,读取 ADC1的转换值, 最后将 AD 转换值取平均后返回。由于ADC1 最大为 12位精度,所以返回值类型为 u16 即可。 $ h* k' ?* j2 S7 S' p* ]. F5 e2 \ 主函数 编写好 ADC1初始化和获取转换值函数后,接下来就可以编写主函数了,代码如下:
主函数实现的功能很简单,首先调用之前编写好的硬件初始化函数,包括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% }![]() ![]() 实验说明:如果之前下载待机唤醒实验程序后,若使用普中 ARM 仿真器下载此程序会出现报警,这是因为处于低功耗模式时,所有外设时钟都已关闭,所以需要在下载程序前先复位下系统。 |
OpenBLT移植到STM32F405开发板
为什么要先开启STM32外设时钟?
【STM32MP157】从ST官方例程中分析RPMsg-TTY/SDB核间通信的使用方法
【经验分享】STM32实例-RTC实时时钟实验④-获取RTC时间函数与中断服务函数
STM32 以太网 MAC Loopback 的实现
刘氓兔的64位入门挑战【1】——MP257芯片下单和硬件准备
刘氓兔的64位入门挑战【0】——MP257选型
STM32功能安全设计包,助力产品功能安全认证
基于STM32启动过程startup_xxxx.s文件经验分享
HRTIM 指南