在嵌入式开发中,定时器堪称最灵活的外设之一。无论是驱动蜂鸣器、控制电机转速,还是测量外部信号频率,都离不开对定时器的精准配置。但很多开发者面对PSC和ARR寄存器时,常常陷入公式计算的泥潭而忽略了实际应用场景。本文将以两个典型任务为主线——用TIM4生成1kHz PWM驱动蜂鸣器,以及用TIM3实现输入捕获测量频率,带你穿透理论迷雾,掌握寄存器配置的实战思维。
理解定时器配置的第一步是厘清时钟信号的来龙去脉。以STM32F103系列为例,当使用内部时钟源时,信号经历了这样的旅程:
code复制SYSCLK(72MHz) → AHB(72MHz) → APB1(36MHz) → TIMxCLK(72MHz)
这里有个关键细节:当APB1预分频系数≠1时,定时器时钟会自动倍频。这就是为什么APB1时钟为36MHz,而TIMxCLK却达到72MHz。这个机制保证了即使APB1低速运行,定时器仍能获得较高时间分辨率。
时钟源经过预分频器(PSC)后,得到计数器实际使用的时钟CK_CNT:
code复制f_CK_CNT = TIMxCLK / (PSC + 1)
例如,当PSC=71时:
c复制f_CK_CNT = 72MHz / (71 + 1) = 1MHz
此时每个计数周期为1μs,这个时间基准将直接影响后续PWM频率和捕获精度。
提示:使用CubeMX配置时,时钟树可视化界面能清晰展示各节点频率,避免手工计算错误
假设我们需要用TIM4的通道1输出占空比50%的1kHz方波,配置过程可分为三个步骤:
选择向上计数模式,PWM频率由ARR和PSC共同决定:
code复制f_PWM = f_CK_CNT / (ARR + 1)
结合CK_CNT公式,得到综合表达式:
code复制f_PWM = TIMxCLK / [(PSC + 1) * (ARR + 1)]
为1kHz目标频率,我们需要解这个方程:
code复制72,000,000 / [(PSC + 1) * (ARR + 1)] = 1,000
即:
code复制(PSC + 1) * (ARR + 1) = 72,000
此时有多种解,但应遵循以下原则:
推荐配置方案:
| 参数 | 值 | 计算依据 |
|---|---|---|
| PSC | 719 | 得到CK_CNT=100kHz |
| ARR | 99 | 100kHz/(99+1)=1kHz |
| CCR1 | 50 | 占空比=CCR1/(ARR+1)=50% |
对应初始化代码:
c复制TIM4->PSC = 719;
TIM4->ARR = 99;
TIM4->CCR1 = 50;
TIM4->CCMR1 |= TIM_CCMR1_OC1M_2 | TIM_CCMR1_OC1M_1; // PWM模式1
TIM4->CCER |= TIM_CCER_CC1E; // 使能输出
TIM4->CR1 |= TIM_CR1_CEN; // 启动定时器
用示波器观察输出时,若发现实际频率偏离预期,可按以下流程排查:
常见问题解决方案:
现在我们需要用TIM3的输入捕获功能测量一个约2kHz的方波信号频率。输入捕获的原理是利用定时器记录信号边沿发生的时刻,通过两次捕获的差值计算周期。
选择适当的定时器参数:
为使单个计数周期=1μs,设置:
c复制TIM3->PSC = 71; // 72MHz/(71+1)=1MHz
此时ARR应足够大以容纳整个信号周期,对于2kHz信号:
code复制ARR > 500μs / 1μs = 500
选择ARR=65535(最大值)以确保不溢出。
配置TIM3通道1为输入捕获模式:
c复制// GPIO初始化(略)
TIM3->PSC = 71;
TIM3->ARR = 0xFFFF;
TIM3->CCMR1 |= TIM_CCMR1_CC1S_0; // CC1通道配置为输入
TIM3->CCER |= TIM_CCER_CC1E; // 使能捕获
TIM3->DIER |= TIM_DIER_CC1IE; // 使能捕获中断
TIM3->CR1 |= TIM_CR1_CEN;
中断处理逻辑:
c复制volatile uint32_t last_capture = 0;
volatile uint32_t period = 0;
void TIM3_IRQHandler(void) {
if (TIM3->SR & TIM_SR_CC1IF) {
uint32_t current_capture = TIM3->CCR1;
period = current_capture - last_capture;
last_capture = current_capture;
TIM3->SR = ~TIM_SR_CC1IF; // 清除标志位
}
}
测得周期值后,频率计算为:
code复制f = 1,000,000 / period; // 单位Hz
为提高测量精度,可采用以下策略:
误差来源分析表:
| 误差类型 | 影响程度 | 解决方案 |
|---|---|---|
| 定时器时钟偏差 | 中 | 校准系统时钟源 |
| 边沿抖动 | 高 | 添加硬件滤波,软件多次采样 |
| 中断延迟 | 低 | 使用DMA传输替代中断 |
在实际项目中,常常需要同时使用多个定时器功能。例如用TIM4生成PWM的同时,用TIM3测量反馈信号频率。此时需注意:
检查定时器共享资源:
高级定时器(TIM1/TIM8)支持主从模式,可实现精确同步:
c复制// TIM1作为主定时器
TIM1->CR2 |= TIM_CR2_MMS_1; // 更新事件作为触发输出
// TIM3作为从定时器
TIM3->SMCR |= TIM_SMCR_SMS_2 | TIM_SMCR_TS_2; // 触发从模式
在电机控制等场景中,可能需要动态调整PWM参数:
c复制void adjust_pwm_frequency(uint32_t new_freq) {
uint32_t new_arr = (72000000 / (TIM4->PSC + 1)) / new_freq - 1;
TIM4->ARR = new_arr; // 修改ARR立即生效
TIM4->EGR |= TIM_EGR_UG; // 产生更新事件
}
注意:直接修改ARR可能导致波形不连续,建议在PWM周期边界同步更新
通过上述案例,我们可以总结出定时器配置的通用方法论:
在调试TIM4输出异常时,发现实际频率只有预期的一半。检查发现CubeMX默认配置了重复计数器(RCR)为1,导致每个更新事件需要两个PWM周期。这个案例告诉我们,高级定时器的扩展功能可能带来意料之外的行为,阅读参考手册时不能遗漏任何细节配置项。