1.1 ADC工作原理

12位ADC是一种逐次逼近型模拟数字转换器。它有多达18个通道,可测量16个外部和2个内部信号源。各通道的A/D转换可以单次、连续、扫描或间断模式执行。ADC的结果可以左对齐或右对齐方式存储在16位数据寄存器中。模拟看门狗特性允许应用程序检测输入电压是否超出用户定义的高/低阀值。ADC 的输入时钟不得超过14MHz,它是由PCLK2经分频产生。

《嵌入式-STM32开发指南》第二部分 基础篇 - 第8章 模拟输入输出-ADC_STM32


图1 ADC框图


1.1.1 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请求产生。



表1 ADC引脚

《嵌入式-STM32开发指南》第二部分 基础篇 - 第8章 模拟输入输出-ADC_ADC_02

【注】VDDA和VSSA应该分别连接到VDD和VSS。




1.1.2 ADC通道选择


表2 ADC对应通道

《嵌入式-STM32开发指南》第二部分 基础篇 - 第8章 模拟输入输出-ADC_数据_03

STM32 将 ADC 的转换分为 2 个通道组:规则通道组和注入通道组。规则通道相当于你正常运行的程序,而注入通道呢,就相当于中断。在你程序正常执行的时候,中断是可以打断你的执行的。同这个类似,注入通道的转换可以打断规则通道的转换, 在注入通道被转换完成之后,规则通道才得以继续转换。

通过一个形象的例子可以说明: 假如你在家里的院子内放了 5 个温度探头,室内放了 3 个温度探头; 你需要时刻监视室外温度即可,但偶尔你想看看室内的温度;因此你可以使用规则通道组循环扫描室外的 5 个探头并显示 AD 转换结果,当你想看室内温度时,通过一个按钮启动注入转换组(3 个室内探头)并暂时显示室内温度,当你放开这个按钮后,系统又会回到规则通道组继续检测室外温度。从系统设计上,测量并显示室内温度的过程中断了测量并显示室外温度的过程,但程序设计上可以在初始化阶段分别设置好不同的转换组,系统运行中不必再变更循环转换的配置,从而达到两个任务互不干扰和快速切换的结果。可以设想一下,如果没有规则组和注入组的划分,当你按下按钮后,需要从新配置 AD 循环扫描的通道,然后在释放按钮后需再次配置 AD 循环扫描的通道。

上面的例子因为速度较慢,不能完全体现这样区分(规则通道组和注入通道组)的好处,但在工业应用领域中有很多检测和监视探头需要较快地处理,这样对 AD 转换的分组将简化事件处理的程序并提高事件处理的速度。

STM32 其 ADC 的规则通道组最多包含 16 个转换,而注入通道组最多包含 4 个通道。关于这两个通道组的详细介绍,请参考《STM32 参考手册的》,我们这里就不在一一列举了。

● 规则组由多达16个转换组成。规则通道和它们的转换顺序在ADC_SQRx寄存器中选择。规则组中转换的总数应写入ADC_SQR1寄存器的L[3:0]位中。

● 注入组由多达4个转换组成。注入通道和它们的转换顺序在ADC_JSQR寄存器中选择。注入组里的转换总数目应写入ADC_JSQR寄存器的L[1:0]位中。

如果ADC_SQRx或ADC_JSQR寄存器在转换期间被更改,当前的转换被清除,一个新的启动脉冲将发送到ADC 以转换新选择的组。

温度传感器和通道ADC1_IN16相连接,内部参照电压VREFINT和ADC1_IN17相连接。可以按注入或规则通道对这两个内部通道进行转换。

【注意】温度传感器和VREFINT只能出现在主ADC1 中。

1.1.3 ADC转换模式

 单次转换模式

单次转换模式下,ADC只执行一次转换。该模式既可通过设置ADC_CR2 寄存器的ADON位(只适用于规则通道)启动也可通过外部触发启动(适用于规则通道或注入通道),这时CONT位为0 。一旦选择通道的转换完成:

● 如果一个规则通道被转换:

─ 转换数据被储存在16位ADC_DR寄存器中

─ EOC(转换结束)标志被设置

─ 如果设置了EOCIE,则产生中断。

● 如果一个注入通道被转换:

─ 转换数据被储存在16位的ADC_DRJ1寄存器中

─ JEOC(注入转换结束)标志被设置

─ 如果设置了JEOCIE位,则产生中断。然后ADC停止。

 连续转换模式

在连续转换模式中,当前面ADC转换一结束马上就启动另一次转换。此模式可通过外部触发启动或通过设置ADC_CR2寄存器上的ADON位启动,此时CONT位是1。 每个转换后:

● 如果一个规则通道被转换:

─ 转换数据被储存在16位的ADC_DR寄存器中

─ EOC(转换结束)标志被设置

─ 如果设置了EOCIE,则产生中断。

● 如果一个注入通道被转换:

─ 转换数据被储存在16位的ADC_DRJ1寄存器中

─ JEOC(注入转换结束)标志被设置

─ 如果设置了JEOCIE位,则产生中断。

 扫描模式

此模式用来扫描一组模拟通道。

扫描模式可通过设置ADC_CR1寄存器的SCAN位来选择。一旦这个位被设置,ADC扫描所有被ADC_SQRX 寄存器(对规则通道)或ADC_JSQR(对注入通道)选中的所有通道。在每个组的每个通道上执行单次转换。在每个转换结束时,同一组的下一个通道被自动转换。如果设置了CONT位,转换不会在选择组的最后一个通道上停止,而是再次从选择组的第一个通道继续转换。

如果设置了DMA位,在每次EOC后,DMA控制器把规则组通道的转换数据传输到SRAM 中。而注入通道转换的数据总是存储在ADC_JDRx寄存器中。

 间断模式

1)规则组

此模式通过设置ADC_CR1 寄存器上的DISCEN位激活。它可以用来执行一个短序列的n次转换(n<=8),此转换是ADC_SQRx寄存器所选择的转换序列的一部分。数值n由ADC_CR1寄存器的DISCNUM[2:0]位给出。

一个外部触发信号可以启动ADC_SQRx 寄存器中描述的下一轮n次转换,直到此序列所有的转换完成为止。总的序列长度由ADC_SQR1寄存器的L[3:0]定义。

举例: n=3,被转换的通道 = 0 、1、2、3、6、7、9、10

第一次触发:转换的序列为 0 、1、2

第二次触发:转换的序列为 3 、6、7

第三次触发:转换的序列为 9 、10,并产生EOC事件

第四次触发:转换的序列 0 、1、2

注意:

当以间断模式转换一个规则组时,转换序列结束后不自动从头开始。

当所有子组被转换完成,下一次触发启动第一个子组的转换。在上面的例子中,第四次触发重新转换第一子组的通道 0 、1和2。

2)注入组

此模式通过设置ADC_CR1 寄存器的JDISCEN位激活。在一个外部触发事件后,该模式按通道顺序逐个转换ADC_JSQR寄存器中选择的序列。

一个外部触发信号可以启动ADC_JSQR寄存器选择的下一个通道序列的转换,直到序列中所有的转换完成为止。总的序列长度由ADC_JSQR寄存器的JL[1:0]位定义。

例子: n=1,被转换的通道 = 1 、2、3

第一次触发:通道1被转换

第二次触发:通道2被转换

第三次触发:通道3被转换,并且产生EOC和JEOC事件

第四次触发:通道1被转换

【注意】


1 当完成所有注入通道转换,下个触发启动第1个注入通道的转换。在上述例子中,第四个触发重新转换第1个注入通道1。
2 不能同时使用自动注入和间断模式。
3 必须避免同时为规则和注入组设置间断模式。间断模式只能作用于一组转换。




1.2 ADC寄存器描述

我们介绍一下我们执行规则通道的单次转换,需要用到的 ADC 寄存器。第一个要介绍的是 ADC 控制寄存器(ADC_CR1 和 ADC_CR2)。 ADC_CR1 的各位描述如图2所示。

《嵌入式-STM32开发指南》第二部分 基础篇 - 第8章 模拟输入输出-ADC_扫描模式_04


图2 ADC_CR1 寄存器各位描述

这里我们不再详细介绍每个位,而是抽出几个我们本章要用到的位进行针对性的介绍,详细的说明及介绍,请参考《STM32 参考手册》。

ADC_CR1 的 SCAN 位,该位用于设置扫描模式,由软件设置和清除,如果设置为 1,则使用扫描模式,如果为 0,则关闭扫描模式。在扫描模式下,由 ADC_SQRx 或 ADC_JSQRx 寄存器选中的通道被转换。如果设置了 EOCIE 或 JEOCIE,只在最后一个通道转换完毕后才会产生 EOC 或 JEOC 中断。

ADC_CR1[19: 16]用于设置 ADC 的操作模式,详细的对应关系如图3所示。

《嵌入式-STM32开发指南》第二部分 基础篇 - 第8章 模拟输入输出-ADC_ADC_05


图3 ADC 操作模式

本章我们要使用的是独立模式,所以设置这几位为 0 就可以了。接着我们介绍 ADC_CR2,该寄存器的各位描述如图4所示。

《嵌入式-STM32开发指南》第二部分 基础篇 - 第8章 模拟输入输出-ADC_数据_06


图4 ADC_CR2 寄存器操作模式

该寄存器我们也只针对性的介绍一些位: ADON 位用于开关 AD 转换器。而 CONT 位用于设置是否进行连续转换,我们使用单次转换,所以 CONT 位必须为 0。 CAL 和 RSTCAL 用于AD 校准。

ADC_CR2寄存器中的ALIGN位选择转换后数据储存的对齐方式。数据可以左对齐或右对齐,如图5和图6所示。

注入组通道转换的数据值已经减去了在ADC_JOFRx寄存器中定义的偏移量,因此结果可以是一个负值。SEXT位是扩展的符号值。

对于规则组通道,不需减去偏移值,因此只有12个位有效。

《嵌入式-STM32开发指南》第二部分 基础篇 - 第8章 模拟输入输出-ADC_ADC_07


图5数据右对齐

《嵌入式-STM32开发指南》第二部分 基础篇 - 第8章 模拟输入输出-ADC_数据_08


图6数据左对齐

EXTSEL[2: 0]用于选择启动规则转换组转换的外部事件,详细的设置关系如图7所示。

《嵌入式-STM32开发指南》第二部分 基础篇 - 第8章 模拟输入输出-ADC_扫描模式_09


图7 ADC 选择启动规则转换事件设置

我们这里使用的是软件触发(SWSTART),所以设置这 3 个位为 111。 ADC_CR2 的SWSTART 位用于开始规则通道的转换,我们每次转换(单次转换模式下)都需要向该位写 1。AWDEN 为用于使能温度传感器和 Vrefint。 STM32 内部的温度传感器我们将在后文介绍。

第二个要介绍的是 ADC 采样事件寄存器(ADC_SMPR1 和 ADC_SMPR2),这两个寄存器用于设置通道 0~17 的采样时间,每个通道占用 3 个位。 ADC_SMPR1 的各位描述如图8。

《嵌入式-STM32开发指南》第二部分 基础篇 - 第8章 模拟输入输出-ADC_STM32_10


图8 ADC_SMPR1 寄存器各位描述

ADC_SMPR2 的各位描述如下图9所示。

《嵌入式-STM32开发指南》第二部分 基础篇 - 第8章 模拟输入输出-ADC_扫描模式_11


图9 ADC_SMPR2 寄存器各位描述

对于每个要转换的通道,采样时间建议尽量长一点,以获得较高的准确度,但是这样会降低 ADC 的转换速率。 ADC 的转换时间可以由以下公式计算:

Tcovn=采样时间+12.5 个周期

其中: Tcovn 为总转换时间,采样时间是根据每个通道的 SMP 位的设置来决定的。例如,当 ADCCLK=14Mhz 的时候,并设置 1.5 个周期的采样时间,则得到: Tcovn=1.5+12.5=14 个周期=1us。

常见的周期有:

1.5周期、7.5周期、13.5周期、28.5周期、41.5周期、55.5周期、71.5周期、239.5周期。

第三个要介绍的是 ADC 规则序列寄存器(ADC_SQR1~3) ,该寄存器总共有 3 个,这几个寄存器的功能都差不多,这里我们仅介绍一下 ADC_SQR1,该寄存器的各位描述如图10所示。

《嵌入式-STM32开发指南》第二部分 基础篇 - 第8章 模拟输入输出-ADC_ADC_12


图10 ADC_ SQR1 寄存器各位描述

L[3: 0]用于存储规则序列的长度,我们这里只用了 1 个,所以设置这几个位的值为 0。其他的 SQ13~16 则存储了规则序列中第 13~16 个通道的编号(0~17)。另外两个规则序列寄存器同 ADC_SQR1 大同小异,我们这里就不再介绍了,要说明一点的是:我们选择的是单次转换,所以只有一个通道在规则序列里面,这个序列就是 SQ1,通过 ADC_SQR3 的最低 5 位(也就是 SQ1) 设置。

第四个要介绍的是 ADC 规则数据寄存器(ADC_DR)。规则序列中的 AD 转化结果都将被存在这个寄存器里面,而注入通道的转换结果被保存在 ADC_JDRx 里面。 ADC_DR 的各位描述如图11。

《嵌入式-STM32开发指南》第二部分 基础篇 - 第8章 模拟输入输出-ADC_扫描模式_13


图11 ADC_ DRx 寄存器各位描述

《嵌入式-STM32开发指南》第二部分 基础篇 - 第8章 模拟输入输出-ADC_数据_14


图12 ADC_ SR 寄存器各位描述

这里要提醒一点的是,该寄存器的数据可以通过 ADC_CR2 的 ALIGN 位设置左对齐还是右对齐。在读取数据的时候要注意。

最后一个要介绍的 ADC 寄存器为 ADC 状态寄存器(ADC_SR),该寄存器保存了 ADC 转换时的各种状态。该寄存器的各位描述如图12。

这里我们要用到的是 EOC 位,我们通过判断该位来决定是否此次规则通道的 AD 转换已经完成,如果完成我们就从 ADC_DR 中读取转换结果,否则等待转换完成。

1.3 ADC具体代码分析

接下来笔者将通过三种方式实现ADC单通道电压数据采集,先看看笔者使用的开发板的硬件电路,其中PC0外接了一个滑动电阻。

《嵌入式-STM32开发指南》第二部分 基础篇 - 第8章 模拟输入输出-ADC_数据_15



1.3.1 ADC单通道电压采集查询方式实现

ADC详细设置步骤:

1)开启 PC 口时钟和 ADC1 时钟,设置 PC0 为模拟输入。

STM32F103ZET6 的ADC 通道 10在 PC0上,所以,我们先要使能 PORTA 的时钟和 ADC1时钟,然后设置 PC0为模拟输入。 使能 GPIOA 和 ADC 时钟用 RCC_APB2PeriphClockCmd 函数,设置 PC0的输入方式,使用 GPIO_Init 函数即可。 STM32 的 ADC 通道与GPIO 对应表2。

2)复位 ADC1,同时设置 ADC1 分频因子。

开启 ADC1 时钟之后,我们要复位 ADC1, 将 ADC1 的全部寄存器重设为缺省值之后我们就可以通过 RCC_CFGR 设置 ADC1 的分频因子。分频因子要确保 ADC1 的时钟(ADCCLK)不要超过 14Mhz。 这个我们设置分频因子位 8, 时钟为 72/8=9MHz,库函数的实现方法是:

void RCC_ADCCLKConfig(uint32_t RCC_PCLK2)

这个函数非常容易理解,就是复位指定的 ADC。

输入参数范围:

#define RCC_PCLK2_Div2    ((uint32_t)0x00000000)
#define RCC_PCLK2_Div4 ((uint32_t)0x00004000)
#define RCC_PCLK2_Div6 ((uint32_t)0x00008000)
#define RCC_PCLK2_Div8 ((uint32_t)0x0000C000)

STM32的ADC最大的转换速率为1Mhz,也就是转换时间为1us(在ADCCLK=14M,采样周期为1.5个ADC时钟下得到),不要让ADC的时钟超过14M,否则将导致结果准确度下降。

3) 初始化 ADC1 参数, 设置 ADC1 的工作模式以及规则序列的相关信息。

在设置完分频因子之后,我们就可以开始 ADC1 的模式配置了,设置单次转换模式、触发方式选择、数据对齐方式等都在这一步实现。 同时,我们还要设置 ADC1 规则序列的相关信息,我们这里只有一个通道,并且是单次转换的,所以设置规则序列中通道数为 1。 这些在库函数中是通过函数 ADC_Init 实现的,下面我们看看其定义:

void ADC_Init(ADC_TypeDef* ADCx, ADC_InitTypeDef* ADC_InitStruct)

从函数定义可以看出,第一个参数是指定 ADC 号。这里我们来看看第二个参数,跟其他外设初始化一样,同样是通过设置结构体成员变量的值来设定参数。

typedef struct
{
uint32_t ADC_Mode;
FunctionalState ADC_ScanConvMode;
FunctionalState ADC_ContinuousConvMode;
uint32_t ADC_ExternalTrigConv;
uint32_t ADC_DataAlign;
uint8_t ADC_NbrOfChannel;
}ADC_InitTypeDef;

ADC_Mode 故名是以是用来设置 ADC 的模式。前面讲解过, ADC 的模式非常多, 包括独立模式,注入同步模式等等,这里我们选择独立模式,所以参数为 ADC_Mode_Independent。

ADC_ScanConvMode 用来设置是否开启扫描模式, 因为是单次转换,这里我们选择不开启值 DISABLE 即可。

ADC_ContinuousConvMode 用来设置是否开启连续转换模式,因为是单次转换模式,所以我们选择不开启连续转换模式, DISABLE 即可。

ADC_ExternalTrigConv 是用来设置启动规则转换组转换的外部事件,这里我们选择软件触发,选择值为 ADC_ExternalTrigConv_None 即可。

DataAlign 用来设置 ADC 数据对齐方式是左对齐还是右对齐,这里我们选择右对齐方式ADC_DataAlign_Right。

ADC_NbrOfChannel 用来设置规则序列的长度,这里我们是单次转换,所以值为 1 即可。通过上面对每个参数的讲解, 下面来看看我们的初始化范例:

ADC_InitTypeDef ADC_InitStructure;
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; //ADC 工作模式:独立模式
ADC_InitStructure.ADC_ScanConvMode = DISABLE; //AD 单通道模式
ADC_InitStructure.ADC_ContinuousConvMode = DISABLE; //AD 单次转换模式
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
//转换由软件而不是外部触发启动
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; //ADC 数据右对齐
ADC_InitStructure.ADC_NbrOfChannel = 1; //顺序进行规则转换的 ADC 通道的数目 1
ADC_Init(ADC1, &ADC_InitStructure); //根据指定的参数初始化外设 ADCx

4)使能 ADC 并校准。

在设置完了以上信息后, 我们就使能 AD 转换器,执行复位校准和 AD 校准,注意这两步是必须的!不校准将导致结果很不准确。

使能指定的 ADC 的方法是:

ADC_Cmd(ADC1, ENABLE); //使能指定的 ADC1

执行复位校准的方法是:

ADC_ResetCalibration(ADC1);

执行 ADC 校准的方法是:

ADC_StartCalibration(ADC1); //开始指定 ADC1 的校准状态

记住,每次进行校准之后要等待校准结束。 这里是通过获取校准状态来判断是否校准是否结束。

下面我们一一列出复位校准和 AD 校准的等待结束方法:

while(ADC_GetResetCalibrationStatus(ADC1)); //等待复位校准结束
while(ADC_GetCalibrationStatus(ADC1)); //等待校 AD 准结束

ADC有一个内置自校准模式。校准可大幅减小因内部电容器组的变化而造成的准精度误差。在校准期间,在每个电容器上都会计算出一个误差修正码(数字值),这个码用于消除在随后的转换中每个电容器上产生的误差。

通过设置ADC_CR2寄存器的CAL位启动校准。一旦校准结束,CAL位被硬件复位,可以开始正常转换。建议在上电时执行一次ADC校准。校准阶段结束后,校准码储存在ADC_DR 中。

【注意】


1.建议在每次上电后执行一次校准。
2.启动校准前,ADC必须处于关电状态(ADON=’0’)超过至少两个ADC时钟周期。


5)读取 ADC 值。

在上面的校准完成之后, ADC 就算准备好了。接下来我们要做的就是设置规则序列 1 里面的通道,采样顺序, 以及通道的采样周期, 然后启动 ADC 转换。在转换结束后,读取 ADC 转换结果值就是了。 这里设置规则序列通道以及采样周期的函数是:

void ADC_RegularChannelConfig(ADC_TypeDef* ADCx, uint8_t ADC_Channel,
uint8_t Rank, uint8_t ADC_SampleTime)

我们这里是规则序列中的第 1 个转换,同时采样周期为 239.5,所以设置为:

ADC_RegularChannelConfig(ADC1, ch, 1, ADC_SampleTime_239Cycles5 );

软件开启 ADC 转换的方法是:

ADC_SoftwareStartConvCmd(ADC1, ENABLE);//使能指定的 ADC1 的软件转换启动功能

开启转换之后,就可以获取转换 ADC 转换结果数据, 方法是:

ADC_GetConversionValue(ADC1);

同时在 AD 转换中,我们还要根据状态寄存器的标志位来获取 AD 转换的各个状态信息。 库函数获取 AD 转换的状态信息的函数是:

FlagStatus ADC_GetFlagStatus(ADC_TypeDef* ADCx, uint8_t ADC_FLAG);

比如我们要判断 ADC1d 的转换是否结束,方法是:

while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC ));//等待转换结束

1.3.2 ADC单通道电压采集中断方式实现

中断方式和查询方式不同的地方在于需要开启ADC中断服务,配置中断优先级和中断服务函数。笔者接下来之讲与查询方式不同的地方。

1.需要在ADC配置函数中开启ADC中断

ADC_ITConfig(ADC1, ADC_IT_EOC, ENABLE);

2. NVIC配置

因为我们是在转换完成后利用中断,在中断函数中读取数据,所以要首先配置中断函数的优先级,值得注意的是,程序还有一个串口中断,最好把优先组设置在同一组中。

static void ADC_NVIC_Configuration(void)
{
NVIC_InitTypeDef NVIC_InitStructure;

NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);

NVIC_InitStructure.NVIC_IRQChannel = ADC1_2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;

NVIC_Init(&NVIC_InitStructure);
}

3.中断服务函数

在中断函数中进行读取数据,将数据存放在变量ADC_ConvertedValue中。需要注意的是,此处使用关键字extern声明,代表变量ADC_ConvertedValue已经在其他文件中定义。

void ADC1_2_IRQHandler(void)
{
if (ADC_GetITStatus(ADC1,ADC_IT_EOC)==SET)
{
// 读取 ADC 的转换值
ADC_ConvertedValue = ADC_GetConversionValue(ADC1);//获取ADC数据

}
ADC_ClearITPendingBit(ADC1,ADC_IT_EOC);
}

4.主函数

主函数负责接收转换的值,并将其转换为电压值,然后通过串口打印出来,便于查看ADC转换值。

int main(void)
{
SysTick_Init();

/*串口初始化*/
USART1_Config();

/*ADC初始化*/
ADC_Configuration();

printf("\r\n ----这是一个ADC实验(中断方式)----\r\n");
while(1)
{
ADC_ConvertedValueLocal =(float) ADC_ConvertedValue/4096*3.3; // 读取转换的AD值
printf("The current AD value = 0x%04X \r\n", ADC_ConvertedValue);
printf("The current AD value = %f V \r\n\r\n",ADC_ConvertedValueLocal); //实际电压值

Delay_ms(1000);
}
}

完整代码请参看附件。

1.3.3 ADC单通道电压采集DMA方式实现

DMA方式实现的代码结构和查询方式差不多,主要是ADC配置不同。

static void ADC1_Mode_Config(void)
{
DMA_InitTypeDef DMA_InitStructure;
ADC_InitTypeDef ADC_InitStructure;

/* DMA channel1 configuration */
DMA_DeInit(DMA1_Channel1);

DMA_InitStructure.DMA_PeripheralBaseAddr = ADC1_DR_Address; //ADC地址
DMA_InitStructure.DMA_MemoryBaseAddr = (u32)&ADC_ConvertedValue; //内存地址
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
DMA_InitStructure.DMA_BufferSize = 1;
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //外设地址固定
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Disable; //内存地址固定
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; //半字
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; //循环传输
DMA_InitStructure.DMA_Priority = DMA_Priority_High;
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
DMA_Init(DMA1_Channel1, &DMA_InitStructure);

/* Enable DMA channel1 */
DMA_Cmd(DMA1_Channel1, ENABLE);

/* ADC1 configuration */
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; //独立ADC模式
ADC_InitStructure.ADC_ScanConvMode = DISABLE ; //禁止扫描模式,扫描模式用于多通道采集
ADC_InitStructure.ADC_ContinuousConvMode = ENABLE; //开启连续转换模式,即不停地进行ADC转换
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; //不使用外部触发转换
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; //采集数据右对齐
ADC_InitStructure.ADC_NbrOfChannel = 1; //要转换的通道数目1
ADC_Init(ADC1, &ADC_InitStructure);

/*配置ADC时钟,为PCLK2的8分频,即9MHz*/
RCC_ADCCLKConfig(RCC_PCLK2_Div8);
/*配置ADC1的通道11为55. 5个采样周期,序列为1 */
ADC_RegularChannelConfig(ADC1, ADC_Channel_10, 1, ADC_SampleTime_55Cycles5);

/* Enable ADC1 DMA */
ADC_DMACmd(ADC1, ENABLE);

/* Enable ADC1 */
ADC_Cmd(ADC1, ENABLE);

/*复位校准寄存器 */
ADC_ResetCalibration(ADC1);
/*等待校准寄存器复位完成 */
while(ADC_GetResetCalibrationStatus(ADC1));

/* ADC校准 */
ADC_StartCalibration(ADC1);
/* 等待校准完成*/
while(ADC_GetCalibrationStatus(ADC1));

/* 由于没有采用外部触发,所以使用软件触发ADC转换 */
ADC_SoftwareStartConvCmd(ADC1, ENABLE);
}

代码的注释已经很详细了,我不再赘述了。完整代码请参看附件。

这里还需要说明一下 ADC 的参考电压,开发板的芯片是STM32F103ZET6,

该芯片有外部参考电压: Vref-和 Vref+,其中 Vref-必须和 VSSA 连接在一起, 而 Vref+的输入范围为: 2.4~VDDA。需要设置 Vref-和 Vref+设置参考电压,默认的我们是通过跳线帽将 Vref-接到 GND, Vref+接到 VDDA,参考电压就是 3.3V。如果大家想自己设置其他参考电压,将你的参考电压接在 Vref-和 Vref+上就 OK 了。本章我们的参考电压设置的是 3.3V。一般的开发板已经设置好了,不在需要单独去设置。

通过以上几个步骤的设置,我们就能正常的使用 STM32 的 ADC1 来执行 AD 转换操作了。

1.4实验现象

将程序编译好后下载到板子中,打开串口助手可以看到如下现象,当然了,普通方式和DMA方式都是一样的现象。

《嵌入式-STM32开发指南》第二部分 基础篇 - 第8章 模拟输入输出-ADC_扫描模式_16


图14实验现象





代码获取方法

1.长按下面二维码,关注公众号[嵌入式实验楼]

2.在公众号回复关键词[STM32F1]获取资料

《嵌入式-STM32开发指南》第二部分 基础篇 - 第8章 模拟输入输出-ADC_寄存器_17




欢迎访问我的网站:

​BruceOu的哔哩哔哩​

​BruceOu的主页​

​BruceOu的博客​

 ​​BruceOu的简书​