STM32频率与相位测量实现
本文介绍基于STM32定时器输入捕获功能的频率测量与双路信号相位差计算方法,涵盖硬件配置、中断处理、抗干扰滤波及低频扩展策略,适用于电力电子与工业控制中的同步检测应用。
基于STM32的频率测量与相位跟踪技术实现
在工业控制和电力电子系统中,信号的频率稳定性与多路之间的相位关系直接决定了系统的运行效率与安全性。比如,在并网逆变器启动时,若输出交流电的频率或相位未能与电网同步,轻则导致冲击电流,重则损坏设备。传统方案常依赖专用测频IC或FPGA实现高精度检测,但成本高、开发周期长。而如今,随着STM32系列MCU性能的不断提升,利用其内置定时器资源即可完成纳秒级时间分辨的频率捕捉与实时相位差计算,为中小型嵌入式系统提供了极具性价比的技术路径。
以STM32F103为例,它配备多个通用和高级定时器(如TIM2~TIM5),每个都支持输入捕获功能。这意味着我们无需额外硬件,仅通过配置GPIO引脚和定时器寄存器,就能将外部周期性信号的边沿变化转化为精确的时间戳。这种“软硬协同”的设计思路,正是现代嵌入式信号处理的核心优势所在。
假设我们需要监测电网电压频率,并判断两路线电压之间的相位差。最直接的方法是使用两个定时器通道同时监听各自的过零点。当信号从正半周进入负半周(或反之)时,比较器将其转换为方波上升沿,触发STM32的输入捕获中断。此时,定时器计数值被自动锁存到CCR(Capture/Compare Register)中,形成一个时间标记。由于两路信号共享同一个定时器时钟源,它们之间的时间差不会受到时钟漂移的影响——这是保证相位测量准确性的关键。
来看一个实际场景:某三相电机控制系统需要实时识别U相与V相之间的相位是否保持120°左右。若因线路故障导致相位偏移过大,控制器必须及时报警或停机。在这种需求下,我们可以将U相信号接入TIM3_CH1(PC6),V相信号接入TIM3_CH2(PC7),并配置为上升沿捕获模式。每当任一通道发生捕获事件,中断服务程序就会读取当前计数值,并尝试匹配另一通道的数据。一旦两者都有有效值,立即计算时间差Δt。再结合已知的信号周期T(可通过连续两次捕获同一通道获得),即可得出相位差:
$$
\phi = \frac{\Delta t}{T} \times 360^\circ
$$
这里有个细节值得注意:如果只等一个完整周期才更新结果,响应速度会滞后。更好的做法是采用“交叉捕获”策略——只要任意一路有新边沿到来,就用最新的时间戳去匹配另一路最近的有效值。这样即使两路信号不完全对齐,也能实现每半周期甚至更短时间内的动态刷新,显著提升跟踪灵敏度。
当然,理想很丰满,现实中的信号往往充满噪声。尤其是在强电磁干扰环境下,微小的毛刺可能被误判为有效边沿,造成频率跳变或相位抖动。为此,STM32提供了可编程数字滤波器机制。例如,在 TIM_ICInitTypeDef 结构体中设置 TIM_ICFilter = 0x0A ,表示需连续8个采样周期满足极性条件才确认一次捕获。这相当于一个简单的脉冲宽度甄别器,能有效抑制高频干扰,同时保留真实信号特征。
另一个常见问题是低频信号测量范围受限。假设系统主频为72MHz,定时器预分频设为1,则每个计数单位对应约13.89ns。对于1kHz以下的信号,一个周期长达1ms,对应的计数值高达72,000,仍在16位寄存器范围内;但若频率降至1Hz以下,周期达1秒,计数值将超过65535,发生溢出。解决办法是在初始化时开启定时器溢出中断,并维护一个32位的“累计计数”变量。每次溢出时递增该变量,最终时间差由“高16位(溢出次数)+低16位(当前计数值)”共同构成,从而将测频下限延伸至毫赫兹级别。
下面是一段典型的输入捕获初始化代码,针对TIM2_CH1(PA0)进行单相信号频率测量:
void TIM2_Cap_Init(void) {
GPIO_InitTypeDef GPIO_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_ICInitTypeDef TIM_ICInitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
// PA0 -> TIM2_CH1
GPIO_InitStructure.GPIO_Pin = GPIO_PIN_0;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// 定时器基础配置:72MHz APB1时钟,不分频
TIM_TimeBaseStructure.TIM_Period = 0xFFFF;
TIM_TimeBaseStructure.TIM_Prescaler = 0;
TIM_TimeBaseStructure.TIM_ClockDivision = 0;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
// 输入捕获配置
TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;
TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;
TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;
TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;
TIM_ICInitStructure.TIM_ICFilter = 0x0F;
TIM_ICInit(TIM2, &TIM_ICInitStructure);
// 开启捕获中断
TIM_ITConfig(TIM2, TIM_IT_CC1, ENABLE);
TIM_Cmd(TIM2, ENABLE);
}
配合中断服务函数,可以实现周期性的频率更新:
uint16_t cap_val1 = 0, cap_val2 = 0;
uint32_t pulse_period = 0;
uint8_t cap_state = 0;
float frequency = 0.0f;
void TIM2_IRQHandler(void) {
if (TIM_GetITStatus(TIM2, TIM_IT_CC1)) {
if (cap_state == 0) {
cap_val1 = TIM_GetCapture1(TIM2);
cap_state = 1;
} else {
cap_val2 = TIM_GetCapture1(TIM2);
pulse_period = cap_val2 - cap_val1;
if (pulse_period != 0) {
frequency = 72000000.0f / pulse_period;
}
cap_val1 = cap_val2;
}
TIM_ClearITPendingBit(TIM2, TIM_IT_CC1);
}
}
这段代码虽然简洁,但在实际部署中还需考虑更多工程细节。例如,若信号频率突然变化剧烈,前一次捕获可能来自旧周期,而后一次已是新周期,导致计算出错。为此,可以在中断中加入合理性判断:若两次捕获间隔超出预期范围(如±20%),则舍弃本次结果,等待下一轮稳定后再更新。此外,也可以引入滑动平均滤波,对连续几个周期的结果做加权处理,进一步平抑波动。
对于双通道相位差测量,推荐使用同一定时器的不同通道(如TIM3_CH1和CH2),避免跨定时器带来的时钟不同步问题。以下是核心初始化部分:
void TIM3_Phase_Capture_Init(void) {
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOC, &GPIO_InitStruct);
TIM_TimeBaseInitTypeDef TIM_TimeBaseStruct;
TIM_TimeBaseStruct.TIM_Period = 0xFFFF;
TIM_TimeBaseStruct.TIM_Prescaler = 0;
TIM_TimeBaseStruct.TIM_ClockDivision = 0;
TIM_TimeBaseStruct.TIM_CounterMode = TIM_UpCounting;
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStruct);
TIM_ICInitTypeDef ICStruct;
ICStruct.TIM_ICFilter = 0xA;
ICStruct.TIM_ICPrescaler = TIM_ICPSC_DIV1;
// 通道1:Signal A
ICStruct.TIM_Channel = TIM_Channel_1;
ICStruct.TIM_ICPolarity = TIM_ICPolarity_Rising;
TIM_ICInit(TIM3, &ICStruct);
// 通道2:Signal B
ICStruct.TIM_Channel = TIM_Channel_2;
TIM_ICInit(TIM3, &ICStruct);
TIM_ITConfig(TIM3, TIM_IT_CC1 | TIM_IT_CC2, ENABLE);
TIM_Cmd(TIM3, ENABLE);
}
中断处理逻辑稍作调整,确保任意一路捕获后都能尝试与另一路配对计算:
uint16_t cap_A = 0, cap_B = 0;
float phase_diff_deg = 0.0f;
int captured_A = 0, captured_B = 0;
void TIM3_IRQHandler(void) {
if (TIM_GetITStatus(TIM3, TIM_IT_CC1)) {
cap_A = TIM_GetCapture1(TIM3);
captured_A = 1;
if (captured_B) CalculatePhaseDifference();
TIM_ClearITPendingBit(TIM3, TIM_IT_CC1);
}
if (TIM_GetITStatus(TIM3, TIM_IT_CC2)) {
cap_B = TIM_GetCapture2(TIM3);
captured_B = 1;
if (captured_A) CalculatePhaseDifference();
TIM_ClearITPendingBit(TIM3, TIM_IT_CC2);
}
}
void CalculatePhaseDifference(void) {
uint32_t dt, T = (uint32_t)(72000000.0f / frequency);
if (cap_A > cap_B) {
dt = cap_A - cap_B;
phase_diff_deg = (dt * 360.0f) / T;
} else {
dt = cap_B - cap_A;
phase_diff_deg = (dt * 360.0f) / T;
}
// 可选:根据cap_A < cap_B判断A是否领先B
captured_A = 0;
captured_B = 0; // 复位标志,准备下一轮
}
整个系统的外围电路设计同样不可忽视。原始正弦信号通常幅值较低且含有谐波成分,直接送入MCU GPIO可能导致误触发。建议先通过高速比较器(如TLV3501)构建过零检测电路,或将信号经运放放大后输入施密特触发器整形为陡峭方波。这样不仅能提高抗干扰能力,还能确保边沿一致性,减少测量偏差。
在更高要求的应用中,还可以结合ADC同步采样,实现幅值、频率、相位三位一体的综合分析。例如,在智能电表中,除了监测电网频率外,还需计算功率因数,这就需要同时获取电压与电流的幅值和相位差。此时可让定时器捕获电压过零点作为基准,启动ADC定时采样电流信号,通过FFT或相关算法提取相位信息,形成完整的电能质量监测方案。
总结来看,基于STM32的频率与相位测量并非单纯依赖某个外设的功能堆砌,而是软硬件协同设计的艺术。从信号调理到中断调度,从数据滤波到误差补偿,每一个环节都在影响最终的测量精度与系统稳定性。然而,一旦掌握其中要领,工程师便能以极低成本构建出媲美专业仪器的实时分析平台。这种高度集成化的设计理念,正在推动电力监控、电机驱动、新能源并网等领域的产品向更小巧、更智能、更可靠的方向持续演进。
更多推荐



所有评论(0)