GD32开发实战指南(基础篇) 第12章 ADC

描述

开发环境:

MDK:Keil 5.30

开发板:GD32F207I-EVAL

MCU:GD32F207IK

1 ADC工作原理

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

1.1 ADC架构

ADC架构如下图所示。

adc

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 就可以测量。

adc

【注】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控制的是通道 09, ADC_SAMPT0 控制的是通道 1017。每个通道可以分别用不同的时间采样。其中采样周期最小是 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。

1.2 ADC通道选择

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

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

GD32 ADC IO通道分配
ADC0 IO ADC1 IO ADC2 IO
通道0 PA0 通道0 PA0 通道0 PA0
通道1 PA1 通道1 PA1 通道1 PA1
通道2 PA2 通道2 PA2 通道2 PA2
通道3 PA3 通道3 PA3 通道3 PA3
通道4 PA4 通道4 PA4 通道4 PF6
通道5 PA5 通道5 PA5 通道5 PF7
通道6 PA6 通道6 PA6 通道6 PF8
通道7 PA7 通道7 PA7 通道7 PF9
通道8 PB0 通道8 PB0 通道8 PF10
通道9 PB1 通道9 PB1 通道9 内部VSS
通道10 PC0 通道10 PC0 通道10 PC0
通道11 PC1 通道11 PC1 通道11 PC1
通道12 PC2 通道12 PC2 通道12 PC2
通道13 PC3 通道13 PC3 通道13 PC3
通道14 PC4 通道14 PC4 通道14 内部VSS
通道15 PC5 通道15 PC5 通道15 内部VSS
通道16 内部温度传感器 通道16 内部VSS 通道16 内部VSS
通道17 内部Vrefint 通道17 内部VSS 通道17 内部VSS

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

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

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

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

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,将产生一个中断。

adc

  • 连续转换模式

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

adc

  • 扫描模式

扫描转换模式可以通过将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,则在规则通道转换完之后,这个转换自动重新开始。

adc

  • 间断模式

规则组

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

adc

举例: 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 将产生一个中断。

adc

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

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

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

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

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

【注意】

1.当完成所有注入通道转换,下个触发启动第1个注入通道的转换。在上述例子中,第四个触发重新转换第1个注入通道1。

2.不能同时使用自动注入和间断模式。

3.必须避免同时为规则和注入组设置间断模式。间断模式只能作用于一组转换。

规则组和注入组不能同时工作在间断模式,同一时刻只能有一组被设置成间断模式

2 ADC寄存器描述

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

adc

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

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

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

adc

adc

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

adc

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

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

adc

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

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

adc

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

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

adc

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周期。

adc

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

adc

adc

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的各位描述如下图。

adc

adc

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

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

adc

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

3 ADC具体代码实现

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

adc

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]  adc_typedef_enum adc_id
    param[out] none
    retval     none
*/
void adc_init(adc_typedef_enum adc_id)
{
    /* enable GPIOC clock */
    rcu_periph_clock_enable(ADC_GPIO_CLK[adc_id]);

    /* enable ADC0 clock */
    rcu_periph_clock_enable(ADC_CLK[adc_id]);

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

    /* config the GPIO as analog mode */
    gpio_init(ADC_GPIO_PORT[adc_id], GPIO_MODE_AIN, GPIO_OSPEED_50MHZ, ADC_GPIO_PIN[adc_id]);

    /* ADC mode config */
    adc_mode_config(ADC_MODE_FREE);
	
    /* ADC continuous mode function disable */
    adc_special_function_config(ADC_PERIPH[adc_id], ADC_CONTINUOUS_MODE, DISABLE);

    /* ADC data alignment config */
    adc_data_alignment_config(ADC_PERIPH[adc_id], ADC_DATAALIGN_RIGHT);

    /* ADC channel length config */
    adc_channel_length_config(ADC_PERIPH[adc_id], ADC_REGULAR_CHANNEL, 1);

    /* ADC regular channel config */
    adc_regular_channel_config(ADC_PERIPH[adc_id], 0, ADC_CHANNEL[adc_id], ADC_SAMPLETIME_1POINT5);

    /* ADC trigger config */
    adc_external_trigger_source_config(ADC_PERIPH[adc_id], ADC_REGULAR_CHANNEL, ADC0_1_2_EXTTRIG_REGULAR_NONE);

    /* ADC external trigger enable */
    adc_external_trigger_config(ADC_PERIPH[adc_id], ADC_REGULAR_CHANNEL, ENABLE);

    /* enable ADC interface */
    adc_enable(ADC_PERIPH[adc_id]);
    delay_ms(1);

    /* ADC calibration and reset calibration */
    adc_calibration_enable(ADC_PERIPH[adc_id]);
}

主函数如下所示。

/*
    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, 115200, 0, 1);

    //adc init
    adc_init(A0);

    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

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, 115200, 0, 1);

    //adc init
    adc_init(A0, 1, 0);

    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()函数的循环体的内容。这样可空出主循环干其他事情了。

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 转换操作了。

4 实验现象

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

adc

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分