前言:


...


1:简介

12 位 ADC 是一种采用逐次逼近方式的模拟数字转换器。它有 18 个多路复用通道,可以转换来自 16 个外部通道和 2 个内部通道的模拟信号。模拟看门狗允许应用程序来检测输入电压是否超出用户设定的高低阈值。各种通道的 A/D 转换可以配置成单次、连续、扫描或间断转换模式。 ADC 转换的结果可以按照左对齐或右对齐的方式存储在 16 位数据寄存器中。片上的硬件过采样机制可以通过减少来自 MCU 的相关计算负担来提高性能。
 


2:ADC 主要特征

高性能:

– 可配置12位、 10位、 8位、或者6位分辨率;

– 自校准;

– 可编程采样时间;

– 数据寄存器可配置数据对齐方式;

– 支持规则数据转换的DMA请求。

 模拟输入通道:

– 16个外部模拟输入通道;

– 1个内部温度传感通道(VSENSE);

– 1个内部参考电压输入通道(VREFINT)。

 转换开始的发起:

– 软件;

– 硬件触发。

 转换模式:

– 转换单个通道,或者扫描一序列的通道;

– 单次模式,每次触发转换一次选择的输入通道;

– 连续模式,连续转换所选择的输入通道;

– 间断模式;

– 同步模式(适用于具有两个或多个ADC的设备)。

 模拟看门狗。

 中断的产生:

– 规则组或注入组转换结束;

– 模拟看门狗事件。

 过采样:

– 16位的数据寄存器;

– 可调整的过采样率,从2x到256x;

– 高达8位的可编程数据移位。

 ADC供电要求: 2.6V到3.6V,一般电源电压为3.3V。

 ADC输入范围: VREFN ≤VIN ≤VREFP。


3:ADC 硬件结构

以上是ADC的程序框图

ADC 的硬件结构,有来自GPIO的输入(16个外部模拟输入通道),有来自外部采集温度输入,以及参考电压输入,AD模拟数字转换器内含规则组与注入组,含触发方式有软件触发,硬件触发,定时器触发等,规则组可以理解为程序的主流程,注入组可以理解为程序里面的中断注入组可以打断规则组的执行直到执行完成以后才会继续执行规则组的内容,转换完成后执行的结果会放在AD数据寄存器中,规则组16个通道共用一个数据寄存器,注入组:对应有4个数据寄存器,转换完成后设置EOC中断标志位,产生EOC中断,时钟的频率是40MHz,中断输出控制需要使能,NVIC也要打开相关的中断才能正常使能。


4:外部模拟输入通道IO口

5:ADC 转换模式

转换模式中的单次转换和连续转换:单次转换表示的含义是,每次转换都需要一次外部出发,同时设置EOC标志位,连续模式表示的含义是:开始转换时只需要外部触发一次,后续的转换会自己连续的触发,这里采用的是非扫描的模式。

扫描模式:主要针对某个ADC使用多个还是单个

 单次转换扫描模式,单次转换连续模式,需要配合DMA进行使用,因为存储的数据寄存器只有1个

如果不使用DMA进行数据转运会导致后面一次转换的数据覆盖前面一次转换的数据,造成数据丢

失。


同步模式:针对ADC多个同时触发的场景,交替触发等,相互之间配合使用


6:数据对齐

数据的对齐方式有,数据左对齐,数据右对齐

数据右对齐就是高位补0

数据左对齐就是低位补0


7:转换时间

采样的时间可以等同为:采样 + 保持, 对应的ADC周期可以等同为量化加编码。

8:校准

 转换精度

9:初始化ADC配置

单次连续非非扫描模式初始化

使用ADC0进行配置,PC2这个通道对应我们开发板上的可调电阻模块。

初始化函数

#include <stdint.h>
#include <stdio.h>
#include "gd32f30x.h"
#include "delay.h"

static void GpioInit(void)
{
	rcu_periph_clock_enable(RCU_GPIOC);
	gpio_init(GPIOC,GPIO_MODE_AIN,GPIO_OSPEED_10MHZ,GPIO_PIN_2);
}

// 初始化ADC
static void AdcInit(void)
{
	// 使能时钟
	rcu_periph_clock_enable(RCU_ADC0);
	// 设置分频系数,设置分频系数为6分频,120M的时钟频率6分频后得到的时钟主频为20
	rcu_adc_clock_config(RCU_CKADC_CKAPB2_DIV6);
	// 设置独立模式
	adc_mode_config(ADC_MODE_FREE);
	// 设置单次模式
	adc_special_function_config(ADC0,ADC_CONTINUOUS_MODE,DISABLE);
	// 设置数据对齐
	adc_data_alignment_config(ADC0,ADC_DATAALIGN_RIGHT);
	// 设置和转换通道个数
	adc_channel_length_config(ADC0,ADC_REGULAR_CHANNEL,1);
	// 设置转换的通道以及所处在的序列位置,PC2对应的通道为12,放在寄存器序列0中,239.5个周期
	adc_regular_channel_config(ADC0,0,ADC_CHANNEL_12,ADC_SAMPLETIME_239POINT5);
	// 设置选择哪一个外部触发源,使用软件的方式触发
	adc_external_trigger_source_config(ADC0,ADC_REGULAR_CHANNEL,ADC0_1_2_EXTTRIG_REGULAR_NONE);
	// 使能外部触发,规则组触发,使能
	adc_external_trigger_config(ADC0,ADC_REGULAR_CHANNEL,ENABLE);
	// 使能ADC
	adc_enable(ADC0);
	DelayNus(50);
	// 内部校准
	adc_calibration_enable(ADC0);

	
}


void VresDrvInit(void)
{
	GpioInit();
	AdcInit();
}


/*
   *****************************************
   * @brief :  获取ADC数据
   * @param :  
   * @param : 
   * @retval: 
   *****************************************
*/
uint16_t GetAdcVal(void)
{
	// 使能软件触发,每一次转换都需要软件触发一次
	adc_software_trigger_enable(ADC0, ADC_REGULAR_CHANNEL);
	// 判断ADC标志位是否置位
	while(!adc_flag_get(ADC0, ADC_FLAG_EOC));
	// 读取ADC规则组数据并返回
	return adc_regular_data_read(ADC0);
}

/*
   *****************************************
   * @brief :  测试函数
   * @param :  
   * @param : 
   * @retval: 
   *****************************************
*/
void VresDrvTest(void)
{
	// 获取返回的数据
	uint16_t AdcVal = GetAdcVal();
	// 对数据进行转换,将数据转换为电压的值
	float Voltage = (float)AdcVal / 4095 * 3.3f;
	// 打印输出电压数据
	printf("AdcVal = %d, Voltage = %.1f.\n",AdcVal,Voltage);
	// 延时1s
	DelayNms(1000);
}




 对代码相关参数的解释:

可以有如下的理解:就是把系统的采样和保持理解为采样时间+量化和编码理解为ADC的周期

ADC电压转换

一个12位的模数转换器(ADC)的分辨率意味着它可以区分输入信号的2^12个不同的级别。这是因为二进制数的每一位都可以有两个状态(0或1),所以n位的ADC可以表示2^n个不同的值。

对于12位的ADC,其分辨率为2^12,即4096个不同的数值级别。这意味着如果ADC的全量程范围是从0V到Vref(参考电压),那么每一个数值级别代表的电压增量为Vref除以4096。

例如,如果Vref是3.3V,则每个数值级别代表的电压为: 3.3 V4096≈0.805 mV40963.3V​≈0.805mV

因此,12位分辨率的ADC可以提供相当精细的电压测量,最小可以分辨出大约0.805毫伏的变化。这种精度对于许多应用来说已经足够,尤其是在需要高精度测量的场合,如精密仪器、传感器信号采集和控制系统中。

初始化配置头文件

#ifndef _VRES_DRV_H_
#define _VRES_DRV_H_

/**
***********************************************************
* @brief ADC硬件初始化
* @param
* @return 
***********************************************************
*/
void VresDrvInit(void);
void VresDrvTest(void);
#endif

主函数调用

 实验结果:

10:单次连续模式

注:使用单次连续模式只需要软件触发一次无需每次都使用软件进行触发,触发成功后转换后的数据会自动的存储到数据寄存器中,无需每次都判断EOC标志位,直到转换完成后判断一次EOC标志位即可。

#include <stdint.h>
#include <stdio.h>
#include "gd32f30x.h"
#include "delay.h"

static void GpioInit(void)
{
	rcu_periph_clock_enable(RCU_GPIOC);
	gpio_init(GPIOC,GPIO_MODE_AIN,GPIO_OSPEED_10MHZ,GPIO_PIN_2);
}

// 初始化ADC
static void AdcInit(void)
{
	// 使能时钟
	rcu_periph_clock_enable(RCU_ADC0);
	// 设置分频系数,设置分频系数为6分频,120M的时钟频率6分频后得到的时钟主频为20
	rcu_adc_clock_config(RCU_CKADC_CKAPB2_DIV6);
	// 设置独立模式
	adc_mode_config(ADC_MODE_FREE);
	// 设置连续转换模式
	adc_special_function_config(ADC0,ADC_CONTINUOUS_MODE,ENABLE);
	// 设置数据对齐
	adc_data_alignment_config(ADC0,ADC_DATAALIGN_RIGHT);
	// 设置和转换通道个数
	adc_channel_length_config(ADC0,ADC_REGULAR_CHANNEL,1);
	// 设置转换的通道以及所处在的序列位置,PC2对应的通道为12,放在寄存器序列0中,239.5个周期
	adc_regular_channel_config(ADC0,0,ADC_CHANNEL_12,ADC_SAMPLETIME_239POINT5);
	// 设置选择哪一个外部触发源,使用软件的方式触发
	adc_external_trigger_source_config(ADC0,ADC_REGULAR_CHANNEL,ADC0_1_2_EXTTRIG_REGULAR_NONE);
	// 使能外部触发,规则组触发,使能
	adc_external_trigger_config(ADC0,ADC_REGULAR_CHANNEL,ENABLE);
	// 使能ADC
	adc_enable(ADC0);
	DelayNus(50);
	// 内部校准
	adc_calibration_enable(ADC0);
	// 使能软件触发,每次转换完成会将数据放到数据寄存器中
	adc_software_trigger_enable(ADC0, ADC_REGULAR_CHANNEL);
	
}


void VresDrvInit(void)
{
	GpioInit();
	AdcInit();
}


/*
   *****************************************
   * @brief :  获取ADC数据
   * @param :  
   * @param : 
   * @retval: 
   *****************************************
*/
uint16_t GetAdcVal(void)
{

	// 判断ADC标志位是否置位
	while(!adc_flag_get(ADC0, ADC_FLAG_EOC));
	// 读取ADC规则组数据并返回
	return adc_regular_data_read(ADC0);
}

/*
   *****************************************
   * @brief :  测试函数
   * @param :  
   * @param : 
   * @retval: 
   *****************************************
*/
void VresDrvTest(void)
{
	// 获取返回的数据
	uint16_t AdcVal = GetAdcVal();
	// 对数据进行转换,将数据转换为电压的值
	float Voltage = (float)AdcVal / 4095 * 3.3f;
	// 打印输出电压数据
	printf("AdcVal = %d, Voltage = %.1f.\n",AdcVal,Voltage);
	// 延时1s
	DelayNms(1000);
}

代码做了一定程序的小修改,以下是想啊滚修改的位置

 使能触发的位置放到初始化的位置处


 11 :多通道扫描模式

多通道扫描模式配合DMA初始化


原理图与引脚位置:


初始化函数:

void VresDrvInit(void)
{
	// 初始化GPIO
	GpioInit();
	// 初始化ADC
	AdcInit();
	// 初始化DMA
	DmaInit();
}

GPIO 初始化(引脚初始化)

static void GpioInit(void)
{
	// RCU 开启时钟
	rcu_periph_clock_enable(RCU_GPIOC);
	// 引脚初始化,对应的是可调电阻的引脚
	gpio_init(GPIOC,GPIO_MODE_AIN,GPIO_OSPEED_10MHZ,GPIO_PIN_2);
	// 引脚初始化,对应的是温度传感器的引脚
	gpio_init(GPIOC, GPIO_MODE_AIN, GPIO_OSPEED_10MHZ, GPIO_PIN_3);
}

初始化ADC

 注:

阅读英文数据手册可以得知对应的通道为

ADC012_IN13 这个标识符通常在微控制器或数字信号处理器的编程环境中出现,特别是在涉及到模拟到数字转换器(Analog-to-Digital Converter,简称ADC)的配置时。这个标识符的具体含义可以根据不同的芯片制造商和型号有所不同,但通常情况下,它代表了ADC模块的某一通道的配置或选择。

让我们拆解一下这个标识符:

  • ADC012:这可能是指ADC模块的编号或版本,这里的“012”可能只是一个序号,表示这是第12个ADC模块(在某些复杂的系统中,可能会有多个ADC),或者它只是命名的一部分,没有实际的意义。但在大多数情况下,它可能仅仅是一个模块的代号,比如ADC0。

  • IN13:这通常指的是ADC输入通道的编号。在这里,“IN”代表输入,“13”代表这是第13个输入通道。这意味着当使用ADC012_IN13时,你正在配置或引用ADC模块的第13个输入通道,以便将模拟信号转换为数字信号。

综合来看,ADC012_IN13 大概表示的是:“选择ADC模块(可能是ADC0)的第13个输入通道来进行模拟信号的数字化”。在实际应用中,这可能涉及到设置寄存器值,以指定ADC应该从哪个输入引脚采集信号,然后进行转换。

static void AdcInit(void)
{
	// 使能时钟
	rcu_periph_clock_enable(RCU_ADC0);
	// 设置分频系数,设置分频系数为6分频,120M的时钟频率6分频后得到的时钟主频为20
	rcu_adc_clock_config(RCU_CKADC_CKAPB2_DIV6);
	// 设置独立模式
	adc_mode_config(ADC_MODE_FREE);
	// 设置连续转换模式
	adc_special_function_config(ADC0,ADC_CONTINUOUS_MODE,ENABLE);
	// 设置扫描模式
	adc_special_function_config(ADC0,ADC_SCAN_MODE,ENABLE);
	// 设置数据对齐
	adc_data_alignment_config(ADC0,ADC_DATAALIGN_RIGHT);
	// 设置和转换通道个数
	adc_channel_length_config(ADC0,ADC_REGULAR_CHANNEL,2);
	// 设置转换的通道以及所处在的序列位置,PC2对应的通道为12,放在寄存器序列0中,239.5个周期
	adc_regular_channel_config(ADC0,0,ADC_CHANNEL_12,ADC_SAMPLETIME_239POINT5);
	adc_regular_channel_config(ADC0,1,ADC_CHANNEL_13,ADC_SAMPLETIME_239POINT5);
	// 设置选择哪一个外部触发源,使用软件的方式触发
	adc_external_trigger_source_config(ADC0,ADC_REGULAR_CHANNEL,ADC0_1_2_EXTTRIG_REGULAR_NONE);
	// 使能外部触发,规则组触发,使能
	adc_external_trigger_config(ADC0,ADC_REGULAR_CHANNEL,ENABLE);
	/* 使能ADC的DMA功能;*/ 
	adc_dma_mode_enable(ADC0);
	// 使能ADC
	adc_enable(ADC0);
	DelayNus(50);
	// 内部校准
	adc_calibration_enable(ADC0);
	// 使能软件触发,每次转换完成会将数据放到数据寄存器中
	adc_software_trigger_enable(ADC0, ADC_REGULAR_CHANNEL);
}

初始化DMA转运

static void DmaInit(void)
{
	// 使能DMA时钟
	rcu_periph_clock_enable(RCU_DMA0);
	// 复位DMA通道
	dma_deinit(DMA0,DMA_CH0);
	
	// DMA 结构体初始化
	dma_parameter_struct dmaStruct;
	dma_struct_para_init(&dmaStruct);
	// 配置DMA传输方向,片上外设到内存
	dmaStruct.direction = DMA_PERIPHERAL_TO_MEMORY;
	// 配置数据的源地址
	dmaStruct.periph_addr = ADC0_RDATA_ADDR;
	// 配置源地址是固定的地址还是增长的地址,此处表示源地址的固定的
	dmaStruct.periph_inc = DMA_PERIPH_INCREASE_DISABLE;
	// 配置源数据传输位宽,此处使用16位,因为ADC的分辨率为12
	dmaStruct.periph_width = DMA_PERIPHERAL_WIDTH_16BIT;
	
	// 配置数据目的地址
	dmaStruct.memory_addr = (uint32_t)g_adcVal;
	// 配置数据的目的地址是固定的还是增长的,此处配置为增长的
	dmaStruct.memory_inc = DMA_MEMORY_INCREASE_ENABLE;
	// 配置目的数据传输的位宽
	dmaStruct.memory_width = DMA_MEMORY_WIDTH_16BIT;
	/* 配置数据传输最大次数;*/ 
	dmaStruct.number = 2;
	// 配置DMA数据通道的优先级
	dmaStruct.priority = DMA_PRIORITY_HIGH;
	dma_init(DMA0, DMA_CH0, &dmaStruct);
	
	// 使能DMA循环模式搬运数据
	dma_circulation_enable(DMA0,DMA_CH0);
	// 使能DMA通道
	dma_channel_enable(DMA0, DMA_CH0);
}

测试函数

/*
   *****************************************
   * @brief :  测试函数
   * @param :  
   * @param : 
   * @retval: 
   *****************************************
*/
void VresDrvTest(void)
{
	printf("CH12 adcVal = %d.\n",g_adcVal[0]);
	printf("CH13 adcVal = %d.\n",g_adcVal[1]);
	// 延时1s
	DelayNms(1000);	
}

完整代码

 注:

#include <stdint.h>
#include <stdio.h>
#include "gd32f30x.h"
#include "delay.h"

// 宏定义ADC的地址
#define ADC0_RDATA_ADDR (ADC0 + 0x4C)
// 创建静态数组存放DMA搬运过来的数据,也就是DMA的目的地址
static uint16_t g_adcVal[2];

static void GpioInit(void)
{
	// RCU 开启时钟
	rcu_periph_clock_enable(RCU_GPIOC);
	// 引脚初始化,对应的是可调电阻的引脚
	gpio_init(GPIOC,GPIO_MODE_AIN,GPIO_OSPEED_10MHZ,GPIO_PIN_2);
	// 引脚初始化,对应的是温度传感器的引脚
	gpio_init(GPIOC,GPIO_MODE_AIN,GPIO_OSPEED_10MHZ,GPIO_PIN_3);
}


static void AdcInit(void)
{
	// 使能时钟
	rcu_periph_clock_enable(RCU_ADC0);
	// 设置分频系数,设置分频系数为6分频,120M的时钟频率6分频后得到的时钟主频为20
	rcu_adc_clock_config(RCU_CKADC_CKAPB2_DIV6);
	// 设置独立模式
	adc_mode_config(ADC_MODE_FREE);
	// 设置连续转换模式
	adc_special_function_config(ADC0,ADC_CONTINUOUS_MODE,ENABLE);
	// 设置扫描模式
	adc_special_function_config(ADC0,ADC_SCAN_MODE,ENABLE);
	// 设置数据对齐
	adc_data_alignment_config(ADC0,ADC_DATAALIGN_RIGHT);
	// 设置和转换通道个数
	adc_channel_length_config(ADC0,ADC_REGULAR_CHANNEL,2);
	// 设置转换的通道以及所处在的序列位置,PC2对应的通道为12,放在寄存器序列0中,239.5个周期
	adc_regular_channel_config(ADC0,0,ADC_CHANNEL_12,ADC_SAMPLETIME_239POINT5);
	adc_regular_channel_config(ADC0,1,ADC_CHANNEL_13,ADC_SAMPLETIME_239POINT5);
	// 设置选择哪一个外部触发源,使用软件的方式触发
	adc_external_trigger_source_config(ADC0,ADC_REGULAR_CHANNEL,ADC0_1_2_EXTTRIG_REGULAR_NONE);
	// 使能外部触发,规则组触发,使能
	adc_external_trigger_config(ADC0,ADC_REGULAR_CHANNEL,ENABLE);
	/* 使能ADC的DMA功能;*/ 
	adc_dma_mode_enable(ADC0);
	// 使能ADC
	adc_enable(ADC0);
	DelayNus(50);
	// 内部校准
	adc_calibration_enable(ADC0);
	// 使能软件触发,每次转换完成会将数据放到数据寄存器中
	adc_software_trigger_enable(ADC0, ADC_REGULAR_CHANNEL);
}

static void DmaInit(void)
{
	// 使能DMA时钟
	rcu_periph_clock_enable(RCU_DMA0);
	// 复位DMA通道
	dma_deinit(DMA0,DMA_CH0);
	
	// DMA 结构体初始化
	dma_parameter_struct dmaStruct;
	dma_struct_para_init(&dmaStruct);
	// 配置DMA传输方向,片上外设到内存
	dmaStruct.direction = DMA_PERIPHERAL_TO_MEMORY;
	// 配置数据的源地址
	dmaStruct.periph_addr = ADC0_RDATA_ADDR;
	// 配置源地址是固定的地址还是增长的地址,此处表示源地址的固定的
	dmaStruct.periph_inc = DMA_PERIPH_INCREASE_DISABLE;
	// 配置源数据传输位宽,此处使用16位,因为ADC的分辨率为12
	dmaStruct.periph_width = DMA_PERIPHERAL_WIDTH_16BIT;
	
	// 配置数据目的地址
	dmaStruct.memory_addr = (uint32_t)g_adcVal;
	// 配置数据的目的地址是固定的还是增长的,此处配置为增长的
	dmaStruct.memory_inc = DMA_MEMORY_INCREASE_ENABLE;
	// 配置目的数据传输的位宽
	dmaStruct.memory_width = DMA_MEMORY_WIDTH_16BIT;
	/* 配置数据传输最大次数;*/ 
	dmaStruct.number = 2;
	// 配置DMA数据通道的优先级
	dmaStruct.priority = DMA_PRIORITY_HIGH;
	dma_init(DMA0, DMA_CH0, &dmaStruct);
	
	// 使能DMA循环模式搬运数据
	dma_circulation_enable(DMA0,DMA_CH0);
	// 使能DMA通道
	dma_channel_enable(DMA0, DMA_CH0);
}

void VresDrvInit(void)
{
	// 初始化GPIO
	GpioInit();
	// 初始化ADC
	AdcInit();
	// 初始化DMA
	DmaInit();
}

/*
   *****************************************
   * @brief :  测试函数
   * @param :  
   * @param : 
   * @retval: 
   *****************************************
*/
void VresDrvTest(void)
{
	printf("CH12 adcVal = %d.\n",g_adcVal[0]);
	printf("CH13 adcVal = %d.\n",g_adcVal[1]);
	// 延时1s
	DelayNms(1000);	
}


测试结果

后记:


...


Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐