GD32F103 Debugging Notes (1) ADC+DMA

Preface

Due to the severe external environment of the chip, combined with its own internal reasons, this time it began to use domestic 32-bit microcontrollers. In recent years, domestic 32-bit microcontrollers have indeed done pretty well (they have become more reliable), and with industry benchmarks such as ST in the MCU 32-bit field, it has become much easier to get started with a new microcontroller. Without further ado, let’s get to the point of today.

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

Direct access to memory means sending something from one place to another. This place can be a peripheral or an address in the memory. In this way, after the initial configuration, there is no need for CPU intervention at all. You only need to read the corresponding content when needed, which greatly reduces the load on the CPU. In my opinion, this is the key reason why 32-bit microcontrollers are truly more powerful than 8-bit microcontrollers. So when developing with 32-bit, I try my best to use DMA to reduce the load on the CPU.

Programming of each module

After talking about the peripherals used, we start to get to the main topic, writing code. I use GD32F103RCT6 ( note: please make sure you have a GD32F103 project that can be compiled and it already contains the standard library ).

  • Before doing anything else, let's configure our clock below.
  • Note that the maximum clock frequency of the ADC module reminded in the manual is 14MHz .
void SystemClock_Reconfig(void)
{
    
    
		/* Initializes the CPU, AHB and APB busses clocks */
		rcu_pll_config(RCU_PLLSRC_HXTAL,RCU_PLL_MUL9);				//set PLL's Source and multiple number				
		rcu_system_clock_source_config(RCU_CKSYSSRC_PLL);			//select the PLL to the system clock source	
		
		/* Due to AHB,APB2,APB1 has been initialized in SystemInit();(AHB = SYSCLK,APB2 = AHB/1,APB1 = AHB/2)
		 * We don't need to configure it again if unnecessary.
		 *
		 * CK_APB1 Max is 54MHz and CK_APB2 Max is 108MHz in GD32F10X,
		 * Please ensure your prescaler is not above Max Frequency.
	   */
//		rcu_ahb_clock_config(RCU_AHB_CKSYS_DIV1);					//set AHB Clock is equal to system clock		
//		rcu_apb1_clock_config(RCU_APB1_CKAHB_DIV2);					//set CK_APB1 is equal to half AHB clock			Fck_apb1 = 72M / 2 = 36MHz
//		rcu_apb2_clock_config(RCU_APB2_CKAHB_DIV1);					//set CK_APB1 is equal to AHB Clock		
		rcu_adc_clock_config(RCU_CKADC_CKAPB2_DIV6);				//set CK_ADCx is equal to CK_APB2/6					Fck_adcx = 72M / 6 = 12MHz
		
		/* 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_GPIOE);
		rcu_periph_clock_enable(RCU_GPIOF);
		rcu_periph_clock_enable(RCU_GPIOG);
		
		rcu_periph_clock_enable(RCU_AF);
		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);

		/* 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);		
}
  • Next, configure our GPIO.
  • Configure the corresponding pin as an analog input .
/* the ADC port and pins definition */
#define ADC0_PORT						GPIOA
#define ADC2_PORT						GPIOC
#define ADC0_CH4						GPIO_PIN_4			
#define ADC0_CH5						GPIO_PIN_5
#define ADC0_CH6						GPIO_PIN_6
#define ADC2_CH12						GPIO_PIN_2
#define ADC2_CH13						GPIO_PIN_3

void GPIO_Init(void)
{
    
    
	/* 使用SW下载,不使用JTAG下载,管脚用作其它功能 */
	gpio_pin_remap_config(GPIO_SWJ_SWDPENABLE_REMAP, ENABLE);

	/* demo board LED I/O*/
	gpio_init(GPIOB, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_4);
	gpio_bit_reset(GPIOB,GPIO_PIN_4);
	
	/* demo board OLED I/O*/
	gpio_init(GPIOB, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_8);
	gpio_init(GPIOB, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_9);
	gpio_init(GPIOB, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_12);
	gpio_init(GPIOB, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_13);
	gpio_bit_write(GPIOB, GPIO_PIN_12, 0);
	gpio_bit_write(GPIOB, GPIO_PIN_13, 0);	
	
	/* demo board IIC I/O*/
#if USED_IIC_SOFT
	gpio_init(IIC_AT24C08_PORT, GPIO_MODE_OUT_OD, GPIO_OSPEED_50MHZ, IIC_AT24C08_SCL);
	gpio_init(IIC_AT24C08_PORT, GPIO_MODE_OUT_OD, GPIO_OSPEED_50MHZ, IIC_AT24C08_SDA);
	gpio_bit_write(IIC_AT24C08_PORT, IIC_AT24C08_SCL, 1);
	gpio_bit_write(IIC_AT24C08_PORT, IIC_AT24C08_SDA, 1);	
#else 
	gpio_init(IIC_AT24C08_PORT,GPIO_MODE_AF_OD,GPIO_OSPEED_50MHZ,IIC_AT24C08_SCL | IIC_AT24C08_SDA);
#endif

	/* demo board ADC I/O */
	gpio_init(ADC0_PORT, GPIO_MODE_AIN, GPIO_OSPEED_50MHZ, ADC0_CH4 | ADC0_CH5 | ADC0_CH6);
	gpio_init(ADC2_PORT, GPIO_MODE_AIN, GPIO_OSPEED_50MHZ, ADC2_CH12 | ADC2_CH13);  

}
  • Then configure DMA.
  • The DMA channel corresponding to ADC0 is DMA0_CH0.
  • The DMA channel corresponding to ADC2 is DMA1_CH4.
  • The direction is from the data register of the specific peripheral to the variable we define ourselves.
  • Times = total number of channels x how many times a single channel is collected.
  • Finally, remember to turn on DMA loop mode. In this way, after the conversion is turned on, the values ​​in our ADCn_Buffer[ ][ ] will always be the latest.
/* ADC0 and ADC2 ADrawbuffer definition */
#define ADC0_CHANNELS	3 
#define ADC0_NUMBER		16
#define ADC2_CHANNELS	2
#define ADC2_NUMBER		16

uint32_t ADC0_Buffer[ADC0_CHANNELS][ADC0_NUMBER] = {
    
    0};
uint32_t ADC2_Buffer[ADC2_CHANNELS][ADC2_NUMBER] = {
    
    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);
	dma_deinit(DMA1, DMA_CH4);

	/* initialize DMA0 channeln(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 = (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);	
	
	/* initialize DMA1 channeln(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 = (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_LOW;
	dma_init(DMA1, DMA_CH4, &dma_init_ADC2);	
	dma_circulation_enable(DMA1, DMA_CH4);						
	
	/* enable all DMA channels you need */
	dma_channel_enable(DMA0,DMA_CH0);
	dma_channel_enable(DMA1,DMA_CH4);		
}
  • Finally configure our ADC.
  • Because DMA is used here, the ADC conversion mode must be configured in continuous scan mode.
  • The trigger source is configured as software trigger.
  • Synchronous mode is configured as independent mode. (Of course, it can also be set to regular parallel mode).
  • Configure the channel length and regular channel conversion sequence.
  • After enabling ADCn, remember to delay for a short period of time before performing self-calibration.
  • Finally, remember to turn on the DMA function.
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_DAUL_REGULAL_PARALLEL  ADC_MODE_FREE
	
  /* ADC channel length config */
	adc_channel_length_config(ADC0, ADC_REGULAR_CHANNEL, 3);
	adc_channel_length_config(ADC2, ADC_REGULAR_CHANNEL, 2);
  
	/* ADC regular channel config */
	adc_regular_channel_config(ADC0, 0, ADC_CHANNEL_4, ADC_SAMPLETIME_71POINT5);
	adc_regular_channel_config(ADC0, 1, ADC_CHANNEL_5, ADC_SAMPLETIME_71POINT5);
	adc_regular_channel_config(ADC0, 2, ADC_CHANNEL_6, ADC_SAMPLETIME_71POINT5);
	adc_regular_channel_config(ADC2, 0, ADC_CHANNEL_12, ADC_SAMPLETIME_71POINT5);
	adc_regular_channel_config(ADC2, 1, ADC_CHANNEL_13, ADC_SAMPLETIME_71POINT5);
  
	/* ADC external trigger enable */
	adc_external_trigger_config(ADC0, ADC_REGULAR_CHANNEL, ENABLE);
	adc_external_trigger_config(ADC2, ADC_REGULAR_CHANNEL, ENABLE);
	
//	/* ADC internal tempsensor and vrefint enable */
//	adc_tempsensor_vrefint_enable();
   
	/* 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);
}

Main function program

  • After configuring the system clock, first configure our GPIO and DMA, and then configure the peripherals that specifically use DMA, otherwise there may be problems (GD32 has relatively high requirements for the configuration sequence).
  • Before entering the while(1) loop, use adc_software_trigger_enable(); to start triggering our AD conversion.
  • After entering while(1), ADC will continue to convert, and DMA will continue to put the data after ADC conversion into the variable we specified.
  • Here I use OLED for display, and the displayed content is updated every second.
  • In addition, since I used a development board purchased online for testing, the configured AD ports were all floating. As a result, when applying voltage to one of the channels (such as ADC0_CH3), the other two channels (ADC0_CH4, CH5) There will also be a similar AD value in the variable BUFF. When different voltages are applied to each channel, the data read out will all correspond to the correct AD value. This is caused by the fact that the AD port is not connected to an actual external circuit, so don't be surprised.
int main(void)
{
    
    
		/* Define and initialize all Variables */
		uint8_t memory_s[16]={
    
    1,2,4,6,8,10,12,14,16,18,20,22,24,26,28,30};
		uint8_t memory_r[16]={
    
    0};

		/* Reset of all peripherals, Initializes the Systick. */
		SystemTick_Init();
		/* Initializes all peripherals clock you need */
		SystemClock_Reconfig();		
		/* Initialize all configured peripherals */
		GPIO_Init();
		DMA_Init();
		Timer1_Init();
	  	OLED_Init();			       
		ADCx_Init();
		
		
#if USED_IIC_SOFT		
//		I2C_WritePage(SLAVE_ADDRESS0,5,0,memory_s);
//		delay_ms(100);
		I2C_ReadPageBytes(SLAVE_ADDRESS0,5,0,memory_r);
#else
		I2C1_Init();
		delay_ms(10);
		I2C_WriteOneByte(SLAVE_ADDRESS0,5,5,138);
#endif
		/* trigger start ADCx_channels conversion */
		adc_software_trigger_enable(ADC0, ADC_REGULAR_CHANNEL);
		adc_software_trigger_enable(ADC2, ADC_REGULAR_CHANNEL);
		while(1)
		{
    
    			
				
				if(Module.LED_REFRESH)
				{
    
    
						gpio_bit_set(GPIOB,GPIO_PIN_4);					
				}
				else
				{
    
    
						gpio_bit_reset(GPIOB,GPIO_PIN_4);
				}	
				
				if(Module.OLED_REFRESH)
				{
    
    
						Module.OLED_REFRESH = 0;
//						memory_r[3] = I2C_ReadOneByte(SLAVE_ADDRESS0,5,5);
						I2C_ReadPageBytes(SLAVE_ADDRESS0,5,0,memory_r);
					
						OLED_ShowNum(24,0,ADC0_Buffer[0][0],5,16);
						OLED_ShowNum(24,2,ADC0_Buffer[2][0],5,16);
						OLED_ShowNum(24,4,ADC2_Buffer[1][0],5,16);
						OLED_ShowNum(24,6,memory_r[3],5,16);
					
						OLED_ShowNum(96,0,ADC0_Buffer[1][0],4,16);
						OLED_ShowNum(96,2,ADC2_Buffer[0][0],4,16);
						OLED_ShowNum(96,4,memory_r[2],4,16);
						OLED_ShowNum(96,6,memory_r[4],4,16);					
				}
		}
    return 0;
}

Summarize

In general, after all, it is the twin brother (half-father?) of STM32. As long as you are familiar with STM32, there is not much difference in making GD32.
Finally, today is the Chinese Valentine's Day, and I am actually typing this article. Brothers, if you don’t give me a like after reading this, are you worthy of my girlfriend?

Guess you like

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