前言:
...
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初始化
后记:
...