一、引言
在嵌入式开发中,定时器(Timer) 是最常用、也是最容易出问题的外设之一。它看似简单,却几乎贯穿了所有系统:任务调度、PWM 控制、时间测量、信号捕获……只要涉及“时间”或“频率”的地方,就离不开定时器。
二、定时器常见的使用场景
1. 系统时基(定时中断)
-
周期性中断,用于操作系统的心跳(如 1ms Tick)。
-
应用案例:RTOS 调度、定时刷新 UI、传感器采样。
2. pwm波形输出
-
通过调整占空比,灵活实现功率控制。
-
应用案例:电机调速、LED 调光、舵机控制。
3. 输入捕获
-
用于测量信号频率、周期或脉宽。
-
应用案例:测速风扇转速、超声波测距
4. 输出比较
-
定时器计数达到设定值时触发事件。
-
应用案例:精确定时触发 DAC 输出波形。
5. 外部事件计数
-
把定时器当作脉冲计数器使用。
-
应用案例:计数生产线上传感器触发次数。
6. 编码器接口
-
部分定时器支持直接连接增量型编码器。
-
应用案例:电机转角、位移检测
三、定时器调试过程中常见问题与经验
1. 时钟配置问题
-
定时器的时钟源来自 APB 总线。需要确认:
-
系统时钟频率设置正确;
-
APB 分频不为 1 时,定时器时钟会自动加倍(容易忽略)。
-
-
经验:调试定时器前,先打印一下
SystemCoreClock
和rcu_clock_freq_get(CK_APB1)
,确认实际频率,或者在datasheet里看使用的定时器属于哪一个时钟树上的,例如下面的时钟树,能够清楚知道是每个总线的频率。
2. PSC 和 ARR 配置错误
定时周期公式:
-
常见错误:PSC 或 ARR 设置过小,导致溢出过快。
-
经验:计算好目标定时时间后,用
printf
打印 PSC 和 ARR,避免配置偏差。
3. 中断优先级设置
-
如果定时中断优先级过高,可能打断关键业务逻辑;过低则可能延迟执行。
-
经验:RTOS 系统中,建议定时器中断优先级略高于普通外设中断,但低于系统关键中断(如 Systick)。
4. PWM 波形畸变
-
高速 PWM 时,GPIO 驱动不足可能导致波形失真。
-
经验:
-
使用高速 IO 模式;
-
必要时加缓冲驱动电路
-
5. 输入捕获抖动
-
外部信号毛刺可能引起误触发。
-
经验:开启定时器输入滤波,或在硬件加 RC 滤波。
6. 编码器模式
-
编码器 A/B 相位需要对应定时器输入通道,否则解码出错。
-
经验:先用示波器确认编码器信号波形,再接入 MCU。
四、代码示例
1. 定时中断 1ms (LED 翻转)
void timer2_init(void)
{
/* 开启定时器时钟 */
rcu_periph_clock_enable(RCU_TIMER2);
/* 定时器配置 */
timer_parameter_struct timer_initpara;
timer_deinit(TIMER2);
timer_struct_para_init(&timer_initpara);
timer_initpara.prescaler = 60 -1; // 分频系数 (60MHz / 60 = 1MHz)
timer_initpara.alignedmode = TIMER_COUNTER_EDGE;
timer_initpara.counterdirection = TIMER_COUNTER_UP;
timer_initpara.period = 1000-1; // 自动重装载值 (1MHz / 1000 = 1kHz -> 1ms)
timer_initpara.clockdivision = TIMER_CKDIV_DIV1;
timer_init(TIMER2, &timer_initpara);
/* 开启中断 */
timer_interrupt_enable(TIMER2, TIMER_INT_UP);
nvic_irq_enable(TIMER2_IRQn, 1, 1);
/* 启动定时器 */
timer_enable(TIMER2);
}
/* 定时器2中断函数 */
void TIMER2_IRQHandler(void)
{
if(RESET != timer_interrupt_flag_get(TIMER2, TIMER_INT_UP)){
timer_interrupt_flag_clear(TIMER2, TIMER_INT_UP);
/* 用户代码: 每 1ms 执行一次 */
gpio_bit_toggle(GPIOC, GPIO_PIN_13); // 翻转 LED
}
}
2. PWM 输出控制舵机
运行效果:舵机保持在中位(1.5ms 脉宽),通过改变 pulse_value
(如 1000~2000)即可控制舵机转动角度。
void pwm_init(void)
{
/* 开启时钟 */
rcu_periph_clock_enable(RCU_GPIOA);
rcu_periph_clock_enable(RCU_TIMER1);
/* GPIO 配置为复用功能 */
gpio_init(GPIOA, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_1);
/* 定时器配置 (50Hz -> 周期 20ms) */
timer_parameter_struct timer_initpara;
timer_deinit(TIMER1);
timer_struct_para_init(&timer_initpara);
timer_initpara.prescaler = 120 -1; // 120MHz / 120 = 1MHz
timer_initpara.alignedmode = TIMER_COUNTER_EDGE;
timer_initpara.counterdirection = TIMER_COUNTER_UP;
timer_initpara.period = 19999; // 1MHz / 20000 = 50Hz
timer_init(TIMER1, &timer_initpara);
/* PWM 配置 (CH1) */
timer_oc_parameter_struct timer_ocinitpara;
timer_channel_output_struct_para_init(&timer_ocinitpara);
timer_ocinitpara.outputstate = TIMER_CCX_ENABLE;
timer_channel_output_config(TIMER1, TIMER_CH_1, &timer_ocinitpara);
timer_channel_output_pulse_value_config(TIMER1, TIMER_CH_1, 1500); // 默认1.5ms占空比
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_enable(TIMER1);
}
3、定时器CH0上升沿输入捕获,CH2重置计数器输出pwm波
timer_oc_parameter_struct timer_ocinitpara;
timer_parameter_struct timer_initpara;
timer_ic_parameter_struct timer_icinitpara;
rcu_periph_clock_enable(RCU_TIMER1);
timer_deinit(TIMER1);
/* TIMER1 configuration */
timer_initpara.prescaler = 600-1;
timer_initpara.alignedmode = TIMER_COUNTER_EDGE;
timer_initpara.counterdirection = TIMER_COUNTER_UP;
timer_initpara.period = 100000;
timer_initpara.clockdivision = TIMER_CKDIV_DIV1;
timer_initpara.repetitioncounter = 0;
timer_init(TIMER1,&timer_initpara);
/* CH2 configuration in OC PWM0 mode */
timer_ocinitpara.ocpolarity = TIMER_OC_POLARITY_HIGH;
timer_ocinitpara.ocnpolarity = TIMER_OCN_POLARITY_HIGH;
timer_ocinitpara.outputstate = TIMER_CCX_ENABLE;
timer_ocinitpara.outputnstate = TIMER_CCXN_DISABLE;
timer_ocinitpara.ocidlestate = TIMER_OC_IDLE_STATE_LOW;
timer_ocinitpara.ocnidlestate = TIMER_OCN_IDLE_STATE_LOW;
timer_channel_output_config(TIMER1,TIMER_CH_2,&timer_ocinitpara);
timer_channel_output_pulse_value_config(TIMER1,TIMER_CH_2,3999);
timer_channel_output_mode_config(TIMER1,TIMER_CH_2,TIMER_OC_MODE_PWM0);
timer_channel_output_shadow_config(TIMER1,TIMER_CH_2,TIMER_OC_SHADOW_DISABLE);
/* TIMER1 CH0 input capture configuration */
timer_icinitpara.icpolarity = TIMER_IC_POLARITY_RISING;
timer_icinitpara.icselection = TIMER_IC_SELECTION_DIRECTTI;
timer_icinitpara.icprescaler = TIMER_IC_PSC_DIV1;
timer_icinitpara.icfilter = 0x02;
timer_input_capture_config(TIMER1,TIMER_CH_0,&timer_icinitpara);
/* slave mode selection : TIMER1 */
/* TIMER1 input trigger : external trigger connected to CI0 */
timer_input_trigger_source_select(TIMER1,TIMER_SMCFG_TRGSEL_CI0FE0);
timer_slave_mode_select(TIMER1,TIMER_SLAVE_MODE_RESTART);
/* auto-reload preload enable */
timer_auto_reload_shadow_enable(TIMER1);
timer_enable(TIMER1);
测试结果:可以发现,精度还是挺高的小于100ns,可以应用于一些高精度项目。
4. 定时器联动功能
定时器联动是需要根据手册来进行设计的,并不是任意两个都可以进行联动,比如我这边需要联动timer0和timer2,如下图有对应关系表:
timer0产生溢出信号后,会触发边沿信号到timer2
void timer0_timer2(void)
{
timer_parameter_struct timer_initpara;
rcu_periph_clock_enable(RCU_TIMER0);
timer_deinit(TIMER0);
/* TIMER0 configuration */
timer_initpara.prescaler = 0;
timer_initpara.alignedmode = TIMER_COUNTER_CENTER_UP;
timer_initpara.counterdirection = TIMER_COUNTER_UP;
timer_initpara.period = 12000-1;
timer_initpara.clockdivision = TIMER_CKDIV_DIV1;
timer_initpara.repetitioncounter = 0;
timer_init(TIMER0,&timer_initpara);
/* auto-reload preload enable */
timer_auto_reload_shadow_enable(TIMER0);
/* select the master slave mode */
timer_master_slave_mode_config(TIMER0,TIMER_MASTER_SLAVE_MODE_ENABLE);
/* TIMER0 update event is used as trigger output */
timer_master_output_trigger_source_select(TIMER0,TIMER_TRI_OUT_SRC_UPDATE);
rcu_periph_clock_enable(RCU_TIMER2);
/* TIMER2 configuration */
timer_deinit(TIMER2);
timer_initpara.prescaler = 0;
timer_initpara.alignedmode = TIMER_COUNTER_EDGE;
timer_initpara.counterdirection = TIMER_COUNTER_UP;
timer_initpara.period = 10000-1;
timer_initpara.clockdivision = TIMER_CKDIV_DIV1;
timer_initpara.repetitioncounter = 0;
timer_init(TIMER2,&timer_initpara);
/* auto-reload preload enable */
timer_auto_reload_shadow_enable(TIMER2);
/* slave mode selection: TIMER2 */
timer_slave_mode_select(TIMER2,TIMER_SLAVE_MODE_EXTERNAL0);
timer_input_trigger_source_select(TIMER2,TIMER_SMCFG_TRGSEL_ITI0);
timer_enable(TIMER0);
timer_enable(TIMER2);
//timer_interrupt_enable(TIMER0,TIMER_INT_UP);
timer_interrupt_enable(TIMER2,TIMER_INT_UP);
nvic_config(TIMER2_IRQn,0,1);
}