简介
利用GD32 定时器的PWM输入捕获模式来实现PWM波形的占空比和频率的测量。相应的简介可以参考GD32用户手册中关于定时器输入捕获的章节,PWM输入捕获模式是输入捕获模式的一个特例。(记录自己学习过程,如有错误请留言指出)
原理
如何利用定时器测量一个PWM的频率和占空比
只需要测量出下面两个时间 T1 和 T2 即可算出。
frequency = 1/ T1 duty cycle = T2/T1
再来简单看看定时器输入捕获的原理
通 道 输 入 信 号 CIx 有 两 种 选 择 , 一 种 是 TIMERx_CHx 信 号 , 另 一 种 是
TIMERx_CH0,TIMERx_CH1 和 TIMERx_CH2 异或之后的信号。通道输入信号 CIx 先被
TIMER_CK 信号同步,然后经过数字滤波器采样,产生一个被滤波后的信号。通过边沿检测
器,可以选择检测上升沿或者下降沿。通过配置 CHxP 选择使用上升沿或者下降沿。配置
CHxMS.,可以选择其他通道的输入信号,内部触发信号。配置IC 预分频器,使得若干个输入
事件后才产生一个有效的捕获事件。捕获事件发生, TIMERx_CHxCV 存储计数器的值。
如何使用定时器捕获PWM
用户手册里面的描述如下,我们来进行一步一步拆解:
(1)首先一个 PWM波连接到 CI0,在代码中配置TIM2的CH0输入进来,也就是选择了CI0输入信号为TIMER2_CH0 。
(2) 然后配置 TIMERx_CHCTL0 寄存器中 CH0MS 为2’b01,选择通道0 的捕获信号为CI0 并设置上升沿捕获。
(3) 配置 TIMERx_CHCTL0 寄存器中 CH1MS 为2’b10,选择通道1捕获信号为 CI0 并设置下降沿捕获。
具体怎么配置上升沿和下降沿捕获呢,可以看到下面的寄存器配置
内部捕获通道0设置为上升沿捕获,内部捕获通道1设置为下降沿捕获,在代码实现中其实只需要设置内部捕获通道0为上升沿,代码默认设置了内部捕获通道1为下降沿捕获,具体如下:
void timer_input_pwm_capture_config(uint32_t timer_periph, uint16_t channel, timer_ic_parameter_struct* icpwm)
{
//进行了省略,只保留了配置极性
/* Set channel input polarity */
if(TIMER_IC_POLARITY_RISING == icpwm->icpolarity){ //极性取了反
icpolarity = TIMER_IC_POLARITY_FALLING;
}else{
icpolarity = TIMER_IC_POLARITY_RISING;
}
if(TIMER_CH_0 == channel){
/* reset the CH0P and CH0NP bits */
TIMER_CHCTL2(timer_periph) &= (~(uint32_t)(TIMER_CHCTL2_CH0P|TIMER_CHCTL2_CH0NP));
/* set the CH0P and CH0NP bits */
TIMER_CHCTL2(timer_periph) |= (uint32_t)(icpwm->icpolarity);
/* reset the CH1P and CH1NP bits */
TIMER_CHCTL2(timer_periph) &= (~(uint32_t)(TIMER_CHCTL2_CH1P|TIMER_CHCTL2_CH1NP)); //利用icpolarity 配置
/* set the CH1P and CH1NP bits */
TIMER_CHCTL2(timer_periph) |= (uint32_t)((uint32_t)icpolarity << 4U);
}
}
(2) (3) 两步的配置CHxMS主要是,为了把经过边沿选择器生成的上升沿触发信号和下降沿触发信号分别映射到IS0和IS1上 (个人理解这个IS0和IS1是用于分别输入内部的输入捕获CH0通道和CH1通道,用于实现两个输入通道对1个信号的上升沿捕获和下降沿捕获,也就是一个信号输入到两个输入捕获器,只不过一个上升沿触发,一个下降沿触发)
定时器主从管理——复位模式
把TIM2设置为主从管理复位模式,触发源选择CI0FE0,当外部信号为上升沿时触发,根据复位模式的定义,上升沿触发时,会将定时器的计数值清空。定时器主从复位模式,真的是完美契合用于测量PWM频率和占空比,这里借用一下ST手册里面的图来理解一下具体怎么测量频率和占空比的。
第一次配置好定时器后,输入的上升沿会触发定时器,让定时器的计数归0,后面每一次下降沿触发时,CCR2记录定时器的CNT值,也就是高电平时间,上升沿触发时CCR1记录定时器的CNT值,也就是一个周期的时间,然后再将定时器复位从0开始计数。GD32中 CCR1为 CH0CV,CCR2为CH1CV。
(吐槽一下GD32的框图画的是真难理解,很多都是参考ST的框图来进行理解)
具体代码实现
void timer2_pwm_inputcapter_init(unsigned short arr, unsigned short psc)
{
rcu_periph_clock_enable(RCU_GPIOA);
rcu_periph_clock_enable(RCU_AF);
/*configure PA6 (TIMER2 CH0) as alternate function*/
gpio_init(GPIOA, GPIO_MODE_IN_FLOATING, GPIO_OSPEED_50MHZ, GPIO_PIN_6);
/* TIMER2 configuration: PWM input mode ------------------------
the external signal is connected to TIMER2 CH0 pin
the rising edge is used as active edge
the TIMER2 CH0CV is used to compute the frequency value
the TIMER2 CH1CV is used to compute the duty cycle value
------------------------------------------------------------ */
timer_ic_parameter_struct timer_icinitpara;
timer_parameter_struct timer_initpara;
rcu_periph_clock_enable(RCU_TIMER2);
timer_deinit(TIMER2);
/* TIMER2 configuration */
timer_initpara.prescaler = psc;
timer_initpara.alignedmode = TIMER_COUNTER_EDGE;
timer_initpara.counterdirection = TIMER_COUNTER_UP;
timer_initpara.period = arr;
timer_initpara.clockdivision = TIMER_CKDIV_DIV1;
timer_initpara.repetitioncounter = 0;
timer_init(TIMER2, &timer_initpara);
/* TIMER2 configuration */
/* TIMER2 CH0 PWM input capture configuration */
timer_icinitpara.icpolarity = TIMER_IC_POLARITY_RISING; // 配置了 CH0P [CH0NP==0, CH0P==0]:把 CIxFE0 的上升沿作为捕获或者从模式下触发的有效信
// 号,并且 CIxFE0 不会被翻转。
timer_icinitpara.icselection = TIMER_IC_SELECTION_DIRECTTI;
timer_icinitpara.icprescaler = TIMER_IC_PSC_DIV1;
timer_icinitpara.icfilter = 0x0;
timer_input_pwm_capture_config(TIMER2, TIMER_CH_0, &timer_icinitpara);
/* slave mode selection: TIMER2 */
timer_input_trigger_source_select(TIMER2, TIMER_SMCFG_TRGSEL_CI0FE0); // 触发中断源选择 TIMER_SMCFG_TRGSEL_CI0FE0
timer_slave_mode_select(TIMER2, TIMER_SLAVE_MODE_RESTART); // 配置定时器为复位模式
/* select the master slave mode */ // 复位模式. 选中的触发输入的上升沿重新初始化计数器,并且产生更新事件
timer_master_slave_mode_config(TIMER2, TIMER_MASTER_SLAVE_MODE_ENABLE);
/* auto-reload preload enable */
timer_auto_reload_shadow_enable(TIMER2);
/* clear channel 0 interrupt bit */
timer_interrupt_flag_clear(TIMER2, TIMER_INT_FLAG_CH0);
/* channel 0 interrupt enable */
timer_interrupt_enable(TIMER2, TIMER_INT_CH0);
/* channel 0 interrupt enable */
timer_interrupt_enable(TIMER2, TIMER_INT_CH0); // 允许更新中断和CH0捕获中断
nvic_irq_enable(TIMER2_IRQn, 5, 0);
/* 创建消息队列用于接收数据 */
xQueueTime2capter = xQueueCreate(10, sizeof(timer2_ic_pwm_struct));
if (NULL == xQueueTime2capter)
{
while (1)
;
}
/* TIMER2 counter enable */
timer_enable(TIMER2);
}
__IO uint16_t dutycycle = 0;
__IO uint16_t frequency = 0;
void TIMER2_IRQHandler(void)
{
timer2_ic_pwm_struct ic_data;
BaseType_t xHigherPriorityTaskWoken;
xHigherPriorityTaskWoken = pdFALSE;
if (SET == timer_interrupt_flag_get(TIMER2, TIMER_INT_FLAG_CH0))
{
/* clear channel 0 interrupt bit */
timer_interrupt_flag_clear(TIMER2, TIMER_INT_FLAG_CH0);
/* read channel 0 capture value */
ic1value = timer_channel_capture_value_register_read(TIMER2, TIMER_CH_0) + 1;
if (0 != ic1value)
{
/* read channel 1 capture value */
ic2value = timer_channel_capture_value_register_read(TIMER2, TIMER_CH_1) + 1;
/* calculate the duty cycle value */
dutycycle = (ic2value * 100) / ic1value;
/* calculate the frequency value */
frequency = 1000000 / ic1value;
ic_data.dutycycle = dutycycle;
ic_data.frequency = frequency;
ic_data.cnt = timer_counter_read(TIMER2);
xQueueSendFromISR(xQueueTime2capter, &ic_data, &xHigherPriorityTaskWoken);
/* Now the buffer is empty we can switch context if necessary. */
if (xHigherPriorityTaskWoken)
{
/* Actual macro used here is port specific. */
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
}
else
{
dutycycle = 0;
frequency = 0;
}
}
}
输入一个5Khz,50%占空比的方波,使用pwm输入捕获测量的结果如下: