配置流程
一般使用ADC功能,都需要有以下几个步骤。
- 配置时钟
- 配置引脚
- 配置ADC模式
- 配置数据对齐方式
- 配置分辨率
- 配置采集通道
- 配置触发方式
- 使能ADC
关于ADC的一些常规配置步骤在ADC光敏电阻实验章节中已经讲过了,这里就不再赘述。下面从通道配置部分进行介绍。ADC通道配置就是在前面ADC配置的基础上加多几路ADC通道功能和DMA请求。
配置引脚
自行选择支持ADC采集功能的引脚,案例中选择PA4~7引脚,分别对应ADC的4~7通道。
开启GPIOA的时钟,初始化PA4~7引脚为模拟输入模式,无上下拉电阻。
rcu_periph_clock_enable(RCU_GPIOA);
gpio_mode_set(GPIOA, GPIO_MODE_ANALOG, GPIO_PUPD_NONE, GPIO_PIN_4);
gpio_mode_set(GPIOA, GPIO_MODE_ANALOG, GPIO_PUPD_NONE, GPIO_PIN_5);
gpio_mode_set(GPIOA, GPIO_MODE_ANALOG, GPIO_PUPD_NONE, GPIO_PIN_6);
gpio_mode_set(GPIOA, GPIO_MODE_ANALOG, GPIO_PUPD_NONE, GPIO_PIN_7);
2
3
4
5
6
配置ADC模式
ADC的模式有4种,分别是单次、连续、扫描和间断转换模式。具体的讲解见章节【GD32的ADC介绍】,这里我们配置为扫描模式+连续模式,这样就可以根据自己的需要,配置采集通道进行持续的依次扫描采集对应引脚的模拟信号。也可以根据需要配置为连续模式和间断模式。
扫描模式配置如下:
//使能连续转换模式
dc_special_function_config(ADC_CONTINUOUS_MODE, ENABLE);
//使能扫描模式
adc_special_function_config(ADC_SCAN_MODE, ENABLE);
2
3
4
配置采集通道
我们需要配置的是使用的通道和采集通道个数。这里我们使用的是常规通道组,并且需要转换4个通道。
/* ADC通道配置 */
adc_channel_length_config(ADC_ROUTINE_CHANNEL, 4);
/* 设置通道的采集顺序 */
adc_routine_channel_config(0, ADC_CHANNEL_4, ADC_SAMPLETIME_111POINT5);
adc_routine_channel_config(1, ADC_CHANNEL_5, ADC_SAMPLETIME_111POINT5);
adc_routine_channel_config(2, ADC_CHANNEL_6, ADC_SAMPLETIME_111POINT5);
adc_routine_channel_config(3, ADC_CHANNEL_7, ADC_SAMPLETIME_111POINT5);
2
3
4
5
6
7
8
根据以上的配置可以知道,当触发采集时,优先采集ADC通道4的数据,然后到ADC通道5,通道6,通道7。关于adc_routine_channel_config
函数,它是用于配置ADC的采集顺序以及采样时间的配置。原型如下:
void adc_routine_channel_config(uint8_t rank, uint8_t channel, uint32_t sample_time);
其一共有3个参数,各参数的含义如下:
rank
:指定 ADC 转换顺序排名,取值范围为 0 到 15,越小的值表示越先转换。channel
:指定要转换的 ADC 通道,可以选择ADC_CHANNEL_n
的形式,其中 n 取值为 0 到 18,表示不同的通道。sample_time
:指定采样时间,即每个转换的时间长度。可以选择ADC_SAMPLETIME_nPOINT5
的形式,其中 n 取值为 1、2、14、27、55、83、111、143或 479,分别表示n 个 ADC 时钟周期。
开启DMA请求
adc_dma_request_after_last_enable()
是一个 ADC (模数转换器)的配置选项,用于在转换完成后继续请求 DMA (直接存储器访问)。在这个选项被启用后,ADC会在转换完成后,立即向DMA模块发送请求,以便将转换结果直接传输到内存中这样可以减少处理器的负担,提高系统的效率。
//使能规则组通道每转换完成一个就发送一次DMA请求
adc_dma_request_after_last_enable();
2
配置完DMA请求后,还需要有一个使能操作,以开启ADC的DMA模式。
//使能ADC的DMA模式
adc_dma_mode_enable();
2
最终的ADC全部配置参数如下:
关于 ADCDmaConfig();
见后面章节。
void AdcConfig(void)
{
/* 使能引脚时钟 */
rcu_periph_clock_enable(RCU_GPIOA);
/* 使能ADC时钟 */
rcu_periph_clock_enable(RCU_ADC);
/* 配置ADC时钟 */
adc_clock_config(ADC_ADCCK_PCLK2_DIV4);
/* 配置GPIO为模拟模式 */
gpio_mode_set(GPIOA, GPIO_MODE_ANALOG, GPIO_PUPD_NONE, GPIO_PIN_4);
gpio_mode_set(GPIOA, GPIO_MODE_ANALOG, GPIO_PUPD_NONE, GPIO_PIN_5);
gpio_mode_set(GPIOA, GPIO_MODE_ANALOG, GPIO_PUPD_NONE, GPIO_PIN_6);
gpio_mode_set(GPIOA, GPIO_MODE_ANALOG, GPIO_PUPD_NONE, GPIO_PIN_7);
/* 使能连续转换模式 */
adc_special_function_config(ADC_CONTINUOUS_MODE, ENABLE);
/* 使能扫描模式 */
adc_special_function_config( ADC_SCAN_MODE, ENABLE);
/* 配置数据对齐方式 */
adc_data_alignment_config(ADC_DATAALIGN_RIGHT);
/* ADC通道配置 */
adc_channel_length_config(ADC_ROUTINE_CHANNEL, 4U);
/* 设置采集通道 */
adc_routine_channel_config(0U, ADC_CHANNEL_4, ADC_SAMPLETIME_111POINT5);
adc_routine_channel_config(1U, ADC_CHANNEL_5, ADC_SAMPLETIME_111POINT5);
adc_routine_channel_config(2U, ADC_CHANNEL_6, ADC_SAMPLETIME_111POINT5);
adc_routine_channel_config(3U, ADC_CHANNEL_7, ADC_SAMPLETIME_111POINT5);
/* ADC0设置为12位分辨率 */
adc_resolution_config( ADC_RESOLUTION_12B);
/* ADC外部触发禁用, 即只能使用软件触发 */
adc_external_trigger_config(ADC_ROUTINE_CHANNEL, EXTERNAL_TRIGGER_DISABLE);
/* 使能规则组通道每转换完成一个就发送一次DMA请求 */
adc_dma_request_after_last_enable();
/* 使能DMA请求 */
adc_dma_mode_enable();
/* 使能ADC功能 */
adc_enable();
/* 配置ADC的DMA功能 */
ADCDmaConfig();
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
ADC的DMA采集配置流程
一般使用ADC的DMA功能,都需要有以下几个步骤。
- 开启时钟
- 配置参数结构体
- 配置DMA模式
- 使能通道外设
开启时钟
老规矩,使用外设之前要先开启时钟,而GD32VW553HQM6只有一个DMA。
时钟使能如下:
/* 开启时钟 */
rcu_periph_clock_enable(RCU_DMA);
2
配置参数结构体
关于DMA的参数结构体在串口DMA接收章节已经进行过说明,这里不再讲述。
关于配置结构体和初始化代码如下:
//清除 DMA通道0 之前配置
dma_deinit(DMA_CH0);
// DMA初始化配置
dma_single_data_parameter.periph_addr = (uint32_t)(&ADC_RDATA); //设置DMA传输的外设地址为ADC0基地址
dma_single_data_parameter.periph_inc = DMA_PERIPH_INCREASE_DISABLE; //关闭外设地址自增
dma_single_data_parameter.memory0_addr = (uint32_t)(gt_adc_val); //设置DMA传输的内存地址为 gt_adc_val数组
dma_single_data_parameter.memory_inc = DMA_MEMORY_INCREASE_ENABLE; //开启内存地址自增(因为不止一个通道)
dma_single_data_parameter.periph_memory_width = DMA_PERIPH_WIDTH_16BIT;//传输的数据位 为 16位
dma_single_data_parameter.direction = DMA_PERIPH_TO_MEMORY; //DMA传输方向为 外设往内存
dma_single_data_parameter.number = 4*30;//传输的数据长度为:4个通道 * 每个通道采集30次
dma_single_data_parameter.priority = DMA_PRIORITY_HIGH;//设置高优先级
dma_single_data_mode_init( DMA_CH0, &dma_single_data_parameter); //将配置保存至DMA的通道0
2
3
4
5
6
7
8
9
10
11
12
13
因为我们是采集4个ADC通道的数据到我们的内存之中,选择的是外设往内存。其中外设地址一直保持不变,一直为ADC的读取地址;内存地址为我们的数据缓存数组gt_adc_val
,当有数据时就存入该数组。而每个通道的数据不能互相干扰,所以需要开启内存地址自增,防止数据叠加形成混乱数据。
传输的长度是根据开启了多少个ADC通道,例如我开启了2个通道,每一个通道采集1次,则传输的数据长度为2*1=2。但是由于ADC的采集有误差,所以通常每一个通道需要采集多次进行滤波。例如开启了4个通道,每一个通道采集30次,则传输的数据长度为4*30=120
。
配置DMA模式
在配置好DMA参数之后,还需要配置DMA的模式。我们使用ADC的DMA功能就是为了让DMA自动的一直帮我们搬运ADC的数据到内存之中。如何让DMA循环搬运ADC的数据?可以使用dma_circulation_enable
函数,原型如下:
void dma_circulation_enable(dma_channel_enum channelx);
通过启用 DMA 循环模式,可以实现 DMA 在完成一次传输后自动重新开始下一次传输,而无需重新配置。这在需要连续处理大量数据、循环缓冲区等应用场景中非常有用。它只有一个参数,channelx参数为开启的DMA通道。实际应用如下:
//使能DMA通道0循环模式
dma_circulation_enable( DMA_CH0 );
2
使能通道外设
在配置好DMA参数和模式之后,还需要使能通道外设才能正常使用通道。
void dma_channel_subperipheral_select(dma_channel_enum channelx, dma_subperipheral_enum sub_periph);
这个函数是DMA通道外设选择。有两个参数,第一个参数channelx
就是要使用的通道,第二个参数sub_periph
就是要开启的通道外设。关于外设通道如图所示。
我们使用的是ADC的DMA功能,其配置如图所示。
可见最前面的数值是000,对应的10进制也就是0,对应于枚举定义就是DMA_SUBPERI0
。
使能通道外设代码如下:
/* 使能通道外设 */
dma_channel_subperipheral_select(DMA_CH0, DMA_SUBPERI0);
2
使能通道外设之后还需要使能对应的通道。
void dma_channel_enable(dma_channel_enum channelx);
这个函数是使能DMA通道。有一个参数,这个参数channelx
是要使用的DMA通道。
配置代码如下:
/* 使能DMA通道 */
dma_channel_enable(DMA_CH0);
2
最终的DMA配置如下:
/***********************
采样次数 30
ADC通道 4
***********************/
uint16_t gt_adc_val[30][4]; //DMA缓冲区
void ADCDmaConfig(void)
{
/* DMA初始化功能结构体定义 */
dma_single_data_parameter_struct dma_single_data_parameter;
/* 使能DMA时钟 */
rcu_periph_clock_enable(RCU_DMA);
/* 清除 DMA通道0 之前配置 */
dma_deinit(DMA_CH0);
/* DMA初始化配置 */
dma_single_data_parameter.periph_addr = (uint32_t)(&ADC_RDATA); //设置DMA传输的外设地址为ADC0基地址
dma_single_data_parameter.periph_inc = DMA_PERIPH_INCREASE_DISABLE; //关闭外设地址自增
dma_single_data_parameter.memory0_addr = (uint32_t)(gt_adc_val); //设置DMA传输的内存地址为 gt_adc_val数组
dma_single_data_parameter.memory_inc = DMA_MEMORY_INCREASE_ENABLE; //开启内存地址自增(因为不止一个通道)
dma_single_data_parameter.periph_memory_width = DMA_PERIPH_WIDTH_16BIT; //传输的数据位 为 16位
dma_single_data_parameter.direction = DMA_PERIPH_TO_MEMORY; //DMA传输方向为 外设往内存
dma_single_data_parameter.number = 4*30; //传输的数据长度为:4个通道 * 每个通道采集30次
dma_single_data_parameter.priority = DMA_PRIORITY_HIGH; //设置高优先级
dma_single_data_mode_init( DMA_CH0, &dma_single_data_parameter); //将配置保存至DMA的通道0
/* DMA通道外设选择 */
/* 用户手册的166页根据PERIEN[2:0]值确定第三个参数,例是100 则为DMA_SUBPERI4 例是010 则为DMA_SUBPERI2 */
/* 我们是ADC0功能,PERIEN[2:0]值为000,故为DMA_SUBPERI0 */
dma_channel_subperipheral_select( DMA_CH0, DMA_SUBPERI0);
/* 使能DMA通道0循环模式 */
dma_circulation_enable( DMA_CH0 );
/* 启动DMA的通道0功能 */
dma_channel_enable( DMA_CH0 );
//开始软件转换
adc_software_trigger_enable(ADC_ROUTINE_CHANNEL);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
当配置完ADC之后,开启软件转换即可开始自动采集。
采集转换
我们现在用的是ADC+DMA功能,开启了软件转换之后,我们直接去对应的内存中取出数据即可。
uint16_t adc_channel_sample(uint8_t channel)
{
unsigned char i = 0;
unsigned int AdcValue = 0;
/* 因为采集30次,故循环30次 */
for(i=0;i<30;i++)
{
/* 累加 */
AdcValue += gt_adc_val[i][channel];
}
/* 求平均值 */
AdcValue=AdcValue / 30;
return AdcValue;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
实验现象
在main.c中调用uint16_t adc_channel_sample(uint8_t channel)
函数,采集4个引脚的电压,并通过串口输出。
#include "gd32vw55x.h"
#include "systick.h"
#include <stdio.h>
#include "bsp_adc.h"
#include "bsp_usart.h"
/*!
\brief main function
\param[in] none
\param[out] none
\retval none
*/
int main(void)
{
unsigned int adc_value = 0;
float voltage = 0.0;
/* 使能全局中断 */
eclic_global_interrupt_enable();
/* 中断分组 */
eclic_priority_group_set(ECLIC_PRIGROUP_LEVEL2_PRIO2);
systick_config();
Uart0InitConfig(115200);
AdcConfig();
printf("start Demo\r\n");
while(1)
{
adc_value = adc_channel_sample(0);
printf("adc_value0 = %d\r\n", adc_value );
adc_value = adc_channel_sample(1);
printf("adc_value1 = %d\r\n", adc_value );
adc_value = adc_channel_sample(2);
printf("adc_value2 = %d\r\n", adc_value );
adc_value = adc_channel_sample(3);
printf("adc_value3 = %d\r\n", adc_value );
delay_1ms(100);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
往PA4
PA6
接入GND
,往PA5
PA7
接入3.3V
。得到以下结果。
注意⚠
开启ADC功能时,引脚不能浮空!否则数据不准确!
关于这一章节的代码,可以在开发板资料/03 - 软件资料/代码例程/里面的013ADC多通道DMA采集
。
下载中心跳转📦
资料下载中心:点击跳转🚀