1. 项目背景与核心需求
在蓝桥杯电子类竞赛中,定时器中断的应用是考察选手嵌入式开发能力的重要考点。这个项目通过STM32的定时器1中断实现LED周期性翻转,看似简单却涵盖了嵌入式开发中的多个关键技术点。
我当年第一次参加蓝桥杯时,就曾在定时器配置环节栽过跟头。那个下午调试到实验室关门,才发现是时钟源配置错了。现在回想起来,这些"踩坑"经历反而成了最宝贵的经验。
这个方案特别适合需要精确时间控制的场景,比如:
- 工业设备的周期信号生成
- 物联网终端的状态指示灯
- 需要低功耗运行的设备状态监测
2. 硬件环境搭建
2.1 最小系统组成
要实现这个功能,我们需要准备:
- STM32F103C8T6核心板(蓝桥杯官方指定型号)
- 8MHz外部晶振
- 1个LED灯(接在PC13引脚)
- 220Ω限流电阻
- 杜邦线若干
注意:蓝桥杯竞赛中常使用STM32F103系列,其定时器1是高级控制定时器,功能最丰富但也最复杂。
2.2 电路连接示意图
code复制TIM1_CH1(PA8) --|
|--> LED阳极
PC13 --|
LED阴极 -- 220Ω电阻 -- GND
虽然题目要求用定时器1,但实际LED控制我们用了PC13,这是因为:
- 开发板通常自带PC13连接用户LED
- 定时器通道引脚可能被其他功能占用
- 简化电路连接
3. 软件实现详解
3.1 开发环境配置
使用Keil MDK-ARM开发环境:
- 新建STM32F103C8T6工程
- 添加标准外设库(StdPeriph_Lib)
- 配置工程包含路径:
- \CMSIS
- \StdPeriph_Driver\inc
实测发现:使用HAL库会显著增加代码体积,在资源受限的竞赛环境中建议用标准库。
3.2 定时器1关键配置
c复制// 定时器初始化函数
void TIM1_Init(u16 arr, u16 psc) {
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE);
TIM_TimeBaseStructure.TIM_Period = arr; // 自动重装载值
TIM_TimeBaseStructure.TIM_Prescaler = psc; // 预分频系数
TIM_TimeBaseStructure.TIM_ClockDivision = 0;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure);
TIM_ITConfig(TIM1, TIM_IT_Update, ENABLE);
TIM_Cmd(TIM1, ENABLE);
}
参数计算示例:
- 系统时钟72MHz
- 要实现1Hz翻转(500ms间隔)
- 预分频设为7200-1 → 计数器时钟=10kHz
- 自动重装载值设为5000-1 → 500ms中断
3.3 中断服务函数实现
c复制void TIM1_UP_IRQHandler(void) {
if (TIM_GetITStatus(TIM1, TIM_IT_Update) != RESET) {
TIM_ClearITPendingBit(TIM1, TIM_IT_Update);
GPIO_WriteBit(GPIOC, GPIO_Pin_13,
(BitAction)(1 - GPIO_ReadOutputDataBit(GPIOC)));
}
}
关键点解析:
- 必须先检查中断标志位
- 必须清除中断标志
- 使用BitAction类型确保操作原子性
4. 调试技巧与常见问题
4.1 定时不准的排查步骤
- 检查系统时钟配置
c复制RCC_GetClocksFreq(&rcc_clocks); printf("SYSCLK: %d Hz", rcc_clocks.SYSCLK_Frequency); - 验证定时器时钟使能
c复制if (RCC_APB2Periph_TIM1 != (RCC->APB2ENR & RCC_APB2Periph_TIM1)) printf("TIM1 clock not enabled!"); - 用逻辑分析仪捕获波形
4.2 中断不触发的可能原因
根据我的调试经验,按此优先级排查:
- 未启用全局中断(忘记调用NVIC_Configuration())
- 中断优先级配置冲突
- 定时器未使能(TIM_Cmd)
- 中断标志未清除导致"锁死"
4.3 低功耗优化技巧
在电池供电场景下:
c复制// 进入中断前
__WFI(); // 等待中断
// 中断服务程序中
GPIO_WriteBit(GPIOC, GPIO_Pin_13, Bit_SET);
TIM_ClearITPendingBit(TIM1, TIM_IT_Update);
PWR_EnterSTOPMode(PWR_Regulator_LowPower, PWR_STOPEntry_WFI);
5. 进阶应用扩展
5.1 多定时器协同工作
通过主从模式实现复杂时序:
c复制// 配置TIM1为主模式
TIM_SelectMasterSlaveMode(TIM1, TIM_MasterSlaveMode_Enable);
TIM_SelectOutputTrigger(TIM1, TIM_TRGOSource_Update);
// 配置TIM2为从模式
TIM_SelectSlaveMode(TIM2, TIM_SlaveMode_Trigger);
5.2 PWM呼吸灯实现
利用定时器1的PWM模式:
c复制TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = 50; // 初始占空比
TIM_OC1Init(TIM1, &TIM_OCInitStructure);
TIM_OC1PreloadConfig(TIM1, TIM_OCPreload_Enable);
在中断中动态调整Pulse值即可实现渐变效果。
6. 竞赛实战建议
根据多次带队参赛的经验:
- 提前准备好定时器配置代码模板
- 带上有源晶振作为备用时钟源
- 准备逻辑分析仪或示波器排查时序问题
- 对关键参数(arr/psc)做好预计算表格
一个实用的调试技巧:在中断服务函数开头加上IO翻转,用示波器测量实际中断间隔:
c复制GPIO_WriteBit(GPIOA, GPIO_Pin_0, (BitAction)(1-GPIO_ReadOutputDataBit(GPIOA)));
最后分享一个容易忽略的点:在Keil调试时,记得关闭"Run to main()"选项,否则第一次中断前会有不可预测的延迟。这个细节曾经让我们团队在省赛时多花了半小时调试。