网上ADC相关的文章主要是STM32 ADC,而GD32 ADC较少;虽然两者基本一致,但是使用GD32 ADC时参考GD32 ADC的资料最方便。因此本文介绍GF32 ADC的常用功能 以及 代码实现,包括:
ADC 采样
ADC DMA采样
代码示例包括以上两种实现的 软件/硬件触发 多通道/单通道 连续转换/单次转换等。
GD32F4xx MCU ADC 介绍
1、GD32F4xx ADC
MCU 片上集成了 12 位逐次逼近式模数转换器模块(ADC),可以采样来自于 16 个外部通道和 2 个内部通道和一个电池电压(VBAT)通道的模拟信号。
1.1 主要特征
可配置12位、10位、8位、6位分辨率;
ADC采样率:12位分辨率为2.6MSPs,10位分辨率为3.0 MSPs。分辨率越低,转换越快;
(Million Samples per Second)
自校准时间:131个ADC时钟周期;(每次采样为一个ADC时钟周期,比如40MHz时钟,校准时间约3.275us)
可编程采样时间
可配置数据对齐方式;
支持规则通道数据转换的DMA请求;(有些文档/程序中 规则组 又叫 常规组)
模拟输入通道:
1) 16个外部输入通道;
2) 1个内部温度传感器通道;
3) 1个内部参考电压输入通道;
4) 1个外部监测电池Vbat供电引脚输入通道;
转换触发: 软件触发 和 硬件触发
1.2 ADC框图
1.3 ADC校准
ADC带有一个前置校准功能。在校准期间,ADC计算一个校准系数,这个系数直到ADC下次掉电才无效。在校准期间,不能使用ADC必须等到校准完成。在AD转换之前应执行校准操作,一般放在初始化里执行。
当ADC运行条件改变(如,VDDA、VREFP、温度等条件变化,建议重新执行一次校准操作。)
1.4 ADC时钟
ADC CLK 与 CK_AHB、PLCK2 时钟保持同步。ADC最大时钟频率为40MHz。ADC 时钟可以在 RCU 时钟控制器中进行分配和配置。
ADC CLK 分频前 CK_AHB、PLCK2 如何设置,可以参考超全面GD32F4XX 系统时钟与AHB/APB1/APB2时钟配置_gd32 apb1和系统时钟的关系-CSDN博客
1.5 规则组和注入组
规则组又称作:常规组、常规序列
ADC转换可以组织成2组: 一个规则组通道和一个注入组通道。
1)规则组最多由16个转换组成。而注入组最多由4个转换组成。
2)注入组通道可以打断规则组通道
注入通道有两种模式:
① 触发注入模式: 在规则组通道转换期间如果软件触发或外部触发发生,ADC取消当前转换,启动触发注入转换,注入通道序列被以单次扫描方式进行转换。注入通道转换结束后,规则组转换从上次被取消的转换处重新开始。
② 自动注入模式:在规则通道之后,注入通道被自动转换。
1.6 转换模式
假设配置了5个通道 ch0、1、2、3、4
单次转换
开启扫描 : 每触发一次,转换一次 ch0、1、2、3、4
不开启扫描 :每触发一次,转换一次ch0
连续转换
开启扫描 :触发后持续转换 ch0、1、2、3、4
不开启扫描: 触发后持续转换 ch0
1.7 采样时间
ADC 使用多个 CK_ADC 周期对输入电压采样。每个通道可以用不同的采样时间。在 12 位分辨率 的情况下,总转换时间=采样时间+12 个 CK_ADC 周期。 例如: CK_ADC = 40MHz ,采样时间为 3 个周期,那么总的转换时间为:“3+12”个 CK_ADC 周期, 即 0.375us。
1.8 ADC使能
ADC 使 能后需等待T SU 时间后才能采样,T SU 数值详见芯片数据手册。 实际使用一般延时 1ms 。
1.9 触发方式
可以实时修改外部触发选择,在修改期间不会出现触发事件。后文会依次实现软件触发与外部触发。
软件触发
使用库函数/寄存器使能触发
外部触发(硬件触发)
触发模式
双边沿触发
下降沿触发
上升沿触发
失能
触发源
定时器触发
外部中断触发
2.0 对齐方式
常用的12位分辨率,分为左对齐与右对齐;
参考GD32F4XX用户手册的图片如下:
其他分辨率的存储如下:
6 位分辨率的数据存储模式不同于 12 位/10 位/8 位分辨率数据存储模式
2.1 DMA请求
DMA 请求用于常规序列多个通道的转换结果。ADC 在常规序列一个通道转换结束后产生一个 DMA 请求,DMA 接受到请求后可 以将转换的数据从 ADC_RDATA 寄存器传输到用户指定的目的地址。
2.1 其他
ADC同步模式、间断转换、溢出检测、模拟看门狗、内部通道用的比较,之后会补充更新。
代码
0、前言
1.头文件
//芯片头文件
#include "gd32f4xx.h"
//延时函数头文件
#include "systick.h"
不管是哪种ADC的配置,都需要包含以上头文件
gd32f4xx.h 芯片的头文件,包含内核、芯片时钟、各种外设的标准库等的头文件,芯片宏定义等等。
systick.h 包含毫秒级的延时函数,ADC的配置过程需要毫秒延时,可以替换为自己工程的延时文件,比如操作系统的头文件。
这两个就够了,如果有其它需求,比如stdio.h等,根据自己需求添加。
2、标准库
大家到这一步的时候,相信都已经有搭建好的工程,所以系统文件、启动文件等默认已经搭建好了。除此之外在写ADC驱动之前,需要先添加ADC的标注库文件,所涉及的如下:
gd32f4xx_adc.c
gd32f4xx_gpio.c
gd32f4xx_rcu.c
gd32f4xx_dma.c
gd32f4xx_timer.c
gd32f4xx_misc.c
gd32f4xx_exti.c
前三个文件是所以ADC配置都需要添加的文件,4-7是使用到外设时才添加。
1、ADC + DMA 多通道 常规组 扫描 采样 单词转换/连续转换
软件触发
RCU、GPIO、DMA初始化
static uint32_t S_uAdcData[ADC_CHANNEL_ALL];
/*
* 函数名:ADC0_RCU_Config
* 描述
* 输入 :无
* 输出 :无
* 调用 :内部调用
*/
static void ADC0_RCU_Config(void)
{
/* enable GPIOC clock */
rcu_periph_clock_enable(ADC_VOLT_RCU);
rcu_periph_clock_enable(ADC_X_RCU);
rcu_periph_clock_enable(ADC_Y_RCU);
/* enable ADC clock */
rcu_periph_clock_enable(RCU_ADC0);
/* enable DMA clock */
rcu_periph_clock_enable(RCU_DMA1);
/* config ADC clock */
adc_clock_config(ADC_ADCCK_PCLK2_DIV8);
}
/*
* 函数名:ADC0_GPIO_Config
* 描述
* 输入 :无
* 输出 :无
* 调用 :内部调用
*/
static void ADC0_GPIO_Config(void)
{
gpio_mode_set(ADC_VOLT_PORT, GPIO_MODE_ANALOG, GPIO_PUPD_NONE, ADC_VOLT_PIN);
gpio_mode_set(ADC_X_PORT, GPIO_MODE_ANALOG, GPIO_PUPD_NONE, ADC_X_PIN);
gpio_mode_set(ADC_Y_PORT, GPIO_MODE_ANALOG, GPIO_PUPD_NONE, ADC_Y_PIN);
}
/*
* 函数名:ADC0_DMA_Config
* 描述
* 输入 :无
* 输出 :无
* 调用 :内部调用
*/
static void ADC0_DMA_Config(void)
{
/* ADC_DMA_channel configuration */
dma_single_data_parameter_struct dma_single_data_parameter;
/* ADC DMA_channel configuration */
dma_deinit(DMA1, DMA_CH0);
/* initialiLe DMA single data mode */
dma_single_data_parameter.periph_addr = (uint32_t)(&ADC_RDATA(ADC0));
dma_single_data_parameter.periph_inc = DMA_PERIPH_INCREASE_DISABLE;
dma_single_data_parameter.memory0_addr = (uint32_t)(S_uAdcData);
dma_single_data_parameter.memory_inc = DMA_MEMORY_INCREASE_ENABLE;
dma_single_data_parameter.periph_memory_width = DMA_PERIPH_WIDTH_16BIT;
dma_single_data_parameter.direction = DMA_PERIPH_TO_MEMORY;
dma_single_data_parameter.number = ADC_CHANNEL_ALL;
dma_single_data_parameter.priority = DMA_PRIORITY_HIGH;
dma_single_data_mode_init(DMA1, DMA_CH0, &dma_single_data_parameter);
dma_channel_subperipheral_select(DMA1, DMA_CH0, DMA_SUBPERI0);
/* enable DMA circulation mode */
dma_circulation_enable(DMA1, DMA_CH0);
/* enable DMA channel */
dma_channel_enable(DMA1, DMA_CH0);
}
ADC 单次转换的的配置
/*
* 函数名:ADC0_Config
* 描述
* 输入 :无
* 输出 :无
* 调用 :内部调用
*/
static void ADC0_Config( void )
{
/* ADC mode config */
adc_sync_mode_config(ADC_SYNC_MODE_INDEPENDENT);
/* ADC contineous function disable */
adc_special_function_config(ADC0, ADC_CONTINUOUS_MODE, DISABLE);
/* ADC scan mode disable */
adc_special_function_config(ADC0, ADC_SCAN_MODE, ENABLE);
/* ADC data alignment config */
adc_data_alignment_config(ADC0, ADC_DATAALIGN_RIGHT);
/* ADC channel length config */
adc_channel_length_config(ADC0, ADC_REGULAR_CHANNEL, ADC_CHANNEL_ALL);
/* ADC trigger config */
adc_external_trigger_source_config(ADC0, ADC_REGULAR_CHANNEL, ADC_EXTTRIG_REGULAR_T0_CH0);
adc_external_trigger_config(ADC0, ADC_REGULAR_CHANNEL, EXTERNAL_TRIGGER_DISABLE);
/* ADC DMA function enable */
adc_dma_request_after_last_enable(ADC0);
adc_dma_mode_enable(ADC0);
/* enable ADC interface */
adc_enable(ADC0);
/* wait for ADC stability */
delay_1ms(1);
/* ADC calibration and reset calibration */
adc_calibration_enable(ADC0);
}
/*
* 函数名:ADC0_Init
* 描述
* 输入 :无
* 输出 :无
* 调用 :内部调用
*/
static void ADC0_Init(void)
{
ADC0_RCU_Config();
ADC0_GPIO_Config();
ADC0_DMA_Config();
ADC0_Config();
}
/*
* 函数名:ADC0_Init
* 描述
* 输入 :无
* 输出 :无
* 调用 :内部调用
*/
static void ADC0_CannelSample()
{
/* ADC routine channel config */
adc_regular_channel_config(ADC0, 0, ADC_VOLT_CHANNEL, ADC_SAMPLETIME_15);
adc_regular_channel_config(ADC0, 1, ADC_X_CHANNEL, ADC_SAMPLETIME_15);
adc_regular_channel_config(ADC0, 2, ADC_Y_CHANNEL, ADC_SAMPLETIME_15);
/* ADC software trigger enable */
adc_software_trigger_enable(ADC0, ADC_REGULAR_CHANNEL); // ADC软件触发使能
}
ADC 连续转换的的配置
/*
* 函数名:ADC0_Config
* 描述
* 输入 :无
* 输出 :无
* 调用 :内部调用
*/
static void ADC0_Config( void )
{
/* ADC mode config */
adc_sync_mode_config(ADC_SYNC_MODE_INDEPENDENT);
/* ADC contineous function disable */
adc_special_function_config(ADC0, ADC_CONTINUOUS_MODE, ENABLE);
/* ADC scan mode disable */
adc_special_function_config(ADC0, ADC_SCAN_MODE, ENABLE);
/* ADC data alignment config */
adc_data_alignment_config(ADC0, ADC_DATAALIGN_RIGHT);
/* ADC channel length config */
adc_channel_length_config(ADC0, ADC_REGULAR_CHANNEL, ADC_CHANNEL_ALL);
/* ADC routine channel config */
adc_routine_channel_config(ADC0, 0, ADC_VOLT_CHANNEL, ADC_SAMPLETIME_15);
adc_routine_channel_config(ADC0, 1, ADC_X_CHANNEL, ADC_SAMPLETIME_15);
adc_routine_channel_config(ADC0, 2, ADC_Y_CHANNEL, ADC_SAMPLETIME_15);
/* ADC trigger config */
adc_external_trigger_source_config(ADC0, ADC_REGULAR_CHANNEL, ADC_EXTTRIG_REGULAR_T0_CH0);
adc_external_trigger_config(ADC0, ADC_REGULAR_CHANNEL, EXTERNAL_TRIGGER_DISABLE);
/* ADC DMA function enable */
adc_dma_request_after_last_enable(ADC0);
adc_dma_mode_enable(ADC0);
/* enable ADC interface */
adc_enable(ADC0);
/* wait for ADC stability */
delay_1ms(1);
/* ADC calibration and reset calibration */
adc_calibration_enable(ADC0);
/* enable ADC software trigger */
adc_software_trigger_enable(ADC0, ADC_ROUTINE_CHANNEL);
}
/*
* 函数名:ADC0_Init
* 描述
* 输入 :无
* 输出 :无
* 调用 :内部调用
*/
static void ADC0_Init(void)
{
ADC0_RCU_Config();
ADC0_GPIO_Config();
ADC0_DMA_Config();
ADC0_Config();
}
ADC DMA 多通道采样 对外接口
/* 对外接口 */
/*
*@brief ADC初始化
*@retval 初始化状态
*/
void ADC_Init(void)
{
ADC0_Init();
for(int8_t i=0;i< ADC_CHANNEL_ALL;i++)
{
S_uAdcData = 0;
}
}
/*
*@brief ADC值读取 单次转换/连续转化
*@retval ADC值
*/
uint16_t ADC_GetValue(uint16_t Chn)
{
//单次转换保留该行,连续转化注释该行代码
ADC0_CannelSample();
return S_uAdcData[Chn];
}
以上配置所使用的相关宏定义如下,将其添加到头文件,替换为自己芯片的资源,即可使用以上ADC DMA 多通道 单次转换/连续转换 采样的配置。
/* --------------- ADC0 DMA 定时器触发 --------------- */
//电源电压
/* PC1 PA1 ADC01_IN15*/
#define ADC_VOLT_RCU RCU_GPIOC
#define ADC_VOLT_PORT GPIOC
#define ADC_VOLT_PIN GPIO_PIN_5
#define ADC_VOLT_ADC_RCU RCU_ADC0
#define ADC_VOLT_ADC ADC0
#define ADC_VOLT_CHANNEL ADC_CHANNEL_15
//遥杆
/* PC1 ADC012_IN11*/
#define ADC_X_RCU RCU_GPIOC
#define ADC_X_PORT GPIOC
#define ADC_X_PIN GPIO_PIN_1
#define ADC_X_ADC_RCU RCU_ADC0
#define ADC_X_ADC ADC0
#define ADC_X_CHANNEL ADC_CHANNEL_11
/*PA1 ADC012_IN1*/
#define ADC_Y_RCU RCU_GPIOA
#define ADC_Y_PORT GPIOA
#define ADC_Y_PIN GPIO_PIN_1
#define ADC_Y_ADC_RCU RCU_ADC0
#define ADC_Y_ADC ADC0
#define ADC_Y_CHANNEL ADC_CHANNEL_1
/* --------------- ADC0 DMA 定时器触发 --------------- */
//ADC通道定义
#define CMD_ADC_VOLT 0 //电源采集ADC
#define CMD_ADC_X 1 //遥感X
#define CMD_ADC_Y 2 //遥感Y
#define ADC_CHANNEL_ALL (3)
2、ADC 多通道 常规组 不扫描 采样 单次转换/连续转换
软件触发
RCU GPIO的初始化时,根据自己芯片的资源,替换对应时钟与引脚,多余的通道数量也删除。
/*!
\brief configure the different system clocks
\param[in] none
\param[out] none
\retval none
*/
void rcu_config(void)
{
/* enable GPIOA clock */
rcu_periph_clock_enable(RCU_GPIOA);
/* enable ADC clock */
rcu_periph_clock_enable(RCU_ADC0);
/* config ADC clock */
adc_clock_config(ADC_ADCCK_PCLK2_DIV8);
}
/*!
\brief configure the GPIO peripheral
\param[in] none
\param[out] none
\retval none
*/
void gpio_config(void)
{
/* config the GPIO as analog mode */
gpio_mode_set(GPIOA, GPIO_MODE_ANALOG, GPIO_PUPD_NONE, GPIO_PIN_1);
gpio_mode_set(GPIOA, GPIO_MODE_ANALOG, GPIO_PUPD_NONE, GPIO_PIN_2);
gpio_mode_set(GPIOA, GPIO_MODE_ANALOG, GPIO_PUPD_NONE, GPIO_PIN_3);
gpio_mode_set(GPIOA, GPIO_MODE_ANALOG, GPIO_PUPD_NONE, GPIO_PIN_4);
}
ADC配置
因为是软件触发 一般是把 连续转换 失能,每次采集时触发一次采样
adc_special_function_config(ADC0, ADC_CONTINUOUS_MODE, DISABLE);
前面的示例在ADC初始化后立刻使能,一直采样,是因为使用DMA搬运数据;而这里在需要采集的时候,才使能ADC采样,这里采样的频率,可以根据需求来设置,怎么样都可以,提供几种情况供大家参考,比如:
只在需要的时候采样一次
通过RTOS任务、裸机的while循环等,来连续采集,并且进行算法滤波。(ADC的各种滤波算法,找时间我也会整理一篇文章,希望大家多多关注哈~)
示例是多通道采集,但是每次只采集一个通道,所以扫描模式也配置为失能
adc_special_function_config(ADC0, ADC_SCAN_MODE, DISABLE);
/*!
\brief configure the ADC peripheral
\param[in] none
\param[out] none
\retval none
*/
void adc_config(void)
{
/* reset ADC */
adc_deinit();
/* ADC mode config */
adc_sync_mode_config(ADC_SYNC_MODE_INDEPENDENT);
/* ADC contineous function disable */
adc_special_function_config(ADC0, ADC_CONTINUOUS_MODE, DISABLE);
/* ADC scan mode disable */
adc_special_function_config(ADC0, ADC_SCAN_MODE, DISABLE);
/* ADC data alignment config */
adc_data_alignment_config(ADC0, ADC_DATAALIGN_RIGHT);
/* ADC channel length config */
adc_channel_length_config(ADC0, ADC_ROUTINE_CHANNEL, 1U);
/* ADC trigger config */
adc_external_trigger_source_config(ADC0, ADC_ROUTINE_CHANNEL, ADC_EXTTRIG_ROUTINE_T0_CH0);
adc_external_trigger_config(ADC0, ADC_ROUTINE_CHANNEL, EXTERNAL_TRIGGER_DISABLE);
/* enable ADC interface */
adc_enable(ADC0);
delay_1ms(1U);
/* ADC calibration and reset calibration */
adc_calibration_enable(ADC0);
}
对外接口
每次采样时输入采样通道,通过这个函数的定义就能查看到
/* ADC routine channel config */
adc_routine_channel_config(ADC0, 0U, channel, ADC_SAMPLETIME_15);
形式如下所示,如果为了减少耦合、输入方便等,可以在uint16_t adc_channel_sample(uint8_t channel)外再定义一个新的接口函数,新接口函数的通道由自己定义,函数内完成自定义通道到芯片通道的映射,再输入到uint16_t adc_channel_sample(uint8_t channel)中。
/* ADC channel definitions */
#define ADC_CHANNEL_0 ((uint8_t)0x00U) /*!< ADC channel 0 */
#define ADC_CHANNEL_1 ((uint8_t)0x01U) /*!< ADC channel 1 */
#define ADC_CHANNEL_2 ((uint8_t)0x02U) /*!< ADC channel 2 */
#define ADC_CHANNEL_3 ((uint8_t)0x03U) /*!< ADC channel 3 */
//...
/*!
\brief adc_init
\param[in] none
\param[out] none
\retval none
*/
void adc_init(void)
{
/* system clocks configuration */
rcu_config();
/* GPIO configuration */
gpio_config();
/* ADC configuration */
adc_config();
}
/*!
\brief ADC channel sample
\param[in] none
\param[out] none
\retval none
*/
uint16_t adc_channel_sample(uint8_t channel)
{
/* ADC routine channel config */
adc_routine_channel_config(ADC0, 0U, channel, ADC_SAMPLETIME_15);
/* ADC software trigger enable */
adc_software_trigger_enable(ADC0, ADC_ROUTINE_CHANNEL);
/* wait the end of conversion flag */
while(!adc_flag_get(ADC0, ADC_FLAG_EOC));
/* clear the end of conversion flag */
adc_flag_clear(ADC0, ADC_FLAG_EOC);
/* return regular channel sample value */
return (adc_routine_data_read(ADC0));
}
采样示例
adc的头文件中只需要声明对外接口中的两个函数,在需要的地方调用即可,以下为简单的示例
#include "drv_adc.h" //包含自己写的adc驱动头文件
uint16_t adc_value[4];
int main(void)
{
/* 其他系统配置 */
/* ADC 初始化 */
adc_init();
while(1)
{
adc_value[0]=adc_channel_sample(ADC_CHANNEL_1);
adc_value[1]=adc_channel_sample(ADC_CHANNEL_2);
adc_value[2]=adc_channel_sample(ADC_CHANNEL_3);
adc_value[3]=adc_channel_sample(ADC_CHANNEL_4);
}
}
定时器触发
待添加...
————————————————
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/LSL_Blogs/article/details/135258319
|
|