第一章 课程导学与开发环境搭建
各位同学好,我是你们的嵌入式系统讲师。做嵌入式这行十几年了,调试过的定时器问题少说也有上百个。今天咱们开始这门《STM32定时器配置与中断系统调试与故障定位实战》的第一章。
这一章说白了就是打地基。地基不牢,后面盖楼全是麻烦。我见过太多工程师,上来就写定时器中断代码,结果连时钟都没配对,折腾一整天。嗯,咱们不干那种事。
1.1 课程目标:学完你能干什么?
这门课不是讲理论,是讲实战。学完之后,我希望你能做到三件事:
- 能独立配置STM32任意一款定时器——基本定时器、通用定时器、高级定时器,拿到手就能配
- 能调试中断系统故障——中断不触发、触发乱跳、优先级冲突,这些坑我带你一个个踩过去
- 能定位硬件问题——用示波器看波形、用逻辑分析仪抓时序、用调试器单步跟踪
一句话总结:这门课的目标是让你从「照着例程抄」变成「自己会排查」。
1.2 STM32定时器家族概览
STM32的定时器,说白了就是三兄弟。我刚开始学的时候也搞混过,后来发现记住一个规律就行——看编号。
| 类型 | 定时器编号 | 位数 | 主要特性 | 典型应用 |
|---|---|---|---|---|
| 基本定时器 | TIM6、TIM7 | 16位 | 仅时基功能,无外部IO | 定时中断、DAC触发 |
| 通用定时器 | TIM2~TIM5、TIM9~TIM14 | 16/32位 | 输入捕获、输出比较、PWM | PWM输出、脉冲计数 |
| 高级定时器 | TIM1、TIM8 | 16位 | 带死区插入、刹车功能、互补输出 | 电机控制、数字电源 |
我的经验:做项目时,80%的场景用通用定时器就够了。高级定时器那些花哨功能,做电机控制才会用到。别一上来就选高级的,给自己找麻烦。
为什么会分这么多类型?说白了就是成本和功能的平衡。基本定时器最便宜,但只能做简单计时。高级定时器功能最强,但寄存器也多,配置起来更复杂。
下面这张图,帮你快速理解定时器家族的结构关系:
1.3 开发板与IDE准备
工欲善其事,必先利其器。咱们这门课用的工具链,我挑的是最主流的组合。
硬件准备
- 开发板:STM32F103C8T6(蓝色药丸板)或STM32F407VET6。我个人习惯用F103入门,便宜耐造,烧了不心疼
- 调试器:ST-Link V2,淘宝二十块钱那种就行
- 辅助工具:逻辑分析仪(24MHz以上)、示波器(最好100MHz以上)
避坑指南:我曾经买过一个十几块的ST-Link,结果调试时经常断连,查了三天发现是调试器本身的问题。调试器别买最便宜的,至少选个带隔离的。
软件准备
咱们用两套工具,各有所长:
| 工具 | 版本建议 | 用途 |
|---|---|---|
| STM32CubeMX | 6.x以上 | 图形化配置时钟、定时器、中断 |
| Keil MDK | 5.38以上 | 代码编写、编译、调试 |
| 串口助手 | 任意 | 打印调试信息 |
1.4 硬件连接基础
定时器要工作,首先得把硬件接对。这里我画了个最简连接图,你照着接就行。
核心连接步骤:
- 开发板通过ST-Link连接到电脑(SWD接口:SWDIO、SWCLK、GND)
- 串口连接:PA9(TX) → USB转串口RX,PA10(RX) → USB转串口TX
- 如果需要观察PWM波形,把定时器通道引脚接到示波器探头
我的习惯:每次焊接或接线后,先用万用表量一下电源和地有没有短路。这一步花30秒,能省你半天排查时间。
1.5 第一个定时器工程:从CubeMX开始
咱们不搞那些「从零写寄存器」的苦行僧式教学。用CubeMX生成初始化代码,然后我们在关键地方加自己的逻辑。这才是工业界的做法。
CubeMX配置步骤:
- 新建工程,选择芯片型号(比如STM32F103C8)
- 配置时钟源:HSE选择外部晶振,LSE可选
- 配置定时器:选TIM2,时钟源选Internal Clock
- 设置预分频器(PSC)和自动重装载值(ARR)
- 使能NVIC中断(如果要用中断模式)
- 生成代码,用Keil打开
举个例子,你想让定时器每1ms触发一次中断。假设时钟频率是72MHz:
// 定时器1ms中断配置
// 时钟72MHz,预分频72-1,重装载1000-1
// 定时频率 = 72MHz / 72 / 1000 = 1kHz = 1ms
TIM_HandleTypeDef htim2;
void MX_TIM2_Init(void)
{
htim2.Instance = TIM2;
htim2.Init.Prescaler = 71; // 72分频
htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
htim2.Init.Period = 999; // 1000个计数
htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
HAL_TIM_Base_Init(&htim2);
HAL_TIM_Base_Start_IT(&htim2); // 启动定时器并使能中断
}
关键点:Prescaler和Period都要减1,因为计数器是从0开始计数的。我见过有人直接写72和1000,结果定时时间完全不对。
1.6 中断回调函数
定时器溢出后,MCU会自动跳转到中断服务函数。在HAL库中,我们只需要重写回调函数:
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if (htim->Instance == TIM2)
{
// 每1ms执行一次
// 在这里放你的业务逻辑
HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);
}
}
为什么用回调函数而不是直接写中断函数?说白了,HAL库帮你处理了中断现场的保存和恢复,你只需要关心业务逻辑。这也是我推荐新手用HAL库的原因——减少低级错误。
1.7 常见问题与排查
第一次跑定时器中断,十有八九会遇到问题。我把最常见的几个列出来:
| 现象 | 可能原因 | 排查方法 |
|---|---|---|
| 中断不触发 | NVIC未使能 / 时钟未开启 | 检查HAL_TIM_Base_Start_IT是否调用 |
| 定时时间不准 | PSC或ARR计算错误 | 用逻辑分析仪抓引脚电平验证 |
| 程序卡死 | 中断频率太高 / 回调函数耗时过长 | 减少回调中的代码,先只做IO翻转 |
| 多个定时器冲突 | 共用同一个中断优先级分组 | 检查NVIC优先级配置 |
我曾经踩过的坑:有一次定时器中断死活不触发,查了两小时,最后发现是CubeMX里忘了勾选「NVIC Settings」里的使能复选框。这种低级错误,工具越自动化越容易忽略。