GD32F303 debugging notes (5) ADC+DMA+hardware oversampling

Preface

Most functions of the microcontroller are controlled based on digital signals. However, in many cases, we also need to involve analog signals, because the control of many variables requires continuous rather than step-like control. Commonly, if you want to get voltage values, temperature values, current values, etc., you need to use A/D conversion (if the peripheral device is not a specific IC but a self-built acquisition circuit), and if you want to output analog signals, you can usually use D/A Conversion or PWM + peripheral one-level or multi-level RC filtering implementation. I have previously written an article on GD32F103 debugging notes (1) ADC+DMA . This article introduces the use of the ADC+DMA module of GD32F303.

ADC

ADC, Analog-to-Digital Converter, converts analog signals (continuously varying voltage values) into digital values ​​for use in processing and control systems. Specific peripheral components can convert temperature, humidity, light intensity, air pressure, etc. into a voltage signal that changes accordingly. Through it, our processor can get a value that changes accordingly. Using its characteristics, we can indirectly obtain various information and process it. The number of bits in the ADC is a description of its accuracy, or the minimum resolution, that is, the change in the analog signal corresponding to a change in the digital value of 1. For example, if an ADC has 12 bits and the reference voltage is 3.3V, then the analog signal voltage change corresponding to a change in its digital value by 1 is 3.3V/(2^12)≈0.8mV. To obtain higher accuracy, you can choose an ADC with a higher number of bits or lower the reference voltage. There are many types of ADCs. I won’t introduce them here. We use the sequential comparison ADC.

DMA

It is a porter, moving data from one place to another.

Programming of each module

Before configuring, please make sure you already have a GD32F303 keil project that includes its corresponding standard library . The project can be created using official routines or according to the project creation and compilation of GD32F303 Debugging Notes (Zero) .

1. Clock configuration

  • First look at the description of the ADC module in the manual.
    Please add image description
    Please add image description
  • From the above two figures, we can see that the ADC module of F303 is mounted on the APB2 bus and the maximum operating frequency is 40MHz .

Please add image description

  • The picture above tells us the hardware characteristics of the F303 ADC module, maximum operating frequency, sampling rate at each resolution, start conversion time, total conversion time, external input impedance, etc. (Reminder here, input impedance It is related to the external voltage dividing resistor you choose. When it reaches a certain value, it will affect the sampling result) .

  • Next is the code part. Turn on the GPIO port clock, GPIO pin multiplexing clock, AF clock, and ADC0 and 2 module clocks.

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);
}

2. GPIO configuration

Please add image descriptionPlease add image description

  • I used the six pins in the above picture, and the relevant IO configuration is as follows:
// 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);	


}

3. ADC module configuration

  • Configure ADC0 and ADC2, reset these two modules, continuous scan mode, regular channel trigger, data right alignment, independent mode, corresponding module regular channel length, channel sequence in each regular channel, set ADC module resolution (can be configured to 12 bit, 10-bit, 8-bit or 6-bit), hardware oversampling configuration, module self-calibration, turning on DMA mode, and starting conversion. (Here I only used hardware oversampling for the ADC2 module) .
  • Regarding the ADC module resolution , the F303 module itself is a 12-bit ADC, which is no different from the F103 ADC module (in terms of resolution), but the manufacturer provides you with a software configurable option to allow the final converted data to The contents of the register can be 12-bit, 10-bit, 8-bit and 6-bit data.
  • Regarding hardware oversampling , it is actually averaging multiple samples, which is the same as the mean filtering in software. For example, the 8 times of oversampling I configured below and then the right shift 3 times means that the hardware collects 8 times and then divides it by 8 times, and finally it is still a 12-bit data. If you oversample 8 times and shift only twice, then you have 13-bit data. By analogy, the maximum oversampling is 16 times without shifting, and the corresponding ADC data register obtains a 16-bit data. But don’t think that the accuracy reaches 16 bits. This is just the accumulated value of 16 times of 12-bit data.
  • Another point to note is that the ADC module resolution and hardware oversampling are not operated for a certain channel , but for a module, such as all the channels you use in the ADC0 module.
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);

}

4. DMA configuration

Please add image description
Please add image description

  • ADC0 corresponds to DMA0_CH0, and ADC2 corresponds to DMA1_CH4.
  • The configuration is as follows: configure the number of DMA conversion completion times, from peripherals to memory, and turn on loop mode. Enable the corresponding channel.
/* 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
}

5. Others (filtering)

  • Generally, our software will handle this. My mean filtering is as follows:
/*
 * 均值滤波:对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;
	}
		
}

6. Main function part

1. Task section

  • After filtering, put them into variables with the specific meaning you want.
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. Main function

  • The main function program logic is as follows:
  • TMT is a time slice framework. See GITEE for the source code . Here we ask task_adc_smothing_event(); this function to be executed every 10ms. The main function can be understood here.
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();
	}
}

7. Effects and Summary

  • Logically, there should be a demonstration of the effect. Forgive me for being lazy and not showing it here. Except for the name, the configuration process of a certain module of a general 32-bit computer is basically the same, and everyone knows the reason.
  • If there are any shortcomings in the article, please correct me. If you have any questions, you can also comment directly and learn from each other.
  • Finally, I wish you all a Happy New Year. In the new year, no matter the joys, sorrows or joys, you can deal with it indifferently, and you will have a lot of happiness and gains! ! !

!!!This article was originally published by Huanxi 6666 on CSDN. Please indicate the source when copying or reprinting:)!!!

Guess you like

Origin blog.csdn.net/qq_37554315/article/details/122386197