第一章 课程导学与开发环境搭建

各位同学好,我是你们的嵌入式系统讲师。做嵌入式这行十几年了,调试过的定时器问题少说也有上百个。今天咱们开始这门《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%的场景用通用定时器就够了。高级定时器那些花哨功能,做电机控制才会用到。别一上来就选高级的,给自己找麻烦。

为什么会分这么多类型?说白了就是成本和功能的平衡。基本定时器最便宜,但只能做简单计时。高级定时器功能最强,但寄存器也多,配置起来更复杂。

下面这张图,帮你快速理解定时器家族的结构关系:

STM32定时器家族结构图 APB1 / APB2 总线 基本定时器 TIM6/TIM7 通用定时器 TIM2~TIM5 高级定时器 TIM1/TIM8 仅时基功能 无外部IO引脚 用于定时中断/DAC触发 输入捕获/输出比较 PWM生成 单脉冲模式 死区插入 刹车功能 互补PWM输出 选型原则:够用就好,别杀鸡用牛刀

1.3 开发板与IDE准备

工欲善其事,必先利其器。咱们这门课用的工具链,我挑的是最主流的组合。

硬件准备

  • 开发板:STM32F103C8T6(蓝色药丸板)或STM32F407VET6。我个人习惯用F103入门,便宜耐造,烧了不心疼
  • 调试器:ST-Link V2,淘宝二十块钱那种就行
  • 辅助工具:逻辑分析仪(24MHz以上)、示波器(最好100MHz以上)

避坑指南:我曾经买过一个十几块的ST-Link,结果调试时经常断连,查了三天发现是调试器本身的问题。调试器别买最便宜的,至少选个带隔离的。

软件准备

咱们用两套工具,各有所长:

工具 版本建议 用途
STM32CubeMX 6.x以上 图形化配置时钟、定时器、中断
Keil MDK 5.38以上 代码编写、编译、调试
串口助手 任意 打印调试信息

1.4 硬件连接基础

定时器要工作,首先得把硬件接对。这里我画了个最简连接图,你照着接就行。

核心连接步骤:

  1. 开发板通过ST-Link连接到电脑(SWD接口:SWDIO、SWCLK、GND)
  2. 串口连接:PA9(TX) → USB转串口RX,PA10(RX) → USB转串口TX
  3. 如果需要观察PWM波形,把定时器通道引脚接到示波器探头

我的习惯:每次焊接或接线后,先用万用表量一下电源和地有没有短路。这一步花30秒,能省你半天排查时间。

1.5 第一个定时器工程:从CubeMX开始

咱们不搞那些「从零写寄存器」的苦行僧式教学。用CubeMX生成初始化代码,然后我们在关键地方加自己的逻辑。这才是工业界的做法。

CubeMX配置步骤:

  1. 新建工程,选择芯片型号(比如STM32F103C8)
  2. 配置时钟源:HSE选择外部晶振,LSE可选
  3. 配置定时器:选TIM2,时钟源选Internal Clock
  4. 设置预分频器(PSC)和自动重装载值(ARR)
  5. 使能NVIC中断(如果要用中断模式)
  6. 生成代码,用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」里的使能复选框。这种低级错误,工具越自动化越容易忽略。