26. ADC——电压采集

26.1. ADC简介

ADC 即模拟-数字转换器(Analog-to-digital converter), 是一种用于将连续的模拟信号转换为离散的数字信号的器件。 就比如我们可以将我们生活中的温度、压力、声音这样的模拟信号通过 ADC 转化为可以通过单片机处理的数字信号。

RA6M5 有2个ADC单元,每个ADC单元有12位、10位、8位读取数据的格式可以选择,在单元0上有13个ADC通道,而在单元1上有16个ADC通道。 ADC单元具有三种扫描方式分别为:单次描模式、连续扫描模式和分组扫描模式, 对于RA6M5来说ADC单元具有强大的功能,具体我们可以通过ADC特性和ADC的结构框图来分析每个部分的功能。

RA4M2 有1个ADC单元,ADC单元也有12位、10位、8位读取数据的格式可以选择,有13个ADC通道。 RA2L1 有1个ADC单元,ADC单元仅有12位读取数据的格式可以选择,有19个ADC通道。

ADC模块特性

以启明6M5开发板的为例,RA6M5 的ADC特性如下:

  • 2 个ADC转换单元。

  • 支持高达 26 个通道。 其中有三组通道(AN000 & AN100、AN001 & AN101 和 AN002 & AN102)分别共用相同的引脚, 因此通道 AN000 和 AN100 不可同时使用,AN001 和 AN101、AN002 和 AN102 这两组也同理。

  • 支持内部温度传感器,可以检测芯片运行温度;支持测量内部参考电压。

  • 逐次逼近型ADC,支持的分辨率:12-bit, 10-bit, 8-bit。

  • 转换时间很短:0.4 μs/每通道(这是在使用 12-bit ADC、时钟 PCLKC (ADCLK) 等于 50 MHz 的条件下)。

  • PCLKA 是 RA6M5 ADC 外设模块的时钟,用于驱动外设模块的工作; 而 PCLKC (ADCLK) 是用于 A/D 转换的时钟,它们的频率需要保持一定的比例。 PCLKA 与 PCLKC (ADCLK) 的时钟频率比可以设置为:1:1, 2:1, 4:1, 8:1, 1:2, 1:4。

  • 可启用的 A/D 数据存储缓冲区是一个环形缓冲区,由16个缓冲组成,用于顺序存储 A/D 转换后的数据。

更多详细具体的特性请读者参考芯片的硬件手册。

需要注意的是,RA4M2 与 RA6M5 的 ADC 外设基本上没多大差别,而 RA2L1 的 ADC 特性差别稍微大一点。 三者 ADC 特性的简单对比如下表所示:

RA6M5/RA4M2/RA2L1 的ADC特性

特性

RA6M5/RA4M2

RA2L1

ADC转换单元

(逐次逼近型)

RA6M5:2个

RA4M2:1个

1个

输入通道

RA6M5:26个 (少数通道连接到相同引脚)

RA4M2:13个

19个

支持分辨率

12-bit, 10-bit, 8-bit

12-bit

AD转换时间

0.4 μs/每通道

正常转换模式:0.7 µs/每通道

快速转换模式:0.67 µs/每通道

AD转换时钟

ADC外设模块时钟:PCLKA

AD转换时钟:PCLKC (ADCLK)

ADC外设模块时钟:PCLKB

AD转换时钟:PCLKD (ADCLK)

操作模式

  • 单次扫描模式

  • 连续扫描模式

  • 分组扫描模式

  • 单次扫描模式

  • 连续扫描模式

  • 分组扫描模式

ADC 的相关引脚或信号及其功能用途如下表所示:

ADC 相关引脚或信号及其功能

引脚

功能

AVCC0

作为ADC12和DAC12的模拟电源输入引脚

AVSS0

作为ADC12和DAC12的模拟电源接地引脚

VREFH0

作为ADC12单元 0 的参考高电压引脚

VREFL0

作为ADC12单元 0 的参考低电压引脚

VREFH

作为ADC12单元 1 的参考高电压引脚

VREFL

作为ADC12单元 1 的参考低电压引脚

AN0xx

ADC12单元0的模拟输入通道(xx为通道号)

AN1xx

ADC12单元1的模拟输入通道(xx为通道号)

26.2. ADC的结构框图

RA6M5 有两个ADC单元,即 ADC Unit 0 和 ADC Unit 1;而 RA4M2 和 RA2L1 仅有一个ADC单元。 实际上,RA4M2 的ADC单元与 RA6M5 的 ADC Unit 0 是基本一致的,RA2L1 的ADC单元也仅有很少的区别。 下面我们将以 RA6M5 的两个ADC单元为例来讲解ADC模块的结构框图。

RA6M5 的 ADC Unit 0 的结构框图如下图所示:

图

RA6M5 的 ADC Unit 1 的结构框图如下图所示:

图

对比以上 ADC 单元0和单元1的结构框图, 可以发现两者除了参考电压引脚不同以外,其他结构几乎完全相同。

26.2.1. 单元0和单元1的独立参考电压

见图中标注 ① 处。

ADC0 和 ADC1 的参考电压其实是独立的,这一点需要注意一下:

  • ADC0 的参考电压为 VREFH0 和 VREFL0,而

  • ADC1 的参考电压为 VREFH 和 VREFL。

RA6M5 的两个 ADC 单元每个单元都可以设置为不同的参考电压, VREFL0、VREFH0 是 ADC0 参考电压和参考地;VREFL、VREFH 是 ADC1 参考电压和参考地。 用户在硬件设计时可以为这两个不同的 ADC 单元设置不同的参考电压。

电压输入范围

  • ADC0 通道的输入电压范围为:VREFL0 ≤ VIN ≤ VREFH0;

  • ADC1 通道的输入电压范围为:VREFL ≤ VIN ≤ VREFH。

我们在设计原理图的时候一般把 AVSS0 和 VREFL0/VREFL 接地, 把 AVCC0 和 VREFH0/VREFH 经过一个磁珠进行滤波之后接到3V3,因而可得到 ADC 的输入电压范围为:0~3.3V。 如果我们想让输入的电压范围变宽,可以测试负电压或者更高的正电压,我们可以在外部加一个电压调理电路, 把需要转换的电压转换到0~3.3V的范围,这样就可以使用ADC进行测量了。

26.2.2. 单元0和单元1的输入通道

见图中标注 ② 处。

ADC 的模拟输入通道:

  • AN0xx 是 ADC0 的模拟输入通道(xx为通道号);

  • AN1xx 是 ADC1 的模拟输入通道(xx为通道号)。

具体的 ADC 通道如下:

  • ADC0 通道:AN000 ~ AN010, AN012, AN013(共13个通道)

  • ADC1 通道:AN100 ~ AN102, AN116 ~ AN128(共16个通道)

  • 总共29个ADC通道(实际可同时使用的为26个通道)

从上图中,我们还可以看到: ADC0 与 ADC1 之间有三组通道,即 AN000 和 AN100、AN001 和 AN101、AN002 和 AN102 分别共用了相同的物理引脚, 因此通道 AN000 和 AN100 不可同时使用,AN001 和 AN101、AN002 和 AN102 这两组也是一样的,不可同时使用。

26.2.3. 模拟多路转换器和采样保持器

见图中标注 ③ 处。

模拟多路转换器用于对需要转换的ADC通道进行选择, 从上图可以发现,使用 ADC 除了可以对一般通道(AN0xx 或 AN1xx)的电压进行测量以外, 我们还可以选择对内部参考电压、内部温度传感器输出等信号进行测量。

采样保持器具有对输入的信号进行采样和保持的功能。

26.2.4. AD转换器、AD数据和控制寄存器

见图中标注 ④ 处。

ADC0 和 ADC1 均为逐次逼近型的AD转换器。 ADC 控制寄存器用于通过控制电路控制 ADC 外设模块的各项功能。 这部分了解即可,读者不必深究。

对于 RA6M5 和 RA4M2 来说,用户可设置 ADC 外设的 AD 转换精度为 12-bit, 10-bit, 8-bit, 相应的,AD 转换结束后保存到数据寄存器的结果对应为 12-bit, 10-bit, 8-bit 数据。 对于 RA2L1,其 ADC 外设仅支持 AD 转换精度为 12-bit。

用户还可以设置 ADC 数据寄存器的保存格式为左对齐或右对齐。如下图所示。

数据对齐格式

26.2.5. 控制电路以及触发输入和输出信号

见图中标注 ⑤ 处。

  • 中断请求信号

  • 事件输出到ELC信号

  • 同步触发信号

  • 异步触发信号

中断源

在 AD 扫描结束后,会产生一个 AD 扫描结束中断请求信号和 ELC 信号。 用户还可以设置激活 DTC 启动数据传输,将 ADC 转换的结果快速传输至指定的位置。

触发源

用于触发启动 AD 转换的触发源包含以下的三种类型:

  • 软件触发

  • 来自事件链接控制器(ELC)的同步触发

  • 通过外部触发引脚,以及 ADTRG0 (unit 0) 和 ADTRG1 (unit 1)

下图为我们可以在FSP里选择的模式:

可配置扫描启动触发器

26.3. ADC扫描模式

在ADC单元0中有多达13个模拟输入通道,而在ADC单元1中有多达16个模拟输入通道, 两个ADC单元加起来总共就拥有了29个ADC通道,但是实际上ADC单元0和ADC单元1分别有3个通道只能连接到相同的引脚, 所以实际上能同时使用的通道数量为26个,加上还有内部的温度传感器输出等特殊通道。

这么多的ADC通道我们该如何使用它们呢?在这里我们引入“扫描模式”的概念, 在使用ADC外设时,我们必须首先配置其扫描模式,使得ADC按照特定扫描模式的方式进行ADC转换。

ADC 外设有三种扫描模式,分别为:单次扫描模式、连续扫描模式和组扫描模式。 在 FSP 配置器中,用户可以为 ADC 模块设置为三种扫描模式当中的其中一种,如下图所示。

配置扫描模式
  1. 单次扫描模式:在单次扫描下,每一次触发将扫描一个或多个指定通道。

  2. 连续扫描模式:在连续扫描下,首先需要一次触发,然后一个或多个指定的通道会被重复连续进行扫描, 直到软件调用函数 R_ADC_ScanStop() 停止扫描。

  3. 分组扫描模式:将所选择的模拟输入通道分为A组和B组,然后按组对所选择的模拟输入通道进行一次A/D转换。 A、B组可独立选择扫描启动条件,可独立启动A、B组的A/D转换。

在每种模式中,模拟通道按通道数的升序进行转换,然后扫描温度传感器和电压传感器(如果它们也被勾选了的话)。 每一种扫描模式都有着它的优点和缺点,但具体使用什么模式进行ADC转换,就需要通过我们的项目的需求需要什么样的效果来决定。

26.3.1. 单次扫描模式

在单次扫描模式转换期间,我们可以通过ADST为来判断ADC是否处在工作状态,在ADC转换的期间ADST为将一直保持为1,当所有选定通道的ADST转换完成时,将自动设置为0。然后ADC将进入一个等待状态。

  1. 当ADCSR.ADST位通过软件触发器、同步触发器输入(ELC)和异步触发器输入被置1的时候,ADC转换开始。对在ADANSA0和ADANSA1寄存器中选择的ANn通道进行A/D转换,从编号最小的n的通道开始。

  2. 每当单个信道的A/D转换完成时,A/D转换结果都被存储在关联的A/D数据寄存器(ADDRy)中。

  3. 当所有选定通道的A/D转换完成时,将生成一个ADC12i_ADI(i = 0,1)中断请求。

26.3.2. 连续扫描模式

在连续扫描模式下,对指定信道的模拟输入重复执行A/D转换。 这里的ADCSR.ADST位不会自动清除,只要ADCSR.ADST位保持1时就会一直的重复步骤2、步骤3、步骤4,直到ADCSR.ADST位通过软件被置0时ADC单元转换才会停止,之后ADC单元进入等待状态。

  1. 当ADCSR.ADST位通过软件触发器、同步触发器输入(ELC)和异步触发器输入被置1的时候,ADC转换开始。对在ADANSA0和ADANSA1寄存器中选择的ANn通道进行A/D转换,从编号最小的n的通道开始。

  2. 每当单个信道的A/D转换完成时,A/D转换结果都被存储在关联的A/D数据寄存器(ADDRy)中。

  3. 当所有选定通道的A/D转换完成时,将生成一个ADC12i_ADI(i = 0,1)中断请求。

  4. 对在ADANSA0和ADANSA1寄存器中选择的ANn通道进行A/D转换,从编号最小的n的通道开始。

26.3.3. 组扫描模式

在分组扫描模式下,应用程序将通道分配给两个组:组 A 和组 B 当中的一个。 可以为这些组分配不同的启动触发器,分别选择A、B两组的开始扫描条件,当接收到该组的指定 ELC 启动触发器时,转换开始。 并且组 A 可以设置为优先于组 B,当 A 组优先于 B 组时,A 组触发器将暂停正在进行的 B 组扫描。

我们以ELC为例子:使用GPT作为A组的触发源,并使用A组作为B组的触发源。

  1. 当ELC0上的GPT触发ELC_ADC(A组)时,A组的ADC开始转换。

  2. 当组A扫描完成时,将生成一个ADC12i_ADI(i = 0,1)中断。

  3. B组的扫描由ELC_ADC(A组)开始。

  4. 当B组扫描完成时,如果ADCSR.GBADIE位为1时将生成一个ADC12i_GBADI(i = 0,1)中断。

26.4. ADC转换时间

26.4.1. ADC时钟

当使用 ADC 时,ADC 转换时钟(ADCLK)必须至少为1MHz。 并且,在使用 ADC 时很多 RA MCU 一般也有 PCLK 比率限制。

RA6M5 和 RA4M2 的 AD 转换时钟是由 PCLKC 经过分频产生, PCLKA 和 PCLKC (ADCLK) 的分频比可以设置为 1:1, 2:1, 4:1, 8:1, 1:2, 1:4。 当使用 50 MHz 时钟的时候 12-bit AD转换时间为 0.4 μs。

RA2L1 的 AD 转换时钟是由 PCLKD 经过分频产生, PCLKB 和 PCLKD (ADCLK) 的分频比可以设置为 1:1, 1:2, 1:4。 在正常转换模式下,当使用 64 MHz 时钟的时候 12-bit AD转换时间为 0.7 μs; 在快速转换模式下,当使用 48 MHz 时钟的时候 12-bit AD转换时间为 0.67 μs。

26.4.2. 采样时间

时间

扫描转换时间(tSCAN)包括:扫描开始时间(tD)、断开检测辅助处理时间(tDIS)*1、自诊断A/D转换处理时间(tDIAG和tDSD)*2、A/D转换处理时间(tCONV)、扫描结束时间(tED)。

A/D转换处理时间(tCONV)由输入采样时间(tSPL)和逐次逼近转换时间(tSAM)组成。采样时间(tSPL)用于在A/D转换器中对采样和保持电路充电。 如果由于模拟输入信号源的高阻抗而没有足够的采样时间,或者如果A/ D转换时钟(ADCLK)很慢,可以使用ADSSTRn寄存器来调整采样时间。

由逐次逼近(tSAM)转换的时间如下

  • 12位精度需要13个ADCLK

  • 状态10位精度需要11个ADCLK

  • 状态8位精度需要9个ADCLK

选择通道数为n的单次描模式下的扫描转换时间(tSCAN)可确定为:

时间

连续扫描模式下第一个周期的扫描转换时间为单次扫描减去tED的tSCAN。连续扫描模式下第二次及后续周期的扫描转换时间固定如下:

时间

26.5. 电压值转换

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

一般在设计硬件原理图的时候会把ADC的输入电压范围设定在:0~3.3V。 若设置ADC的分辨率是12位的,那么12位满量程对应的电压就是3.3V,12位满量程对应的数字值是:2^12,数值0对应的就是0V。 假设转换后的数值为 X,X 对应的模拟电压为 Y,那么会有这么一个等式成立:

\[\frac {2^{12}}{3.3} = \frac {X}{Y} => Y = \frac {3.3 * X}{2^{12}}\]

因此在ADC转换完成之后,我们可以调用 FSP 库函数 R_ADC_Read(), 从 ADC 的数据寄存器里读出上述等式中 X 的值,从而再经过计算得出对应的电压值。

26.6. 实验:电位器电压采集

26.6.1. 硬件设计

野火启明6M5开发板的 ADC 电位器电路图如下图所示。

图

野火启明4M2开发板的 ADC 电位器电路图如下图所示。

图

野火启明2L1开发板的 ADC 电位器电路图如下图所示。

图

可以看到,三块开发板板载的电位器都是连接到 P000 引脚, P000 引脚可以连接到MCU内部的 ADC 外设,从而对电位器输入的模拟信号进行采集。

ADC引脚

电位器可调端ADC引脚

P000

26.6.2. 软件设计

26.6.2.1. 新建工程

对于 e2 studio 开发环境:

拷贝一份我们之前的 e2s 工程模板 “19_UART_Receive_Send”, 然后将工程文件夹重命名为 “25_ADC”,最后再将它导入到我们的 e2 studio 工作空间中。

对于 Keil 开发环境:

拷贝一份我们之前的 Keil 工程模板 “19_UART_Receive_Send”, 然后将工程文件夹重命名为 “25_ADC”,并进入该文件夹里面双击 Keil 工程文件,打开该工程。

工程新建好之后,在工程根目录的 “src” 文件夹下面新建 “adc” 文件夹, 再进入该文件夹里面新建源文件和头文件:“bsp_adc.c” 和 “bsp_adc.h”。 工程文件结构如下。

文件结构
25_ADC
├─ ......
└─ src
   ├─ led
   │  ├─ bsp_led.c
   │  └─ bsp_led.h
   ├─ debug_uart
   │  ├─ bsp_debug_uart.c
   │  └─ bsp_debug_uart.h
   ├─ adc
   │  ├─ bsp_adc.c
   │  └─ bsp_adc.h
   └─ hal_entry.c

26.6.2.2. FSP配置

打开该工程的 FSP 配置界面进行配置。

首先依次点击 “Stacks” -> “Pins” -> “Peripherals” -> “ADC0” 来配置通道 AN000 对应的引脚为 P000。 如下图所示。

图

然后依次点击 “Stacks” -> “New Stack” -> “Analog” -> “ADC (r_adc)” 来配置ADC模块。 如下图所示。

图

ADC 的属性配置:

图
ADC 属性介绍

ADC属性

描述

General > Name

模块实例名

General > Unit

指定要使用的ADC单元

General > Resolution

指定转换分辨率

General > Alignment

指定转换结果对齐方式

General > Clear after read

读取转换结果后自动清除

General > Mode

模式

  • Single Scan:单次扫描

  • Continuous Scan:连续扫描

  • Group Scan:组扫描

General > Double-trigger

双触发

Input > Channel Scan Mask

(channel availability varies by MCU)

该通道位掩码用于使能 ADC 通道;

若是在组模式下,则是用于指定哪些通道归属于扫描组 A。

这里要勾选 “Channel 0”,图上由于篇幅关系没有标出来

Interrupts > Normal/Group A Trigger

正常模式下的或组模式下组A的触发类型。

这里按照默认,选择软件触发

Interrupts > Callback

中断回调函数。设置为 adc_callback

Interrupts > Scan End Interrupt Priority

扫描完成中断优先级

Extra > ADC Ring Buffer

ADC环形缓冲区

配置完成之后可以按下快捷键“Ctrl + S”保存, 最后点右上角的 “Generate Project Content” 按钮,让软件自动生成配置代码即可。

26.6.2.3. ADC初始化函数

代码清单 25‑1 ADC初始化函数
void ADC_Init(void)
{
   fsp_err_t err;
   err = R_ADC_Open(&g_adc0_ctrl, &g_adc0_cfg);
   err = R_ADC_ScanCfg(&g_adc0_ctrl, &g_adc0_channel_cfg);
   assert(FSP_SUCCESS == err);
}
  1. R_ADC_Open()为整个外设设置操作模式、触发源、中断优先级和配置。如果启用了中断,该函数将注册一个回调函数指针,以便在扫描完成时通知用户。

  2. R_ADC_ScanCfg()配置ADC扫描参数。通道特定设置是在这个函数中设置的。

26.6.2.4. ADC中断回调函数

代码清单 25‑2 ADC中断回调函数
//ADC转换完成标志位
volatile bool scan_complete_flag = false;

void adc_callback(adc_callback_args_t * p_args)
{
   FSP_PARAMETER_NOT_USED(p_args);
   scan_complete_flag = true;
}

在FSP配置页面注册回调函数以及优先级,我们就可以使用来自ADC的中断回调函数了。

注解

我们通过ADC的中断回调函数来判断ADC是否转换完成。 我们需要定义了一个布尔类型的数据scan_complete_flag来当做ADC读取完成的标志位。 当没有转换完成的时候scan_complete_flag的值一直为false,单ADC触发中断的时候将scan_complete_flag的值变为true。

26.6.2.5. 如果未启用中断

如果未启用中断,则可使用R_ADC_StatusGet() API 用于轮询 ADC 以确定扫描何时完成。读取 API 函数用于访问转换后的 ADC 结果。这适用于支持校准的MCU的普通扫描和校准扫描。

26.6.2.6. ADC读取转换结果函数

ADC读取思路,我们在这里调用R_ADC_ScanStart触发相应的adc通道转换,当ADC转换完成之后会将scan_complete_flag标志位变为true。 当我们判断到标志位变为true后我们使用R_ADC_Read()或R_ADC_Read32()读取转换完成的数值。

代码清单 25‑3 ADC读取转换结果函数
/* 进行ADC采集,读取ADC数据并转换结果 */
double Read_ADC_Voltage_Value(void)
{
   uint16_t adc_data;
   double a0;

   (void)R_ADC_ScanStart(&g_adc0_ctrl);
   while (!scan_complete_flag) //等待转换完成标志
   {
      ;
   }
   scan_complete_flag = false; //重新清除标志位

   /* 读取通道0数据 */
   R_ADC_Read(&g_adc0_ctrl, ADC_CHANNEL_0, &adc_data);
   /* ADC原始数据转换为电压值(ADC参考电压为3.3V) */
   a0 = (double)(adc_data*3.3/4095);

   return a0;
}
  1. R_ADC_ScanStart()启动软件扫描或启用扫描的硬件触发器,具体取决于触发器在R_ADC_Open调用中的配置方式。如果该单元被配置为ELC或外部硬件触发,那么该功能允许触发信号到达ADC单元。该函数不能控制触发器本身的生成。如果该单元被配置为软件触发,则该功能启动软件触发扫描。

  2. R_ADC_Read()从单通道或传感器寄存器读取转换结果,返回的数据为uint16_t型。

  3. R_ADC_Read32()从单通道或传感器寄存器读取转换结果,返回的数据为uint32_t型。

26.6.2.7. hal_entry入口函数

代码清单 25‑4 hal_entry入口函数
void hal_entry(void)
{
   LED_Init();         // LED 初始化
   Debug_UART4_Init(); // SCI4 UART 调试串口初始化

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

   printf("这是一个读取电位器ADC电压转换值的例程\r\n");
   printf("打开串口助手查看ADC转换结果,旋钮电位器,可以看到ADC值在一定范围之内发生变化\r\n");
   printf("开始读取ADC转换值:\r\n");


   while(1)
   {
      printf("a0 = %f\r\n", Read_ADC_Voltage_Value());
      R_BSP_SoftwareDelay(500, BSP_DELAY_UNITS_MILLISECONDS); //大概0.5秒钟读取一次
      LED1_TOGGLE;
   }


#if BSP_TZ_SECURE_BUILD
   /* Enter non-secure code */
   R_BSP_NonSecureEnter();
#endif
}

26.6.3. 下载验证

用USB TYPE-C线连接开发板“USB TO UART”接口跟电脑,在电脑端打开串口调试助手,把编译好的程序下载到开发板。 在串口调试助手可看到从 ADC 引脚读出的模拟电压数值。