打印
[研电赛技术支持]

GD32F4XX的ADC配置

[复制链接]
174|0
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
网上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

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

2

主题

14

帖子

0

粉丝