GD32F303调试小记(五)之ADC+DMA+硬件过采样

前言

单片机的大多数的功能都是基于数字信号去控制的。然而许多的场合下,我们也需要有模拟信号的参与,因为许多变量的控制是需要连续的而非阶跃式的。常见的若想得到电压值、温度值、电流值等等都需要用到A/D转换(如果外围器件不是特定IC而是自己搭的采集电路的话),而想输出模拟信号通常可以使用D/A转换或者PWM+外围一级或多级RC滤波实现。之前有写过一篇GD32F103调试小记(一)之ADC+DMA,本文介绍GD32F303的ADC+DMA模块使用。

ADC

ADC,模数转换器,会将模拟信号(连续变化的电压值)转换为数字值,以便在处理和控制系统中使用。特定的外围元器件,可以将温度、湿度、光线亮暗、气压大小等等转化成一个会随之变化的电压信号。再通过它,我们的处理器可以得到一个会随之变化的数值。利用其特性,我们可以间接的得到各种信息并加以处理。ADC的位数是其精度的描述,又或者说是最小分辨率,即数字值变化1对应的模拟信号变化是多大。如一个ADC的位数是12位,参考电压为3.3V,那么其数字值变化1对应的模拟信号电压变化为3.3V/(2^12)≈0.8mV。想要获得更高的精度,可以选择位数跟高的ADC或者降低参考电压。ADC又有许多种类,这里不多做介绍,我们使用的是逐次比较型ADC。

DMA

它就是个搬运工,把数据从一个地方搬到另一个地方。

各模块程序编写

在配置前,请确保你已经有一个GD32F303包含其对应标准库的keil工程,工程可使用官方的例程或可按照GD32F303调试小记(零)之工程创建与编译创建。

一、时钟配置

  • 先看看手册中对ADC模块的描述.
    请添加图片描述
    请添加图片描述
  • 上面两图我们可知,F303的ADC模块挂载在APB2总线上且最大工作频率为40MHz.

请添加图片描述

  • 上面这张图则是告诉了我们F303的ADC模块的硬件特性,最大工作频率、各分辨率下采样速率、开始转换时间、总转换时间、外部输入阻抗、等等 (这里提个醒,输入阻抗与外部你选择的分压电阻有关,到一定值会影响采样结果)

  • 接下来是代码部分。开启GPIO端口时钟、GPIO引脚复用时钟、AF时钟、ADC0、2模块的时钟。

void SystemClock_Reconfig(void)
{
		/* Enable all peripherals clocks you need*/
		rcu_periph_clock_enable(RCU_GPIOA);
		rcu_periph_clock_enable(RCU_GPIOB);
		rcu_periph_clock_enable(RCU_GPIOC);
		rcu_periph_clock_enable(RCU_GPIOD);
		
		rcu_periph_clock_enable(RCU_DMA0);
		rcu_periph_clock_enable(RCU_DMA1);
		rcu_periph_clock_enable(RCU_I2C1);
		rcu_periph_clock_enable(RCU_ADC0);
		rcu_periph_clock_enable(RCU_ADC2);
//		rcu_periph_clock_enable(RCU_USART1);
		rcu_periph_clock_enable(RCU_USART2);
		rcu_periph_clock_enable(RCU_SPI2);
		/* Timer1,2,3,4,5,6,11,12,13 are hanged on APB1,
		 * Timer0,7,8,9,10 			 are hanged on APB2
		 */
		rcu_periph_clock_enable(RCU_TIMER1);	

		rcu_periph_clock_enable(RCU_AF);
}

二、GPIO配置

请添加图片描述请添加图片描述

  • 我使用了上述图中的六个pin脚,相关IO配置如下:
// ADC port and pin
#define ADC0_PORT					GPIOA			
#define ADC0_ADI_PIN				GPIO_PIN_4		//ADC0_CH4
#define ADC0_InnerBatV_PIN			GPIO_PIN_5		//ADC0_CH5
#define ADC0_BoardADT_PIN			GPIO_PIN_6		//ADC0_CH6

#define ADC2_PORT					GPIOC			
#define ADC2_PrintADT_PIN			GPIO_PIN_1		//ADC2_CH11
#define ADC2_ClipV_PIN				GPIO_PIN_2		//ADC2_CH12
#define ADC2_ADI_PIN				GPIO_PIN_3		//ADC2_CH13


void GPIO_Init(void)
{
	/* 使用SW下载,不使用JTAG下载,管脚用作其它功能 */
	gpio_pin_remap_config(GPIO_SWJ_SWDPENABLE_REMAP, ENABLE);
	
	/* demo board ADCx I/O */
	gpio_init(ADC0_PORT, GPIO_MODE_AIN, GPIO_OSPEED_50MHZ, ADC0_ADI_PIN | ADC0_InnerBatV_PIN | ADC0_BoardADT_PIN);	
	gpio_init(ADC2_PORT, GPIO_MODE_AIN, GPIO_OSPEED_50MHZ, ADC2_PrintADT_PIN | ADC2_ClipV_PIN | ADC2_ADI_PIN);	


}

三、ADC模块配置

  • 配置ADC0和ADC2,复位这两个模块、连续扫描模式、规则通道触发、数据右对齐、独立模式、对应模块规则通道长度、每个规则通道里通道顺序、设置ADC模块分辨率(可配置成12位、10位、8位或6位)、硬件过采样配置、模块自校准、开启DMA模式、并开始转换。(这里我仅对ADC2模块使用了硬件过采样)
  • 关于ADC模块分辨率,F303模块本身就是个12位ADC,跟F103的ADC模块没啥区别(在分辨率上),只不过厂商给你提供了一个软件可配置选项,让最后的转换出来的数据寄存器里的内容可以是12位、10位、8位和6位的数据罢了。
  • 关于硬件过采样,其实就是多次采样求均,和软件里的均值滤波是一个道理。比如我下面配置的8次过采样再右移3次,就是硬件上采集8次再除以8次最终还是一个12位的数据。如果你过采样8次只移位两次,那么就是一个13位的数据。以此类推,最大过采样16次,不进行移位,对应ADC数据寄存器上得到的是一个16位数据。但不要以为其精度就到达了16位,这只是16次12位数据的累加值。
  • 还有一点要说明下,ADC模块分辨率和硬件过采样不是针对某一个通道,而是针对一个模块去操作的,比如ADC0模块里你使用的所有通道。
void ADCx_Init(void)
{
	/* reset ADC */
	adc_deinit(ADC0);
	adc_deinit(ADC2);	
	/* ADC scan mode function enable */
	adc_special_function_config(ADC0, ADC_SCAN_MODE, ENABLE);
	adc_special_function_config(ADC0, ADC_CONTINUOUS_MODE, ENABLE);
	adc_special_function_config(ADC2, ADC_SCAN_MODE, ENABLE);
	adc_special_function_config(ADC2, ADC_CONTINUOUS_MODE, ENABLE);	
	/* ADC trigger config */
	adc_external_trigger_source_config(ADC0, ADC_REGULAR_CHANNEL, ADC0_1_2_EXTTRIG_REGULAR_NONE);
	adc_external_trigger_source_config(ADC2, ADC_REGULAR_CHANNEL, ADC0_1_2_EXTTRIG_REGULAR_NONE); 
	/* ADC data alignment config */
	adc_data_alignment_config(ADC0, ADC_DATAALIGN_RIGHT);
	adc_data_alignment_config(ADC2, ADC_DATAALIGN_RIGHT);
	/* configure the ADC sync mode */
	adc_mode_config(ADC_MODE_FREE);  	
  /* ADC channel length config */
	adc_channel_length_config(ADC0, ADC_REGULAR_CHANNEL, 4);
	adc_channel_length_config(ADC2, ADC_REGULAR_CHANNEL, 2);
	
	/* ADC regular channel config */
	adc_regular_channel_config(ADC0, 0, ADC_CHANNEL_4, ADC_SAMPLETIME_7POINT5);//tadc = (7.5 + 12.5)/(27MHz) = 741ns
	adc_regular_channel_config(ADC0, 1, ADC_CHANNEL_5, ADC_SAMPLETIME_7POINT5);
	adc_regular_channel_config(ADC0, 2, ADC_CHANNEL_6, ADC_SAMPLETIME_7POINT5);
	adc_regular_channel_config(ADC0, 3, ADC_CHANNEL_11, ADC_SAMPLETIME_7POINT5);
	adc_regular_channel_config(ADC2, 0, ADC_CHANNEL_12, ADC_SAMPLETIME_7POINT5);
	adc_regular_channel_config(ADC2, 1, ADC_CHANNEL_13, ADC_SAMPLETIME_7POINT5);
	
	/* ADC external trigger enable */
	adc_external_trigger_config(ADC0, ADC_REGULAR_CHANNEL, ENABLE);
	adc_external_trigger_config(ADC2, ADC_REGULAR_CHANNEL, ENABLE);
	
	/* ADC resolution config */
	adc_resolution_config(ADC0, ADC_RESOLUTION_12B);
	adc_resolution_config(ADC2, ADC_RESOLUTION_12B);
	
	/* 8 times sample, 3 bits shift */
	adc_oversample_mode_config(ADC2, ADC_OVERSAMPLING_ALL_CONVERT, ADC_OVERSAMPLING_SHIFT_3B, ADC_OVERSAMPLING_RATIO_MUL8);
	adc_oversample_mode_enable(ADC2);

	/* enable ADC interface */
	adc_enable(ADC0);
	delay_ms(10);    
	/* ADC calibration and reset calibration */
	adc_calibration_enable(ADC0);
	/* enable ADC interface */
	adc_enable(ADC2);    
	delay_ms(10);
	/* ADC calibration and reset calibration */
	adc_calibration_enable(ADC2);
    
	/* ADC DMA function enable */
	adc_dma_mode_enable(ADC0);
	/* ADC DMA function enable */
	adc_dma_mode_enable(ADC2);

	/* trigger start ADCx_channels conversion */
	adc_software_trigger_enable(ADC0, ADC_REGULAR_CHANNEL);
	adc_software_trigger_enable(ADC2, ADC_REGULAR_CHANNEL);

}

四、DMA配置

请添加图片描述
请添加图片描述

  • ADC0对应DMA0_CH0,ADC2对应DMA1_CH4。
  • 配置如下:配置DMA转换完成次数、从外设到内存、打开循环模式。使能对应通道。
/* ADC0 and ADC2 ADrawbuffer definition */
#define ADC0_CHANNELS	4 
#define ADC0_NUMBER		16
#define ADC2_CHANNELS	2
#define ADC2_NUMBER		16

uint32_t ADC0_Buffer[ADC0_NUMBER*ADC0_CHANNELS] = {0};
uint32_t ADC2_Buffer[ADC2_NUMBER*ADC2_CHANNELS] = {0};

void DMA_Init(void)
{
	dma_parameter_struct dma_init_ADC0;
	dma_parameter_struct dma_init_ADC2;

	/* deinitialize DMA channel */
	dma_deinit(DMA0, DMA_CH0);									//ADC0
	dma_deinit(DMA1, DMA_CH4);									//ADC2

	/* initialize DMA0 channel0(ADC0) */							
	dma_init_ADC0.direction = DMA_PERIPHERAL_TO_MEMORY;
	dma_init_ADC0.memory_addr = (uint32_t)(&ADC0_Buffer);
	dma_init_ADC0.memory_inc = DMA_MEMORY_INCREASE_ENABLE;
	dma_init_ADC0.memory_width = DMA_MEMORY_WIDTH_32BIT;
	dma_init_ADC0.number = 64;//(uint32_t)ADC0_CHANNELS*ADC0_NUMBER
	dma_init_ADC0.periph_addr = (uint32_t)(&ADC_RDATA(ADC0));
	dma_init_ADC0.periph_inc = DMA_PERIPH_INCREASE_DISABLE;
	dma_init_ADC0.periph_width = DMA_PERIPHERAL_WIDTH_32BIT;
	dma_init_ADC0.priority = DMA_PRIORITY_MEDIUM;
	dma_init(DMA0, DMA_CH0, &dma_init_ADC0);	
	dma_circulation_enable(DMA0, DMA_CH0);						//circulate

	/* initialize DMA1 channel4(ADC2) */						
	dma_init_ADC2.direction = DMA_PERIPHERAL_TO_MEMORY;
	dma_init_ADC2.memory_addr = (uint32_t)(&ADC2_Buffer);
	dma_init_ADC2.memory_inc = DMA_MEMORY_INCREASE_ENABLE;
	dma_init_ADC2.memory_width = DMA_MEMORY_WIDTH_32BIT;
	dma_init_ADC2.number = 32;//(uint32_t)ADC2_CHANNELS*ADC2_NUMBER
	dma_init_ADC2.periph_addr = (uint32_t)(&ADC_RDATA(ADC2));
	dma_init_ADC2.periph_inc = DMA_PERIPH_INCREASE_DISABLE;
	dma_init_ADC2.periph_width = DMA_PERIPHERAL_WIDTH_32BIT;
	dma_init_ADC2.priority = DMA_PRIORITY_MEDIUM;
	dma_init(DMA1, DMA_CH4, &dma_init_ADC2);	
	dma_circulation_enable(DMA1, DMA_CH4);						//circulate

	dma_memory_to_memory_disable(DMA0,DMA_CH0);				//ADC0
	dma_memory_to_memory_disable(DMA1,DMA_CH4);				//ADC2

	/* enable all DMA channels you need */
	dma_channel_enable(DMA0,DMA_CH0);									//ADC0
	dma_channel_enable(DMA1,DMA_CH4);									//ADC2
}

五、其它(滤波)

  • 一般我们软件上还会对此处理一下,我的均值滤波如下:
/*
 * 均值滤波:对DMA读取后的所有ADC通道数据进行处理
 */
void ADCx_Smoothings(const uint32_t* adc_buf,uint8_t channels,uint8_t number,uint32_t* smd_buf)
{
	uint8_t i,j;
	uint32_t cache_smooth[4]={0};
	
	for(i=0;i<number;i++)
	{
		for(j=0;j<channels;j++)
		{
			*(cache_smooth+j) += *(adc_buf + i*channels + j);
		}
	}
	
	for(i=0;i<channels;i++)
	{
		*(smd_buf+i) = *(cache_smooth+i) >> 4;
	}
		
}

六、主函数部分

1. 任务部分

  • 滤波处理后分别放到你要的具体含义的变量里.
void task_adc_smothing_event(void)
{
	uint32_t ADC0_CHx[4]={0};
	uint32_t ADC2_CHx[2]={0};
	
	ADCx_Smoothings(ADC0_Buffer,ADC0_CHANNELS,ADC0_NUMBER,ADC0_CHx);
	ADCx_Smoothings(ADC2_Buffer,ADC2_CHANNELS,ADC2_NUMBER,ADC2_CHx);
	
	ADCx.ADC0_ADI = ADC0_CHx[0];
	ADCx.Inner_Bat = ADC0_CHx[1];
	ADCx.ADT_Board = ADC0_CHx[2];
	ADCx.ADT_Print = ADC0_CHx[3];
	ADCx.ADV_Clip = ADC2_CHx[0];
	ADCx.ADC2_ADI = ADC2_CHx[1];
	
}

2. 主函数

  • 主函数程序逻辑如下:
  • TMT是个时间片框架,源码见GITEE,这里我们是让task_adc_smothing_event();这个函数每10ms执行一次。主函数理解到此处即可。
int main(void)
{	
	SystemTick_Init();
	
	SystemClock_Reconfig();
	
	GPIO_Init();
	Timer1_Init();
	DMA_Init();
	USARTx_Init();
	SPIx_Init();
	ADCx_Init();
	FWDGT_Init();	
	NVIC_Init();
	TMT_Init();
	LCD_Init();
	
	TMT.Create(task_adc_smothing_event,10);

	while(1)
	{
		TMT.Run();
	}
}

七、效果与总结

  • 按道理是该有一个效果演示的,这里原谅我偷个懒,就不展示出来了。通用32位机某一模块的配置除了名字,配置流程基本完全一致,原因大家都懂。
  • 文中有什么不足,还请多多指正。有问题的也可以直接评论,相互学习。
  • 最后,祝大家新年快乐,在新的一年中,无论喜怒哀乐,皆能淡然处之,幸福与收获多多!!!

!!!本文为欢喜6666在CSDN原创发布,复制或转载请注明出处:)!!!

猜你喜欢

转载自blog.csdn.net/qq_37554315/article/details/122386197