在嵌入式开发领域,定时器堪称微控制器的"心脏"。对于STM32初学者来说,TIMx定时器模块往往是最令人困惑的外设之一——它既能实现精确计时,又能生成PWM波形,还能触发中断事件。本文将用一个完整的实验项目,带你玩转STM32F103定时器的三大核心功能:更新中断、比较中断和PWM输出。不同于枯燥的实验报告,我们将通过控制LED闪烁和电机调速这两个直观案例,深入剖析定时器的配置技巧。
提示:如果使用其他STM32型号,需注意定时器资源差异。F103系列中,TIM1/TIM8是高级定时器,TIM2-TIM5为通用定时器,TIM6/TIM7是基本定时器。
c复制// 示例:基础工程结构
Project/
├── CMSIS/ // 内核支持文件
├── STM32F10x_StdPeriph_Driver/ // 标准外设库
├── User/
│ ├── main.c // 主程序
│ ├── stm32f10x_conf.h // 库配置文件
│ └── timer.c // 定时器专用模块
└── Project.uvprojx // Keil工程文件
STM32定时器的关键参数可通过以下公式计算:
定时周期公式:
code复制T = (ARR + 1) * (PSC + 1) / Fclk
其中:
ARR:自动重装载值(16位,0-65535)PSC:预分频系数(16位,0-65535)Fclk:定时器时钟频率(单位:Hz)| 参数 | 作用 | 典型设置 |
|---|---|---|
| ARR | 决定计数上限 | 根据需求计算 |
| PSC | 时钟分频 | 降低计数频率 |
| CCR | 比较匹配值 | PWM占空比控制 |
连接三个LED到GPIO引脚:
c复制// TIM2更新中断配置示例
void TIM2_Config(void) {
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
// 定时500ms @72MHz
TIM_TimeBaseStructure.TIM_Period = 4999; // ARR
TIM_TimeBaseStructure.TIM_Prescaler = 7199; // PSC
TIM_TimeBaseStructure.TIM_ClockDivision = 0;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
TIM_Cmd(TIM2, ENABLE);
NVIC_EnableIRQ(TIM2_IRQn);
}
c复制// TIM2中断服务函数
void TIM2_IRQHandler(void) {
if (TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET) {
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
GPIO_ToggleBits(GPIOD, GPIO_Pin_3); // 翻转LED2
}
}
注意:高级定时器TIM1/TIM8的中断服务函数名不同,应为
TIM1_UP_IRQHandler
比较中断允许在计数器值匹配预设的CCRx值时触发中断,其优势在于:
定时计算公式:
code复制T_compare = CCRx * (PSC + 1) / Fclk
c复制// TIM3四通道比较中断配置
void TIM3_Compare_Config(void) {
TIM_OCInitTypeDef TIM_OCInitStructure;
// 时基配置(同上)
// ...
// 通道1配置(1000ms)
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_Timing;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Disable;
TIM_OCInitStructure.TIM_Pulse = 9999; // CCR1值
TIM_OC1Init(TIM3, &TIM_OCInitStructure);
// 同理配置通道2-4...
TIM_ITConfig(TIM3, TIM_IT_CC1|TIM_IT_CC2|TIM_IT_CC3|TIM_IT_CC4, ENABLE);
}
c复制void TIM3_IRQHandler(void) {
if (TIM_GetITStatus(TIM3, TIM_IT_CC1)) {
// 处理CCR1匹配事件
GPIO_ToggleBits(GPIOD, GPIO_Pin_2);
TIM_SetCompare1(TIM3, TIM_GetCapture1(TIM3) + 9999);
}
// 其他通道处理...
TIM_ClearITPendingBit(TIM3, TIM_IT_CC1|TIM_IT_CC2|TIM_IT_CC3|TIM_IT_CC4);
}
脉冲宽度调制(PWM)通过调节占空比控制平均电压:
关键参数:
c复制void TIM4_PWM_Config(uint16_t freq, uint8_t duty) {
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
GPIO_InitTypeDef GPIO_InitStructure;
// 使能时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
// PB8作为TIM4_CH3
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
// 时基配置(10kHz)
TIM_TimeBaseStructure.TIM_Period = 720 - 1; // ARR
TIM_TimeBaseStructure.TIM_Prescaler = 0; // PSC
TIM_TimeBaseInit(TIM4, &TIM_TimeBaseStructure);
// PWM模式配置
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = 360; // 初始50%占空比
TIM_OC3Init(TIM4, &TIM_OCInitStructure);
TIM_Cmd(TIM4, ENABLE);
}
c复制// 按键处理函数示例
void Key_Handler(void) {
static uint16_t pulse = 360;
if(KEY1_Pressed()) { // 增加占空比
pulse += 10;
if(pulse > 720) pulse = 720;
TIM_SetCompare3(TIM4, pulse);
}
if(KEY2_Pressed()) { // 减小占空比
pulse -= 10;
if(pulse < 0) pulse = 0;
TIM_SetCompare3(TIM4, pulse);
}
}
| 现象 | 可能原因 | 解决方法 |
|---|---|---|
| 无中断触发 | 未使能NVIC中断 | 检查NVIC_EnableIRQ() |
| PWM无输出 | GPIO未重映射 | 确认AFIO时钟和重映射配置 |
| 定时不准 | 时钟源错误 | 检查RCC时钟树配置 |
在完成这个综合实验后,建议尝试修改PWM频率观察电机响应变化,或者用单个定时器同时管理LED闪烁和PWM输出。实际项目中,我曾遇到因未及时清除中断标志导致死循环的问题——这个教训让我养成了在中断退出前双重检查状态位的习惯。