【嵌入式 – GD32开发实战指南(ARM版)】第1部分 基础篇 – 第12章 ADC

开发环境:
MDK:Keil 5.30
开发板:GD32F207I-EVAL
MCU:GD32F207IK

12.1 ADC工作原理

GD32F2系列有 3 个逐次逼近型的ADC,精度为 12 位,有18个多路复用通道,可以转换来自16个外部通道和2个内部通道的模拟信号。其中ADC0 和 ADC1都有 16 个外部通道, ADC2 根据 CPU 引脚的不同通道数也不同,一般都有8 个外部通道。各通道的A/D转换可以单次、连续、扫描或间断模式执行。ADC的结果可以左对齐或右对齐方式存储在16位数据寄存器中。模拟看门狗特性允许应用程序检测输入电压是否超出用户定义的高/低阀值。ADC 的输入时钟不得超过28MHz,它是由PCLK2经分频产生。

12.1.1 ADC架构

ADC架构如下图所示。

jIUZQ0.md.png

1.电压输入范围

ADC 输入范围为:VREFN ≤ VIN ≤ VREFP。由VREFN、VREFP、VDDA、VSSA这四个外部引脚决定。

我们在设计原理图的时候一般把 VSSA 和 VREFN接地,把VREFP和 VDDA 接 3V3,得到ADC 的输入电压范围为: 0 ~ 3.3V。在 64 脚以下的 CPU 中,没有VREFN和 VREFP这两个引脚,
ADC 电压输入范围直接由 VDDA和 VSSA决定。如果我们想让输入的电压范围变宽,去到可以测试负电压或者更高的正电压,我们可以在外部加一个电压调理电路,把需要转换的电压抬升或者降压到 0~3.3V,这样 ADC 就可以测量。

jIUueU.md.png

【注】VDDA和VSSA必须分别连接到VDD和VSS。

2.输入通道

我们确定好ADC输入电压之后,那么电压怎么输入到 ADC?这里我们引入通道的概念,GD32 的ADC多达18个通道,其中外部的16个通道就是框图中的 ADCx_IN0、ADCx_IN1…ADCx_IN5。这16个通道对应着不同的 IO 口,具体是哪一个 IO 口可以从手册查询到。其中 ADC0/1/2还有内部通道: ADC0 的通道 16 连接到了芯片内部的温度传感器, Vrefint 连接到了通道 17。 ADC1 的模拟通道 16 和 17 连接到了内部的 VSS。ADC2 的模拟通道 9、 14、 15、 16 和 17 连接到了内部的 VSS。

外部的 16 个通道在转换的时候又分为规则通道和注入通道,其中规则通道最多有 16路,注入通道最多有 4 路。那这两个通道有什么区别?在什么时候使用?

规则通道
规则通道:规则通道就是很规矩的意思,我们平时一般使用的就是这个通道,或者应该说我们用到的都是这个通道,没有什么特别要注意的可讲。

注入通道
注入,可以理解为插入,插队的意思,是一种不安分的通道。它是一种在规则通道转换的时候强行插入要转换的一种。如果在规则通道转换过程中,有注入通道插队,那么就要先转换完注入通道,等注入通道转换完成后,再回到规则通道的转换流程。这点跟中断程序很像,都是不安分的主。所以,注入通道只有在规则通道存在时才会出现。

3.转换顺序
ADC支持18个多路通道,可以把转换组织成两组:一个规则组通道和一个注入组通道。

规则组,可以按照特定的序列组织成多达16个转换的序列。ADC_RSQ0~ADC_RSQ2寄存器规定了规则组的通道选择。ADC_RSQ0寄存器的RL[3:0]位规定了整个规则组转换序列的长度。

注入组,可以按照特定的序列组织成多达4个转换的序列。ADC_ISQ寄存器规定了注入组的通道选择。ADC_ISQ寄存器的IL[1:0]位规定了整个注入组转换序列的长度。

4.触发源
通道选好了,转换的顺序也设置好了,那接下来就该开始转换了。 ADC 转换可以由ADC_CTL1的 ADON 这个位来控制,写 1 的时候开始转换,写 0 的时候停止转换,这个是最简单也是最好理解的开启 ADC 转换的控制方式,理解起来没啥技术含量。

除了这种庶民式的控制方法, ADC 还支持触发转换,这个触发包括内部定时器触发和外部 IO 触发。触发源有很多,具体选择哪一种触发源,由 ADC_CTL1的ETSRC[2:0]和ETSIC[2:0]位来控制。ETSRC[2:0]用于选择规则通道的触发源,ETSIC[2:0]用于选择注入通道的触发源。选定好触发源之后,触发源是否要激活,则由ADC_CTL1的 ETERC和 ETEIC这两位来激活。

5.数据寄存器
一切准备就绪后,ADC 转换后的数据根据转换组的不同,规则组的数据放在ADC_RDATA寄存器,注入组的数据放在ADC_IDATAx。

6.中断
转换结束中断
数据转换结束后,可以产生中断,中断分为三种:规则通道转换结束中断,注入转换通道转换结束中断,模拟看门狗中断。其中转换结束中断很好理解,跟我们平时接触的中断一样,有相应的中断标志位和中断使能位,我们还可以根据中断类型写相应配套的中断服务程序。

模拟看门狗中断
当被 ADC 转换的模拟电压低于低阈值或者高于高阈值时,就会产生中断,前提是我们开启了模拟看门狗中断,其中低阈值和高阈值由 ADC_WDHT 和 ADC_WDLT置。例如我们设置高阈值是 2.5V,那么模拟电压超过 2.5V 的时候,就会产生模拟看门狗中断,反之低阈值也一样。

DMA 请求
DMA 请求可以通过设置 ADC_CTL1 寄存器的 DMA 位来使能,它用于传输规则组多个通道的转换结果。 ADC 在规则组一个通道转换结束后产生一个 DMA 请求, DMA 接受到请求后可以将转换的数据从 ADC_RDATA 寄存器传输到用户指定的目的地址。

注意: 只有 ADC0 和 ADC2 有 DMA 功能, ADC1 转换的数据可以在 ADC 同步模式下传输。

7.转换时间

ADC 时钟

ADC 输入时钟 ADCCLK由 PCLK2 经过分频产生,最大是28M,分频因子由 RCC 时钟配置寄存器RCU_CFG0的位 15:14 ADCPSC[1:0]设置,可以是 2/4/6/8/12/16 分频,注意这里没有 1 分频。一般我们设置 PCLK2=HCLK=120M。

采样时间
ADC 使用若干个 ADCCLK 周期对输入的电压进行采样,采样的周期数可通过 ADC采样时间寄存器ADC_SAMPT0 和 ADC_SAMPT1中的 SMP[2:0]位设置,ADC_SAMPT1控制的是通道 0 ~ 9,

ADC_SAMPT0 控制的是通道 10 ~ 17。每个通道可以分别用不同的时间采样。其中采样周期最小是 1.5 个,即如果我们要达到最快的采样,那么应该设置采样周期为 1.5个周期,这里说的周期就是 1/ADCCLK。

ADC 的转换时间跟 ADC 的输入时钟和采样时间有关,公式为:

Tconv = 采样时间 +12.5 个周期。

例如,当 ADCLK = 14MHz,采样时间设置为 1.5 周期(最快),那么总的转换时间:

Tconv = 1.5 周期 + 12.5 周期 = 14 周期 = 1us。

8.电压转换

模拟电压经过 ADC 转换后,是一个 12 位的数字值,如果通过串口以 16 进制打印出来的话,可读性比较差,那么有时候我们就需要把数字电压转换成模拟电压,也可以跟实际的模拟电压(用万用表测)对比,看看转换是否准确。

我们一般在设计原理图的时候会把ADC 的输入电压范围设定在: 0~3.3v,因为 ADC是 12 位的,那么 12 位满量程对应的就是3.3V,12 位满量程对应的数字值是: 2^12。数值0 对应的就是 0V。如果转换后的数值为X, X对应的模拟电压为 Y,那么会有这么一个等式成立: 2^12 / 3.3 = X / Y, => Y = (3.3 * X ) / 2^12。

12.1.2 ADC通道选择

GD32 将 ADC 的转换分为 2 个通道组:规则通道组和注入通道组

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

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

jIUJQx.md.png

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

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

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

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

12.1.3 ADC转换模式

 单次转换模式

该模式能够运行在规则组和注入组。单次转换模式下, ADC_RSQ2寄存器的RSQ0[4:0]位或者ADC_ISQ寄存器的ISQ3[4:0]位规定了ADC的转换通道。当ADCON位被置1,一旦相应软件触发或者外部触发发生, ADC就会采样和转换一个通道。

规则通道单次转换结束后,转换数据将被存放于ADC_RDATA寄存器中, EOC将会置1。如果EOCIE位被置1,将产生一个中断。

注入通道单次转换结束后,转换数据将被存放于ADC_IDATA0寄存器中, EOC和EOIC位将会置1。如果EOCIE或EOICIE位被置1,将产生一个中断。

jIawuV.md.png

 连续转换模式
在该模式可以运行在规则组通道上。对ADC_CTL1寄存器的CTN位置1可以使能连续转换模式。在此模式下, ADC执行由RSQ0[4:0]规定的转换通道。当ADCON位被置1,一旦相应软件触发或者外部触发产生, ADC就会采样和转换规定的通道。转换数据保存在ADC_RDATA寄存器中。

jIaoUe.md.png

 扫描模式
扫描转换模式可以通过将ADC_CTL0寄存器的SM位置1来使能。在此模式下, ADC扫描转换所有被ADC_RSQ0~ADC_RSQ2寄存器或ADC_ISQ寄存器选中的所有通道。一旦ADCON位被置1,当相应软件触发或者外部触发产生, ADC就会一个接一个的采样和转换规则组或注入组通道。转换数据存储在ADC_RDATA或ADC_IDATAx寄存器中。规则组或注入组转换结束后,EOC或者EOIC位将被置1。如果EOCIE或EOICIE位被置1,将产生中断。当规则组通道工作在扫描模式下时, ADC_CTL1寄存器的DMA位必须设置为1。

如果ADC_CTL1寄存器的CTN位也被置1,则在规则通道转换完之后,这个转换自动重新开始。

jIaT4H.md.png

 间断模式

规则组
对于规则组, ADC_CTL0 寄存器的 DISRC 位置 1 使能间断转换模式。该模式下可以执行一次n 个通道的短序列转换(n<=8),此转换是 ADC_RSQ0~RSQ2 寄存器所选择的转换序列的一部分。数值 n 由 ADC_CTL0 寄存器的 DISCNUM[2:0]位给出。当相应的软件触发或外部触发发生, ADC 就会采样和转换在 ADC_RSQ0 ~RSQ2 寄存器所选择通道中接下来的 n 个通道,直到规则序列中所有的通道转换完成。每个规则组转换周期结束后, EOC位将被置1。如果EOCIE位被置 1 将产生一个中断。

jIdSUg.md.png

举例: n=3,被转换的通道 = 0 、1、2、3、6、7、9、10
第一次触发:转换的序列为 0 、1、2
第二次触发:转换的序列为 3 、6、7
第三次触发:转换的序列为 9 、10,并产生EOC事件
第四次触发:转换的序列 0 、1、2

注意:
1.当以间断模式转换一个规则组时,转换序列结束后不自动从头开始。
2.当所有子组被转换完成,下一次触发启动第一个子组的转换。在上面的例子中,第四次触发重新转换第一子组的通道 0 、1和2。

注入组
对于注入组,ADC_CTL0 寄存器的 DISIC 位置 1 使能间断转换模式。该模式下可以执行ADC_ISQ 寄存器所选择的转换序列的一个通道进行转换。当相应的软件触发或外部触发发生,ADC 就会采样和转换 ADC_ISQ 寄存器中所选择通道的下一个通道,直到注入组序列中所有通道转换完成。每个注入组通道转换周期结束后, EOIC 位将被置 1。如果 EOICIE 位被置 1 将产生一个中断。

jIdp5Q.md.png

例子: n=1,被转换的通道 = 1 、2、3
第一次触发:通道1被转换
第二次触发:通道2被转换
第三次触发:通道3被转换,并且产生EOC和JEOC事件
第四次触发:通道1被转换

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

12.2 ADC寄存器描述

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

jIdCCj.md.png

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

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

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

jIdi2n.md.png

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

jIdErV.md.png

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

ADC_CTL1寄存器中的DAL位选择转换后数据储存的对齐方式。数据可以左对齐或右对齐,如下图所示。

jIdQ2R.md.png

注入组通道转换的数据值已经减去了在 ADC_IOFFx 寄存器中定义的偏移量,因此结果可能是一个负值。符号值是一个扩展值。对于规则组通道,不需减去偏移值,因此只有12个位有效。

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

jId8r6.md.png

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

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

jIdYVO.md.png

ADC_SAMPT1和ADC_SAMPT0差不多,只是该寄存器用于配置通道0 ~ 通道9。

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

Tcovn=采样时间+12.5 个周期

其中: Tcovn 为总转换时间,采样时间是根据每个通道的SPT 位的设置来决定的。例如,当 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周期。

jIdNIe.md.png

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

jIddGd.md.png

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

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

jIdDMt.md.png

jId6Z8.md.png

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

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

jIdRiQ.md.png

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

12.3 ADC具体代码实现

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

jIdhzn.md.png

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

ADC参数设置的详细步骤:

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

GD32F207的ADC 通道 13在 PC3上,所以,我们先要使能 PC 的时钟和 ADC0时钟,然后设置PC0为模拟输入。 使能 GPIOC 和 ADC 时钟,设置 PC3的输入方式。

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

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

void rcu_adc_clock_config(uint32_t adc_psc);

输入参数范围:

/* ADC prescaler selection */
#define RCU_CKADC_CKAPB2_DIV2           ((uint32_t)0x00000000U)             /*!< ADC prescaler select CK_APB2/2 */
#define RCU_CKADC_CKAPB2_DIV4           ((uint32_t)0x00000001U)             /*!< ADC prescaler select CK_APB2/4 */
#define RCU_CKADC_CKAPB2_DIV6           ((uint32_t)0x00000002U)             /*!< ADC prescaler select CK_APB2/6 */
#define RCU_CKADC_CKAPB2_DIV8           ((uint32_t)0x00000003U)             /*!< ADC prescaler select CK_APB2/8 */
#define RCU_CKADC_CKAPB2_DIV12          ((uint32_t)0x00000005U)             /*!< ADC prescaler select CK_APB2/12 */
#define RCU_CKADC_CKAPB2_DIV16          ((uint32_t)0x00000007U)             /*!< ADC prescaler select CK_APB2/16 */

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

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

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

/* ADC mode config */
adc_mode_config(ADC_MODE_FREE);

/* ADC data alignment config */
adc_data_alignment_config(ADC0, ADC_DATAALIGN_RIGHT);

/* ADC channel length config */
adc_channel_length_config(ADC0, ADC_REGULAR_CHANNEL, 1);

/* ADC regular channel config */
adc_regular_channel_config(ADC0, 0, ADC_CHANNEL_13, ADC_SAMPLETIME_1POINT5);

/* ADC trigger config */
adc_external_trigger_source_config(ADC0, ADC_REGULAR_CHANNEL, ADC0_1_2_EXTTRIG_REGULAR_NONE);

/* ADC external trigger enable */
adc_external_trigger_config(ADC0, ADC_REGULAR_CHANNEL, ENABLE);

adc_mode_config()用来设置ADC模式,这里只使用一个ADC,因此设置为独立模式。
adc_data_alignment_config()用来设置 ADC 数据对齐方式是左对齐还是右对齐,这里我们选择右对齐方式。
adc_channel_length_config()用来设置规则序列的长度,这里我们是单次转换,所以值为 1 即可。
adc_regular_channel_config()用来设置ADC通道转换顺序,这里设置采样时间为1.5个时钟周期。
adc_special_function_config()函数用来设置是否开启连续转换模式,因为是单次转换模式,所以我们选择不开启连续转换模式,DISABLE 即可。
adc_external_trigger_source_config()用来设置启动规则转换组转换的外部事件,这里我们选择软件触发,选择值为ADC0_1_2_EXTTRIG_REGULAR_NONE即可。
adc_external_trigger_config()用于使能外部触发。

4)使能 ADC 并校准。

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

使能指定的 ADC 的方法是:

adc_enable(ADC0);

执行 ADC 校准的方法是:

adc_calibration_enable(ADC0);

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

通过设置ADC_CTL1寄存器的CLB位启动校准。一旦校准结束,CLB位被硬件复位,可以开始正常转换。建议在上电时执行一次ADC校准。

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

5)读取 ADC 值。

在上面的校准完成之后, ADC 就算准备好了。接下来启动 ADC 转换。在转换结束后,读取 ADC 转换结果值就是了。

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

adc_software_trigger_enable(ADC0, ADC_REGULAR_CHANNEL);

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

adc_regular_data_read(ADC0);//ADC转换结果

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

FlagStatus adc_flag_get(uint32_t adc_periph, uint32_t adc_flag)

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

while(!adc_flag_get(ADC0,ADC_FLAG_EOC));//检查转换标志

接下来看看ADC完整的配置。

/*
    brief      Configure the ADC peripheral
    param[in]  none
    param[out] none
    retval     none
*/
void adc_config(void)
{
    /* enable GPIOC clock */
    rcu_periph_clock_enable(RCU_GPIOC);

    /* enable ADC0 clock */
    rcu_periph_clock_enable(RCU_ADC0);

    /* config ADC clock */
    rcu_adc_clock_config(RCU_CKADC_CKAPB2_DIV8);

    /* config the GPIO as analog mode */
    gpio_init(GPIOC, GPIO_MODE_AIN, GPIO_OSPEED_50MHZ, GPIO_PIN_3);

    /* ADC mode config */
    adc_mode_config(ADC_MODE_FREE);

    /* ADC continuous mode function disable */
    adc_special_function_config(ADC0, ADC_CONTINUOUS_MODE, DISABLE);

    /* ADC data alignment config */
    adc_data_alignment_config(ADC0, ADC_DATAALIGN_RIGHT);

    /* ADC channel length config */
    adc_channel_length_config(ADC0, ADC_REGULAR_CHANNEL, 1);

    /* ADC regular channel config */
    adc_regular_channel_config(ADC0, 0, ADC_CHANNEL_13, ADC_SAMPLETIME_1POINT5);

    /* ADC trigger config */
    adc_external_trigger_source_config(ADC0, ADC_REGULAR_CHANNEL, ADC0_1_2_EXTTRIG_REGULAR_NONE);

    /* ADC external trigger enable */
    adc_external_trigger_config(ADC0, ADC_REGULAR_CHANNEL, ENABLE);

    /* enable ADC interface */
    adc_enable(ADC0);
    delay_ms(1);

    /* ADC calibration and reset calibration */
    adc_calibration_enable(ADC0);

}

主函数如下所示。

/*
    brief      main function
    param[in]  none
    param[out] none
    retval     none
*/
int main(void)
{
    float adc_convertedValueLocal;   
    uint32_t adc_convertedValue;

    //systick init
    sysTick_init();

    //usart init 115200 8-N-1
    com_init(COM1);

    //adc config
    adc_config();

    while(1)
    {
        adc_software_trigger_enable(ADC0, ADC_REGULAR_CHANNEL);

        while(!adc_flag_get(ADC0,ADC_FLAG_EOC));//检查转换标志
        adc_flag_clear(ADC0, ADC_FLAG_EOC); // 清除结束标志

        adc_convertedValue=adc_regular_data_read(ADC0);//ADC转换结果

        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);
    }
}

如果想开启连续转换,只需将ADC_CONTINUOUS_MODE配置为ENABLE即可。

adc_special_function_config(ADC0, ADC_CONTINUOUS_MODE, ENABLE);

然后只需打开启动一次ADC转换。

adc_software_trigger_enable(ADC0, ADC_REGULAR_CHANNEL);

最后看看ADC采样时间的计算。

ADCCLK(ADC的时钟频率 ) = 120MHZ(系统时钟频率) / 8 (ADC分频因子) = 15MHZ。

一个ADC周期占用的时间 = 1 / 时钟频率 = 1 / 15MHz = 0.066666 uS

一次采样总的时间 = 采样时间 + 12.5个周期 = 1.5周期 + 12.5周期 = 14 * 0.066666 = 0.933333 us

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

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

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

adc_interrupt_enable(ADC0, ADC_INT_EOC);

2.NVIC配置

因为我们是在转换完成后利用中断,在中断函数中读取数据,所以要首先配置中断函数的优先级。

nvic_irq_enable(ADC0_1_IRQn, 0, 0);

3.中断服务函数

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

/*!
    \brief      this function handles ADC0 and ADC1 interrupt
    \param[in]  none
    \param[out] none
    \retval     none
*/
void ADC0_1_IRQHandler(void)
{
    if(adc_interrupt_flag_get(ADC0, ADC_INT_FLAG_EOC))
    {
        adc_interrupt_flag_clear(ADC0, ADC_INT_FLAG_EOC);  // 清除ADC规则组转换结束中断标志
        adc_convertedValue = adc_regular_data_read(ADC0);   // 读取ADC数据
    }
}

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

/*
    brief      main function
    param[in]  none
    param[out] none
    retval     none
*/
int main(void)
{
    float adc_convertedValueLocal;   

    //systick init
    sysTick_init();

    //usart init 115200 8-N-1
    com_init(COM1);

    //adc config
    adc_config();

    adc_software_trigger_enable(ADC0, ADC_REGULAR_CHANNEL);

    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,定时时间为1s,则可将以下函数的内容替换main()函数的循环体的内容。这样可空出主循环干其他事情了。

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

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

/*
    brief      configure the DMA peripheral
    param[in]  none
    param[out] none
    retval     none
*/
void dma_config(void)
{
    /* ADC_DMA_channel configuration */
    dma_parameter_struct dma_data_parameter;

    /* enable DMA clock */
    rcu_periph_clock_enable(RCU_DMA0);

    /* ADC_DMA_channel deinit */
    dma_deinit(DMA0, DMA_CH0);

    /* initialize DMA single data mode */
    dma_data_parameter.periph_addr  = (uint32_t)(&ADC_RDATA(ADC0));
    dma_data_parameter.periph_inc   = DMA_PERIPH_INCREASE_DISABLE;
    dma_data_parameter.memory_addr  = (uint32_t)(&adc_convertedValue);
    dma_data_parameter.memory_inc   = DMA_MEMORY_INCREASE_DISABLE;
    dma_data_parameter.periph_width = DMA_PERIPHERAL_WIDTH_32BIT;
    dma_data_parameter.memory_width = DMA_MEMORY_WIDTH_32BIT;
    dma_data_parameter.direction    = DMA_PERIPHERAL_TO_MEMORY;
    dma_data_parameter.number       = 1;
    dma_data_parameter.priority     = DMA_PRIORITY_HIGH;
    dma_init(DMA0, DMA_CH0, &dma_data_parameter);

    dma_circulation_enable(DMA0, DMA_CH0);

    /* enable DMA channel */
    dma_channel_enable(DMA0, DMA_CH0);
}

然后使能ADC的DMA。

adc_dma_mode_enable(ADC0);

代码的注释已经很详细了,我不再赘述了。

这里还需要说明一下 ADC 的参考电压,我的开发板使用的是 GD32F207,

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

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

12.4实验现象

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

jIdboF.md.png


欢迎访问我的网站

BruceOu的哔哩哔哩
BruceOu的主页
BruceOu的博客
BruceOu的CSDN博客
BruceOu的简书
BruceOu的知乎


资源获取方式

1.关注公众号[嵌入式实验楼]
2.在公众号回复关键词[GD32开发实战指南]获取资料提取码

Related posts

Leave a Comment