1. 定时器在嵌入式开发中的核心地位
在STM32开发中,定时器就像瑞士军刀里的秒表功能,它不仅仅是简单的计时工具。以我十年前第一次用STM32F103做电机控制的经历为例,当时PWM输出、输入捕获、定时中断这些功能全都依赖定时器模块。这款芯片的定时器资源之丰富,在Cortex-M3内核的MCU中堪称性价比王者。
STM32F103C8T6作为蓝桥杯、电子设计竞赛的常客,其内置的4个通用定时器(TIM2-TIM5)和2个高级定时器(TIM1、TIM8)各有所长。就拿最基础的TIM2来说,它能实现:
- 精确到微秒级的延时控制
- PWM波形生成(控制LED亮度或电机转速)
- 输入捕获测量脉冲宽度
- 编码器接口读取旋转位置
硬件定时器与软件延时的本质区别在于:前者是硬件自动计数不占用CPU资源,后者是靠CPU空跑循环实现的阻塞式等待。在实时性要求高的场景,这个差异直接决定系统成败。
2. 定时器底层原理深度拆解
2.1 时钟树与预分频机制
STM32的定时器时钟源自APB1总线(TIM2-TIM4)或APB2总线(TIM1、TIM8)。以72MHz主频为例,经过APB1预分频器后:
- 如果APB1分频系数≠1,定时器时钟会×2补偿
- 最终TIM2的时钟可能是72MHz(APB1不分频)或144MHz(APB1二分频)
预分频器(PSC)的计算公式为:
code复制实际分频系数 = PSC寄存器值 + 1
定时器时钟 = 输入时钟 / (PSC + 1)
例如要实现1MHz的计数频率:
c复制TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_TimeBaseStructure.TIM_Prescaler = 71; // 72MHz/(71+1)=1MHz
2.2 重装载值与计数模式
自动重装载寄存器(ARR)决定计数上限。当计数器达到ARR值时:
- 在向上计数模式下产生更新事件(UEV)
- 可以触发中断或DMA请求
三种基础计数模式对比:
| 模式 | 计数方向 | 典型应用场景 |
|---|---|---|
| 向上计数 | 0 → ARR | PWM输出 |
| 向下计数 | ARR → 0 | 电机控制 |
| 中央对齐 | 0→ARR→0 | 减少开关损耗 |
3. 定时器实战配置全流程
3.1 基础定时器初始化步骤
以TIM3实现1秒定时为例:
c复制// 1. 开启时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
// 2. 时基配置
TIM_TimeBaseInitTypeDef TIM_InitStruct;
TIM_InitStruct.TIM_Period = 9999; // ARR值
TIM_InitStruct.TIM_Prescaler = 7199; // 72MHz/(7199+1)=10kHz
TIM_InitStruct.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_InitStruct.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM3, &TIM_InitStruct);
// 3. 使能中断
TIM_ITConfig(TIM3, TIM_IT_Update, ENABLE);
NVIC_EnableIRQ(TIM3_IRQn);
// 4. 启动定时器
TIM_Cmd(TIM3, ENABLE);
3.2 PWM输出配置技巧
配置TIM2_CH1(PA0)输出PWM的关键点:
c复制GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; // 必须复用推挽输出
GPIO_Init(GPIOA, &GPIO_InitStructure);
TIM_OCInitTypeDef TIM_OCInitStructure;
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = 500; // 初始占空比
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OC1Init(TIM2, &TIM_OCInitStructure);
TIM_OC1PreloadConfig(TIM2, TIM_OCPreload_Enable); // 使能预装载
实测发现:PWM频率=定时器时钟/((ARR+1)(PSC+1))。若需20kHz PWM且占空比分辨率1%,则ARR至少设为99,此时PSC=72MHz/(20kHz100)-1=35
4. 高级应用与避坑指南
4.1 输入捕获测量脉宽
测量PA6(TIM3_CH1)上的脉冲宽度:
c复制// 上升沿捕获配置
TIM_ICInitTypeDef TIM_ICInitStructure;
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 = 0x0;
TIM_ICInit(TIM3, &TIM_ICInitStructure);
// 下降沿捕获配置(同一通道)
TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Falling;
TIM_ICInit(TIM3, &TIM_ICInitStructure);
计算脉宽公式:
code复制脉宽 = (下降沿捕获值 - 上升沿捕获值) * 时钟周期
4.2 常见问题排查表
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| 定时器不计数 | 未调用TIM_Cmd() | 检查TIMx_CR1寄存器的CEN位 |
| PWM无输出 | GPIO未配置为复用模式 | 确认GPIO_Mode=AF_PP |
| 中断不触发 | NVIC未使能 | 调用NVIC_EnableIRQ() |
| 测量脉宽误差大 | 未配置输入滤波器 | 设置TIM_ICFilter=0x0F |
| 定时周期不准 | 时钟源配置错误 | 检查RCC时钟树配置 |
5. 性能优化实战经验
5.1 定时器级联技巧
将TIM2作为TIM3的预分频器可实现超长定时:
c复制// TIM2配置为1kHz
TIM_TimeBaseInit(TIM2, &TIM_InitStruct);
// TIM3时钟源选择ITR1(连接TIM2)
TIM_SelectInputTrigger(TIM3, TIM_TS_ITR1);
TIM_SelectSlaveMode(TIM3, TIM_SlaveMode_External1);
5.2 使用DMA减轻CPU负载
用DMA自动更新PWM占空比:
c复制DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&TIM2->CCR1;
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)pwm_buffer;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;
DMA_Init(DMA1_Channel5, &DMA_InitStructure);
TIM_DMACmd(TIM2, TIM_DMA_CC1, ENABLE);
调试中发现:DMA传输完成中断中必须重新配置DMA缓冲区地址,否则第二次传输会失败。这是STM32F1系列DMA控制器的特性,在参考手册Errata中有提及。
