GD32定时器输入捕获实战:如何精准测量风扇转速(附完整代码)
最近在做一个智能温控项目,需要实时监控机箱内多个散热风扇的转速。风扇的转速反馈通常是以PWM信号的形式输出,每转一圈会产生固定数量的脉冲。测量这个脉冲信号的频率,就能换算出风扇的实际转速。听起来简单,但实际做起来才发现有不少坑要踩——特别是当你要同时监控多个风扇,还要保证测量精度的时候。
我用的主控是GD32F103,这款芯片的定时器外设功能相当强大,支持输入捕获模式,正好可以用来抓取脉冲信号的边沿。但在多通道测量时,如果处理不当,频繁的中断响应会导致数据严重失真。经过几轮调试和优化,我总结出了一套相对稳定可靠的方案,不仅实现了单通道的精准测量,还通过轮询机制解决了多通道同时捕获的干扰问题。下面我就把整个实现过程,包括硬件配置、中断逻辑、频率计算以及那些容易踩坑的细节,完整地分享出来。
1. 理解输入捕获:从原理到硬件配置
测量频率,本质上就是测量周期性信号两个相邻上升沿(或下降沿)之间的时间间隔。GD32的定时器在输入捕获模式下,可以在检测到指定边沿时,将当前计数器的值(CNT)锁存到对应的捕获/比较寄存器(CCRx)中,并产生中断。我们只要在中断里读取这个捕获值,结合定时器的计数周期,就能算出时间差。
但这里有个关键问题:定时器的计数器是循环计数的。假设我们设置自动重装载值ARR为65535(16位定时器的最大值),计数器从0开始向上计数,到达65535后溢出,回到0重新开始。如果我们要测量的脉冲周期很长,在两个上升沿之间,计数器可能已经溢出了多次。如果不记录这些溢出次数,只凭两次捕获的CNT值相减,结果会是负数或者一个很小的数,完全不对。
所以,完整的周期时间 = (溢出次数 × (ARR+1) + 第二次捕获值 - 第一次捕获值) × 计数时钟周期。
基于这个原理,我们来看具体的硬件配置。我选择的是TIMER2,因为它通道多,且对应的GPIO(PB10, PB11)方便布局。GD32的标准库在引脚映射上有点“小个性”,需要特别注意。
/**
* @brief 风扇PWM输入捕获初始化
* @note 使用TIMER2的通道3和通道4,对应PB10和PB11,需进行完全重映射
*/
void FanPwm_Input_Init(void) {
TIMER_BaseInitPara TIM_TimeBaseStructure;
NVIC_InitPara NVIC_InitStructure;
TIMER_ICInitPara TIM_ICInitStructure;
// 使能TIMER2和GPIOB时钟
RCC_APB1PeriphClock_Enable(RCC_APB1PERIPH_TIMER2, ENABLE);
RCC_APB2PeriphClock_Enable(RCC_APB2PERIPH_GPIOB, ENABLE);
// 关键!必须进行引脚重映射,否则无法使用PB10/PB11的定时器功能
GPIO_PinRemapConfig(GPIO_FULL_REMAP_TIMER2, ENABLE);
// 配置PB10和PB11为浮空输入模式
GPIO_InitPara GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_PIN_10 | GPIO_PIN_11;
GPIO_InitStructure.GPIO_Mode = GPIO_MODE_IN_FLOATING;
GPIO_Init(GPIOB, &GPIO_InitStructure);
// 复位并配置TIMER2时基
TIMER_DeInit(TIMER2);
TIM_TimeBaseStructure.TIMER_Period = 65535; // ARR值,决定最大计数范围
TIM_TimeBaseStructure.TIMER_Prescaler = 107; // 预分频值,108MHz/(107+1)=1MHz,每个计数1us
TIM_TimeBaseStructure.TIMER_ClockDivision = TIMER_CDIV_DIV1;
TIM_TimeBaseStructure.TIMER_CounterMode = TIMER_COUNTER_UP;
TIMER_BaseInit(TIMER2, &TIM_TimeBaseStructure);
// 配置通道3(对应PB10)为输入捕获,上升沿触发
TIM_ICInitStructure.TIMER_ICSelection = TIMER_IC_SELECTION_DIRECTTI;
TIM_ICInitStructure.TIMER_ICPrescaler = TIMER_IC_PSC_DIV1; // 每个边沿都捕获
TIM_ICInitStructure.TIMER_ICPolarity = TIMER_IC_POLARITY_RISING;
TIM_ICInitStructure.TIMER_ICFilter = 0x00; // 不滤波,响应最快
TIM_ICInitStructure.TIMER_CH = TIMER_CH_3; // 注意:标准库中通道编号可能与手册略有差异
TIMER_ICInit(TIMER2, &TIM_ICInitStructure);
// 配置通道4(对应PB11)
TIM_ICInitStructure.TIMER_CH = TIMER_CH_4;
TIMER_ICInit(TIMER2, &TIM_ICInitStructure);
// 配置NVIC,使能定时器更新(溢出)中断
NVIC_InitStructure.NVIC_IRQ = TIMER2_IRQn;
NVIC_InitStructure.NVIC_IRQPreemptPriority = 1;
NVIC_InitStructure.NVIC_IRQSubPriority = 1;

&spm=1001.2101.3001.5002&articleId=148639135&d=1&t=3&u=6f0fc5a789f2407199459335b0f6fda0)

被折叠的 条评论
为什么被折叠?



