GD32 Timer+ADC多通道+DMA+PWM调试记录

本例记录使用GD32307C开发板,实现以内部Timer1 CH1为触发源,触发ADC0的两个通道,进行并行非连续采样,病通过DMA传输采样结果。同时输出PWM,用来检测Timer1 CH1的触发周期。

下面介绍具体实现过程:

1. gpio初始化

本例需要用到以下三根IO:

PA1 -- TIMER1_1,PWM
PC3 -- ADC0_CH13
PC5 -- ADC0_CH15

查询芯片手册需要将PC3/PC5设定为模拟输入AIN(ADC功能),将PA1设定为备份功能输出脚AF_PP(PWM输出)。

以PA1为例,设定为Alternate功能后,如果有启动Timer1,则该脚位的功能为TIMER1_CH1。

  1. 使能GPIO Group时钟。
  2. 使能Alternate Function时钟。
  3. GPIO 功能config 。

代码如下:

/*!
    \brief      configure the GPIO peripheral
    \param[in]  none
    \param[out] none
    \retval     none
*/
//PA1 -- TIMER1_1,PWM
//PC3 -- ADC0_CH13
//PC5 -- ADC0_CH15
static void gpio_config(void)
{
	/* enable GPIOC clock */
	rcu_periph_clock_enable(RCU_GPIOC);
	rcu_periph_clock_enable(RCU_GPIOA); 

	/* 开启复用功能时钟 */
  rcu_periph_clock_enable(RCU_AF);	

  /* config the GPIO as analog mode */  
  gpio_init(GPIOC, GPIO_MODE_AIN, GPIO_OSPEED_MAX, GPIO_PIN_3|GPIO_PIN_5);

  /*configure PB3(TIMER1 CH1) as alternate function*/
  gpio_init(GPIOA, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_1);
}

2.Timer初始化

这里配置Timer1的定时周期为1ms,附上Timer时间公式:

定时时间:Time= (1+prescaler)/ systemcoreclock *(1+period)

我这边systemcoreclock为120MHZ。

设定CH1位PWM0模式输出,用来验证ADC采样间隔(ADC采样以内部Timer1_CH1为触发源)。

在下面代码里面,我们设定为边沿对齐方式(EAPWM),向上计数:

EAPWM的周期由TIMERx_CAR寄存器值决定,占空比由TIMERx_CHxCV寄存器值决定。

这里介绍一下PWM的工作模式(PWM0/PWM1)

PWM的工作模式:

  • PWM 模式0。在向上计数时,一旦计数器值小于TIMERx_CH0CV时,
    O0CPRE为有效电平,否则为无效电平。在向下计数时,一旦计数器的值大
    于TIMERx_CH0CV时,O0CPRE 为无效电平,否则为有效电平。
  • PWM 模式1。在向上计数时,一旦计数器值小于TIMERx_CH0CV时,
    O0CPRE为无效电平,否则为有效电平。在向下计数时,一旦计数器的值大
    于TIMERx_CH0CV时,O0CPRE为有效电平,否则为无效电平。

占空比计算公式:

Duty = A/(N+1)   

N = Timer装载值

A = TIMER_CHxCV,即timer_channel_output_pulse_value_config的最后一个参数

EAPWM的时序示意图如下:

代码如下:

/*!
    \brief      configure the timer peripheral
    \param[in]  none
    \param[out] none
    \retval     none
*/
static void timer1_config(void)	// 1 ms
{
	timer_oc_parameter_struct timer_ocintpara;
	timer_parameter_struct timer_initpara;
	
	//使能定时器时钟
	rcu_periph_clock_enable(RCU_TIMER1);

	timer_deinit(TIMER1);
	/*  TIMER1 基本配置 */
	timer_initpara.prescaler				 = 119;
	timer_initpara.alignedmode			 = TIMER_COUNTER_EDGE;
	timer_initpara.counterdirection  = TIMER_COUNTER_UP;
	timer_initpara.period 					 = 999;
	timer_initpara.clockdivision		 = TIMER_CKDIV_DIV1;
	timer_initpara.repetitioncounter = 0;
	timer_init(TIMER1,&timer_initpara);

	/* CH1 configuration in PWM mode1 */
	timer_ocintpara.outputstate  = TIMER_CCX_ENABLE;		/* 通道使能 */    
	timer_ocintpara.outputnstate = TIMER_CCXN_DISABLE;		/* 通道互补输出使能(定时器2无效) */    
	timer_ocintpara.ocpolarity   = TIMER_OC_POLARITY_HIGH;	/* 通道极性 */    
	timer_ocintpara.ocnpolarity  = TIMER_OCN_POLARITY_HIGH;/* 互补通道极性(定时器2无效)*/    
	timer_ocintpara.ocidlestate  = TIMER_OC_IDLE_STATE_LOW;/* 通道空闲状态输出(定时器2无效)*/    
	timer_ocintpara.ocnidlestate = TIMER_OCN_IDLE_STATE_LOW;/*互补通道空闲状态输出(定时器2无效) */

	timer_channel_output_config(TIMER1, TIMER_CH_1, &timer_ocintpara);

	/* duty 占空比设定*/
  timer_channel_output_pulse_value_config(TIMER1, TIMER_CH_1, 500);
  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);
}

使用示波器量测PA1脚位,输出为1KHZ的方波。

3. dma初始化

这里我们选择DMA0_CH0,这里我们设定为16bit宽度数据源,与16bit外设数据宽度。DMA传输数量设定为100.

所以我们定义一个100 uint16_t的数组作为DMA存放数据的内存。

开启半满与全满中断。

代码如下:

/*!
    \brief      configure the DMA peripheral
    \param[in]  none
    \param[out] none
    \retval     none
*/
uint16_t adc_value[100];
uint32_t dmaHalfIntCnt; 

static void dma_config(void)
{
	/* ADC_DMA_channel configuration */
	dma_parameter_struct dma_data_parameter;

  /* enable DMA clock */
  rcu_periph_clock_enable(RCU_DMA0);
	
	/* 复位dma 通道0  */
	dma_deinit(DMA0, DMA_CH0);
	
	/* initialize DMA data mode */
	dma_data_parameter.periph_addr	= (uint32_t)(&ADC_RDATA(ADC0));//配置外设寄存器为adc的规则数据寄存器
	dma_data_parameter.periph_inc 	= DMA_PERIPH_INCREASE_DISABLE;//外设寄存器地址不增加
	dma_data_parameter.memory_addr	= (uint32_t)(&adc_value);//存放数据的内存地址
	dma_data_parameter.memory_inc 	= DMA_MEMORY_INCREASE_ENABLE;//存放数据的内存地址自增
	dma_data_parameter.periph_width = DMA_PERIPHERAL_WIDTH_16BIT;//外设数据宽度16位,每个16bit存储一个通道的值
	dma_data_parameter.memory_width = DMA_MEMORY_WIDTH_16BIT;  //内存数据宽度16位
	dma_data_parameter.direction		= DMA_PERIPHERAL_TO_MEMORY;//数据传输方式是外设到内存
	dma_data_parameter.number 			= 100;//dma传输数量
	dma_data_parameter.priority 		= DMA_PRIORITY_HIGH;//dma优先级高
	dma_init(DMA0, DMA_CH0, &dma_data_parameter);
	//循环模式开启
	dma_circulation_enable(DMA0, DMA_CH0);

	/* dam 转换结束/半满产生中断*/
	dma_interrupt_enable(DMA0, DMA_CH0, DMA_INT_FTF);		
	dma_interrupt_enable(DMA0, DMA_CH0, DMA_INT_HTF);	

	/* 使能dma通道0*/
	dma_channel_enable(DMA0, DMA_CH0);
	
	//中断管理器开启通道中断
	nvic_irq_enable(DMA0_Channel0_IRQn, 0, 0);	
}

DMA中断程序如下:(函数名与startup中的中断向量表函数名一致)

void DMA1_Channel1_IRQHandler(void)
{
	if(dma_interrupt_flag_get(DMA0, DMA_CH0, DMA_INT_FLAG_FTF)){  
		//清除dma中断标记
    dma_interrupt_flag_clear(DMA0, DMA_CH0, DMA_INT_FLAG_FTF);
		gd_eval_led_toggle(LED2);
    //dma_finish = SET;
  } else if(dma_interrupt_flag_get(DMA0, DMA_CH0, DMA_INT_FLAG_HTF)){
		//清除dma中断标记
    dma_interrupt_flag_clear(DMA0, DMA_CH0, DMA_INT_FLAG_HTF);
		dmaHalfIntCnt ++;
	}
}

截取部分Startup_xx.s向量表

g_pfnVectors:

  .
  .word EXTI4_IRQHandler
  .word DMA1_Channel1_IRQHandler
  .

4. ADC初始化

我们使用ADC0的CH13/CH15两个通道,这里设置ADC为规则并行模式。

开启扫描模式,ADC会自动采样转换开启的通道。

不要开启连续扫描,采用间断扫描方式。

设定TIM1_CH1为外部触发源,病使能ADC的DMA mode,具体代码如下:

/*!
    \brief      configure the ADC peripheral
    \param[in]  none
    \param[out] none
    \retval     none
*/
static void adc_config(void)
{
	/* enable ADC0 clock */
	rcu_periph_clock_enable(RCU_ADC0);

 /* reset ADC */
	adc_deinit(ADC0);

	/* ADC mode config 工作在规则并行模式 */
	//adc_mode_config(ADC_MODE_FREE); 
	adc_mode_config(ADC_DAUL_REGULAL_PARALLEL); 

  // 连续转换模式使能
  //adc_special_function_config(ADC0, ADC_CONTINUOUS_MODE, ENABLE);
		
	/* ADC continous function enable */
	//adc_special_function_config(ADC0, ADC_SCAN_MODE, DISABLE); 
	adc_special_function_config(ADC0, ADC_SCAN_MODE, ENABLE); 
 
	/* 右对齐*/
	adc_data_alignment_config(ADC0, ADC_DATAALIGN_RIGHT);
		
	/* 配置规则通道数量 */
	//adc_channel_length_config(ADC0, ADC_REGULAR_CHANNEL, 1);
	adc_channel_length_config(ADC0, ADC_REGULAR_CHANNEL, 2);
	
	/* ADC规则模式配置;2个adc并行模式下,不可以2个adc同时采集一个通道,adc1数据放在高16位,adc0数据放在低16位*/
	adc_regular_channel_config(ADC0, 0, ADC_CHANNEL_13, ADC_SAMPLETIME_55POINT5);
	adc_regular_channel_config(ADC0, 1, ADC_CHANNEL_15, ADC_SAMPLETIME_55POINT5);

	/* adc 使用TIM1 的CH1 的上升沿触发*/
	adc_external_trigger_source_config(ADC0, ADC_REGULAR_CHANNEL, ADC0_1_EXTTRIG_REGULAR_T1_CH1);

	/* adc触发使能*/
	adc_external_trigger_config(ADC0, ADC_REGULAR_CHANNEL, ENABLE);
 
	/* 使能ADC*/
	adc_enable(ADC0);
	delay_1ms(1U);
	
	/* 标定复位ADC */
	adc_calibration_enable(ADC0);

	/* ADC DMA 使能*/
	adc_dma_mode_enable(ADC0);
}

5. 测试代码

这里创建一个thread来测试,测试代码如下:

void LED1_Task(void* parameter)
{
	gpio_config();
	timer1_config();
	dma_config();
	adc_config();
	
	timer_enable(TIMER1);

	while(1){
    //gd_eval_led_toggle(LED3);
		printf("%s tick:%d \n",__FUNCTION__,xTaskGetTickCount());		//Ben 221024#1
		printf("dmaHalfIntCnt:%d \n",dmaHalfIntCnt);
		printf("%d %d %d %d\n",adc_value[0],adc_value[1],adc_value[2],adc_value[3]);		
    vTaskDelay(500); 
  }
}

在500ms间隔下的测试结果为:

dmaHalfIntCnt:2410 
1663 0 1663 1
LED1_Task tick:120965 
dmaHalfIntCnt:2420 
1663 1 1663 0
LED1_Task tick:121469 
dmaHalfIntCnt:2430 
1663 0 1661 1
LED1_Task tick:121973 
dmaHalfIntCnt:2440 
1663 0 1663 0
LED1_Task tick:122477 
dmaHalfIntCnt:2450 

<think>好的,我现在要介绍一下GD32F303CCT6的GPIO复用功能。首先,我需要了解GD32F303CCT6是什么型号的微控制器。GD32系列是兆易创新(GigaDevice)推出的基于Arm Cortex-M内核的微控制器,类似于STM32系列。GD32F303属于高性能系列,Cortex-M4内核,主频较高,适用于工业控制、消费电子等。 接下来是GPIO复用。GPIO即通用输入输出端口,复用功能指的是每个GPIO引脚除了作为普通输入输出外,还可以配置为其他外设功能的引脚,比如UART、SPI、I2C、定时器、ADC等。复用功能通过复用器(Multiplexer)实现,需要配置相应的寄存器来选择引脚的功能模式。 我需要回忆一下GD32的GPIO配置方式。可能涉及到GPIOx_CTL0和GPIOx_CTL1寄存器,每个引脚占用4位,用于设置模式(输入、输出、复用功能、模拟)以及速度。复用功能的选择可能由AFIO(复用功能I/O)相关的寄存器控制,或者像STM32那样有AFR(Alternate Function Register)。 GD32F303CCT6可能有多个复用功能选项,每个引脚可能有多个AF(Alternate Function)映射,具体取决于数据手册中的AFIO配置表。例如,PA9可能作为UART0_TX,或者TIM1_CH2等,具体需要参考数据手册的引脚定义部分。 可能需要注意时钟配置,开启相应GPIO和复用功能外设的时钟,比如RCU_APB2EN寄存器中的对应位。配置步骤大致为:使能时钟,配置GPIO模式为复用功能模式,选择具体的复用功能编号,最后初始化外设本身。 需要注意的是,不同的复用功能可能对应不同的AF编号,例如AF0到AF15,每个外设信号对应特定的AF编号。例如,USART0_TX可能对应AF7,需要将引脚配置为该AF值。 此外,可能还存在重映射功能(Remap),允许将某些外设的引脚映射到不同的GPIO端口上,这可能需要配置AFIO的重映射寄存器。 可能遇到的问题:引脚功能冲突,比如同一引脚同时用于两个外设,或者配置错误导致功能无法正常工作。需要仔细检查数据手册中的引脚复用表,确保正确选择AF模式和对应的外设。 总结,GD32F303CCT6的GPIO复用功能需要配置GPIO的模式为复用模式,选择正确的AF编号,开启相关时钟,并正确配置外设寄存器。具体每个引脚的复用功能需要查阅数据手册中的Alternate Function Mapping表格。</think>GD32F303CCT6 是兆易创新(GigaDevice)基于 Arm Cortex-M4 内核的高性能微控制器,其 GPIO 复用功能允许引脚在普通 I/O 和其他外设功能(如 UART、SPI、定时器等)之间切换。以下是其 GPIO 复用的核心要点: --- ### **1. 复用功能配置步骤** - **a. 使能时钟** 通过 RCU(复位时钟单元)使能 GPIO 端口和相关外设的时钟(如 `RCU_APB2EN` 用于 GPIO 和 AFIO)。 - **b. 配置 GPIO 模式** 在 `GPIOx_CTL0/CTL1` 寄存器中,设置引脚为 **复用功能模式**(AF模式),并选择输出速度(如 50MHz)。 - **c. 选择复用功能编号(AF)** 通过 `GPIO_AFSEL0/GPIO_AFSEL1` 寄存器,为引脚分配具体的复用功能编号(AF0~AF15)。例如: - USART0_TX 可能对应 AF7 - SPI0_SCK 可能对应 AF5 - **d. 外设初始化** 配置对应外设(如 USART、SPI)的寄存器,确保功能启用。 --- ### **2. 关键寄存器** - **GPIOx_CTL0/CTL1** 控制引脚模式(输入/输出/复用/模拟)及驱动能力。 - **GPIO_AFSEL0/GPIO_AFSEL1** 选择引脚的复用功能编号(AF0~AF15)。 - **AFIO_PCF0/AFIO_PCF1** 用于引脚重映射(Remap)配置(如将外设信号切换到其他引脚组)。 --- ### **3. 复用功能示例** 以 **PA9 配置为 USART0_TX** 为例: 1. 使能 GPIOA 和 USART0 时钟: ```c rcu_periph_clock_enable(RCU_GPIOA); rcu_periph_clock_enable(RCU_USART0); ``` 2. 设置 PA9 为复用推挽输出: ```c gpio_init(GPIOA, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_9); ``` 3. 选择 AF7(USART0_TX): ```c gpio_af_set(GPIOA, GPIO_AF_7, GPIO_PIN_9); ``` --- ### **4. 注意事项** - **查阅数据手册**:不同引脚的复用功能映射可能不同,需参考《GD32F30x 用户手册》的 **Alternate Function Mapping** 表格。 - **避免冲突**:确保同一引脚不同时用于多个外设。 - **重映射功能**:若需将外设信号重映射到其他引脚(如将 USART0_TX 从 PA9 切换到 PB6),需配置 AFIO 的重映射寄存器。 --- ### **5. 典型应用场景** - 通信接口:UART、SPI、I2C、CAN。 - 定时器输入/输出:PWM 输出、编码器接口。 - 模拟功能:ADC/DAC 通道(需配置为模拟模式)。 如需具体引脚的复用功能列表,建议直接查阅官方数据手册中的复用功能表。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值