天天看点

【GD32】GD32 DMA+ADC多通道扫描

芯片:GD32F350 运行在8M

目标:每10ms读取三个adc通道,并且通过DMA传输

流程

1.配置DMA传输

2.配置ADC模式和通道

3.配置定时器定时触发

4.中断函数和获取足够数据后进行数据处理

说明:本次使用ADC的定时扫描模式,由定时器触发ADC采集转换

ADC扫描模式预先设定好读取的通道,比如ch4 ch5 ch8,当触发adc时候就会连续采集三个通道的数据,通过dma传输到内存,过程(触发->4->5->8->触发->4->5->8->触发->4->5->8->触发)

注意是触发一次即转换三个通道的数据,而不是触发一次转换一个通道

1.配置DMA传输

//adc 的dma是否传输完成标志位
	bool flag_DMA_ADC_accomplish = FALSE;

	//dma缓冲区
	uint16_t battery_ADC_value_arry[ADC_VALUE_ARRY_SIZE] = {0};

   /* 使能dma时钟 */
    rcu_periph_clock_enable(RCU_DMA);

    /* dma初始化结构体 */
    dma_parameter_struct dma_data_parameter;
    /* 复位DAM通道 */
    dma_deinit(DMA_CH0);
    //ADC_RDATA是读取adc数据的寄存器
    dma_data_parameter.periph_addr  = (uint32_t)(&ADC_RDATA);
    dma_data_parameter.periph_inc   = DMA_PERIPH_INCREASE_DISABLE;
    //battery_ADC_value_arry 是存储adc数据的内存数组
    dma_data_parameter.memory_addr  = (uint32_t)(&battery_ADC_value_arry);
    dma_data_parameter.memory_inc   = DMA_MEMORY_INCREASE_ENABLE;
    dma_data_parameter.periph_width = DMA_PERIPHERAL_WIDTH_16BIT;
    dma_data_parameter.memory_width = DMA_MEMORY_WIDTH_16BIT;  
    //从外设到内存
    dma_data_parameter.direction    = DMA_PERIPHERAL_TO_MEMORY;
    //这里的150是设置dma搬运数据的个数即3个通道共读取150个数据,每个通道读取50个,完成后会触发中断
    dma_data_parameter.number       = 150;
    dma_data_parameter.priority     = DMA_PRIORITY_HIGH;
    dma_init(DMA_CH0, &dma_data_parameter);

    dma_circulation_enable(DMA_CH0);
    //设置dma中断优先级
    nvic_irq_enable(DMA_Channel0_IRQn, 0, 0);
    /* enable DMA transfer complete interrupt */
    dma_interrupt_enable(DMA_CH0, DMA_INT_FTF);

    /* 使能通道 */
    dma_channel_enable(DMA_CH0);
    
           

2.配置ADC模式和通道

#define ADC_DMA_OUTPUT_INDEX   (0u)
	#define ADC_DMA_BATTERY_INDEX  (1u)
	#define ADC_DMA_USB_INDEX      (2u)

	//开启时钟ADC的时钟
	rcu_osci_on(RCU_IRC28M);
	while(ERROR == rcu_osci_stab_wait(RCU_IRC28M));
	 
	/* 配置ADC时钟 */
	rcu_adc_clock_config(RCU_ADCCK_IRC28M_DIV2);
	
	/* 使能ADC时钟 */
	rcu_periph_clock_enable(RCU_ADC);
	
	//=========================================================    
	/* 端口时钟使能 */
	rcu_periph_clock_enable(ADC_GPIO_CLK);
	 /* 配置读取ADC引脚 */
	gpio_mode_set(ADC_GPIO_PORT, GPIO_MODE_ANALOG, GPIO_PUPD_NONE, ADC_GPIO_PIN);
	
	//=========================================================
	//连续转换关闭
	adc_special_function_config(ADC_CONTINUOUS_MODE, DISABLE);
	//打开扫描模式
	adc_special_function_config(ADC_SCAN_MODE, ENABLE);
	//=========================================================
	/* ADC 触发模式 用定时器1通道触发*/
	adc_external_trigger_source_config( ADC_REGULAR_CHANNEL, ADC_EXTTRIG_REGULAR_T1_CH1);
	/* 数据对齐方式 */
	adc_data_alignment_config(ADC_DATAALIGN_RIGHT);
	
	/* ADC分辨率 12B */
	adc_resolution_config(ADC_RESOLUTION_12B);
	//=========================================================
	/* ADC channel 个数配置 */
	adc_channel_length_config( ADC_REGULAR_CHANNEL, _ADC_VALUE_NUMBER);
	/* 配置ADC通道 */
	adc_regular_channel_config(ADC_DMA_OUTPUT_INDEX , ADC_CHANNEL_4, ADC_SAMPLETIME_239POINT5);    //输出功率
	adc_regular_channel_config(ADC_DMA_BATTERY_INDEX, ADC_CHANNEL_5, ADC_SAMPLETIME_239POINT5);    //电池通道 
	adc_regular_channel_config(ADC_DMA_USB_INDEX    , ADC_CHANNEL_8, ADC_SAMPLETIME_239POINT5);    //USB电压
	
	//使能adc规则通道外部触发器
	adc_external_trigger_config(ADC_REGULAR_CHANNEL, ENABLE);
	
	/* 开启ADC DMA */
	adc_dma_mode_enable();
	
	/* ADC使能 */
	adc_enable();
	delay_1ms(2U);
	/* ADC校准复位 */
	adc_calibration_enable();
           

3.配置定时器定时触发

//将定时器配置成周期10ms
    timer_oc_parameter_struct timer_ocintpara;
    timer_parameter_struct timer_initpara;
	
	rcu_periph_clock_enable(RCU_TIMER1);
	
    /* TIMER1 configuration */
    //这里需要注意!!!!!因为我的主频是8M所以定时器这样配置,如果不一样,需要根据实际频率来调节
    timer_initpara.prescaler         = 8-1;
    timer_initpara.alignedmode       = TIMER_COUNTER_EDGE;
    timer_initpara.counterdirection  = TIMER_COUNTER_UP;
    timer_initpara.period            = 10*1000;
    timer_initpara.clockdivision     = TIMER_CKDIV_DIV1;
    timer_initpara.repetitioncounter = 0U;
    timer_init(TIMER1,&timer_initpara);
	
    /* CH0 configuration in PWM mode1 */
    timer_ocintpara.ocpolarity  = TIMER_OC_POLARITY_HIGH;
    timer_ocintpara.outputstate = TIMER_CCX_ENABLE;
    timer_channel_output_config(TIMER1, TIMER_CH_1, &timer_ocintpara);

	//这里设置比较值是5*1000 5ms是触发点,但是由于是周期性,所以每次采集也是等间距的
    timer_channel_output_pulse_value_config(TIMER1, TIMER_CH_1, 5*1000);
    timer_channel_output_mode_config(TIMER1, TIMER_CH_1, TIMER_OC_MODE_PWM0);
    timer_channel_output_shadow_config(TIMER1, TIMER_CH_1, TIMER_OC_SHADOW_DISABLE);
    
    
    timer_auto_reload_shadow_enable(TIMER1);

//	  可以尝试让他输出PWM用示波器查看配置是否正确
//    gpio_mode_set(GPIOA, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_0);
//    gpio_output_options_set(GPIOA, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ,GPIO_PIN_0);
//    gpio_af_set(GPIOA, GPIO_AF_2, GPIO_PIN_0);
           

4.中断函数和获取足够数据后进行数据处理

//当传输完成足够的数据后,就关闭定时器,设置标志位,进行处理,可以在中断中处理,
//也可以在其他地方处理,看个人习惯
void DMA_Channel0_IRQHandler(void)
{
    //传输完成标志位
    if(dma_interrupt_flag_get(DMA_CH0, DMA_INT_FLAG_FTF))
    {     
        //设置标志位 DMA完成了 
        flag_DMA_ADC_accomplish = TRUE;
        //关闭定时器,停止继续读取数据
        timer_disable(TIMER1);
        
        //清除全部标志位
        dma_interrupt_flag_clear(DMA_CH0, DMA_INT_FLAG_G);
    }
  
}
           

数据处理,这里我就做了个简单的求平均

//dma平均之后的值
	uint32_t voltage_raw_value[3] = {0};
    //DMA传输完成 这里进行转换
    if(TRUE == flag_DMA_ADC_accomplish)
    {
        //清除之前的数据
        memset(voltage_raw_value,0,sizeof(voltage_raw_value));
        for(int i = 0;i < ADC_VALUE_ARRY_SIZE;i += 3)
        {
           voltage_raw_value[0] += battery_ADC_value_arry[i + 0];
           voltage_raw_value[1] += battery_ADC_value_arry[i + 1];       
           voltage_raw_value[2] += battery_ADC_value_arry[i + 2];
        }
    }
    
    float tmp = 0;
    for(int i = 0;i < _ADC_VALUE_NUMBER;i += 1)
    {
        tmp = voltage_raw_value[i];
        //这里除50是求平均值 
        tmp = tmp / 50;
        
        voltage_raw_value[i] = (uint16_t)tmp;    
    }