定时器中断代码寄存器分析1
132.5.4定时器中断代码寄存器分析(1)
/*
* 整体目的:沿着“工程配置值 → HAL 句柄 → TIM1 寄存器”理解上一节的 1 秒定时器代码。
* 关键区分:TIM_Base_InitTypeDef 保存准备使用的参数;TIM_TypeDef 映射真实硬件寄存器;
* TIM_HandleTypeDef 用 Instance 指针和 Init 成员把二者连接起来。
* 代码范围:结构体仅摘录本节讲到的成员,初始化赋值来自 CubeIDE/06TIM/Core/Src/tim.c。
* HAL_TIM_Base_Init() 等库函数只说明作用和调用边界,不对库内部逐行注释。
*/
/* ==================== HAL 头文件概念摘录:初始化参数 ==================== */
typedef struct /* 定义一组尚未直接写入硬件的定时器基础参数。 */
{ /* TIM_Base_InitTypeDef 成员列表开始。 */
uint32_t Prescaler; /* 预分频寄存器目标值 PSC;实际分频系数为该值加 1。 */
uint32_t CounterMode; /* 计数方向/模式,例如向上、向下或中心对齐。 */
uint32_t Period; /* 自动重装寄存器目标值 ARR;向上计数包含 ARR+1 个节拍。 */
uint32_t ClockDivision; /* CKD 配置,主要影响数字滤波和死区采样时钟。 */
uint32_t RepetitionCounter; /* 高级定时器重复计数值 RCR,控制多少个周期产生一次更新。 */
uint32_t AutoReloadPreload; /* 指定 ARR 是否使用预装载/影子寄存器机制。 */
} TIM_Base_InitTypeDef; /* 为这组初始化参数建立类型名。 */
/* ==================== CMSIS 设备头文件概念摘录:硬件寄存器 ==================== */
typedef struct /* 按地址顺序描述 TIM 外设寄存器布局。 */
{ /* TIM_TypeDef 相关成员摘录开始。 */
volatile uint32_t CR1; /* 控制寄存器 1,含 CEN、DIR、CMS、ARPE、CKD 等位。 */
volatile uint32_t CR2; /* 控制寄存器 2,保存主模式等控制项。 */
volatile uint32_t SMCR; /* 从模式控制寄存器,选择触发源和从模式。 */
volatile uint32_t DIER; /* DMA/中断使能寄存器,UIE 位允许更新中断。 */
volatile uint32_t SR; /* 状态寄存器,UIF 位指示已发生更新事件。 */
volatile uint32_t EGR; /* 事件产生寄存器,写 UG 可强制产生更新事件。 */
volatile uint32_t CNT; /* 当前计数器值,定时器运行时由硬件自动改变。 */
volatile uint32_t PSC; /* 预分频寄存器,把定时器输入时钟分频后送给 CNT。 */
volatile uint32_t ARR; /* 自动重装寄存器,决定基本计数周期的边界。 */
volatile uint32_t RCR; /* 高级定时器重复计数寄存器;TIM1/TIM8 支持。 */
} TIM_TypeDef; /* 为硬件寄存器映射结构建立类型名;此处省略通道等其他成员。 */
/* ==================== HAL 头文件概念摘录:句柄 ==================== */
typedef struct /* 定义 HAL 用来管理一个定时器实例的句柄。 */
{ /* TIM_HandleTypeDef 相关成员摘录开始。 */
TIM_TypeDef *Instance; /* 指向 TIM1、TIM2 等实际寄存器块基地址。 */
TIM_Base_InitTypeDef Init; /* 内嵌一份基础初始化参数,随后由 HAL 写入寄存器。 */
HAL_TIM_ActiveChannel Channel; /* 记录当前活动通道;基本更新中断暂不依赖它。 */
HAL_LockTypeDef Lock; /* HAL 的软件锁状态,防止部分接口重复进入。 */
volatile HAL_TIM_StateTypeDef State; /* 保存 HAL 视角下的定时器运行状态。 */
} TIM_HandleTypeDef; /* 此处省略 DMA、各通道状态和可选回调成员。 */
/* ==================== Core/Src/tim.c:本实验参数如何装入句柄 ==================== */
TIM_HandleTypeDef htim1; /* 创建一个全局句柄,供初始化、IRQ 和应用回调共享。 */
void MX_TIM1_Init(void) /* 填写 TIM1 的 1 秒周期参数并调用 HAL 应用配置。 */
{ /* TIM1 初始化函数体开始。 */
htim1.Instance = TIM1; /* TIM1 宏代表 TIM1 基地址,转换后赋给寄存器指针。 */
htim1.Init.Prescaler = 7199U; /* PSC=7199,使 72 MHz 除以 7200 得到 10 kHz。 */
htim1.Init.CounterMode = TIM_COUNTERMODE_UP; /* CR1.DIR/CMS 对应向上边沿对齐计数。 */
htim1.Init.Period = 9999U; /* ARR=9999,使一次更新包含 10000 个 0.1 ms 节拍。 */
htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; /* CKD 不分频;它不是 PSC 的替代项。 */
htim1.Init.RepetitionCounter = 0U; /* RCR=0,使 TIM1 每次溢出都形成更新事件。 */
htim1.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE; /* 让 ARR 在更新边界生效。 */
if (HAL_TIM_Base_Init(&htim1) != HAL_OK) /* 请求 HAL 把 Init 参数配置到 Instance 指向的硬件。 */
{ /* 初始化失败分支开始。 */
Error_Handler(); /* 失败时停止正常业务,避免错误时钟下继续计数。 */
} /* 初始化结果判断结束。 */
} /* TIM1 初始化函数结束,此时还没有主动启动计数。 */
/* ==================== Core/Src/main.c:初始化与启动是两步 ==================== */
MX_TIM1_Init(); /* 第一步:准备 TIM1 时钟、NVIC 和基础寄存器。 */
HAL_TIM_Base_Start_IT(&htim1); /* 第二步:允许更新中断并启动计数器。 */
/* 运行时可按下面的关系理解,但应用代码通常应继续调用 HAL/LL 接口。 */
htim1.Instance->PSC = htim1.Init.Prescaler; /* 概念对应:Init.Prescaler 最终影响硬件 PSC。 */
htim1.Instance->ARR = htim1.Init.Period; /* 概念对应:Init.Period 最终影响硬件 ARR。 */
一、定时器中断代码寄存器分析
1.1 答疑
uint32_t 是 32 位无符号整数,取值范围为 0~2^32-1,即 0~4294967295。变量加到最大值后继续加 1,会按无符号整数规则回到 0;这叫回绕,不等于程序崩溃。若每秒加 1,大约经过 136.1 年才会回绕。
定时器的计数方向要和更新周期公式一起理解:
| 计数方式 | DIR/CMS | 计数过程 | 一个更新周期的近似公式 |
|---|---|---|---|
| 向上计数 | DIR=0,CMS=00 | 0 → ARR → 0 | (PSC+1)(ARR+1)/f_TIM |
| 向下计数 | DIR=1,CMS=00 | ARR → 0 → ARR | (PSC+1)(ARR+1)/f_TIM |
| 中心对齐 | CMS≠00 | 0 → ARR → 0 | 2×ARR×(PSC+1)/f_TIM |
中心对齐模式会在上下边界改变方向,不会从 ARR 突然跳到 0,所以特别适合需要对称 PWM 的场合。中心对齐公式中的端点计数细节与具体事件定义有关,计算 PWM 基频时应以参考手册的计数时序图为准。
常用频率与单周期时间可直接换算:
| 频率 | 单周期时间 |
|---|---|
| 1 Hz | 1 s |
| 100 Hz | 10 ms |
| 1 kHz | 1 ms |
| 10 kHz | 100 μs |
| 100 kHz | 10 μs |
| 1 MHz | 1 μs |
1.2 定时器代码
1.2.1 示例分析
本实验用 TIM1 产生 1 秒一次的更新中断。定时器时钟为 72 MHz,预分频器 PSC=7199,自动重装值 ARR=9999:
计数器时钟 = 72 MHz / (7199 + 1) = 10 kHz
单次计数时间 = 1 / 10 kHz = 0.1 ms
更新周期 = (9999 + 1) × 0.1 ms = 1 s
启动流程可以压缩为:
flowchart LR
A[MX_TIM1_Init] --> B[HAL_TIM_Base_Init]
B --> C[配置CR1 ARR PSC RCR]
C --> D[写EGR.UG装载预分频器]
D --> E[HAL_TIM_Base_Start_IT]
E --> F[DIER.UIE=1]
F --> G[CR1.CEN=1]
G --> H[更新事件]
H --> I[TIM1_UP_IRQHandler]
I --> J[HAL_TIM_IRQHandler]
J --> K[HAL_TIM_PeriodElapsedCallback]
HAL_TIM_Base_Start_IT(&htim1) 同时完成两件关键工作:允许更新中断 DIER.UIE,再启动计数器 CR1.CEN。只有初始化而不启动,计数器不会运行。
1.2.2 结构体类型
TIM_HandleTypeDef htim1 是 HAL 层的定时器句柄。最重要的两个成员是:
| 成员 | 类型 | 含义 |
|---|---|---|
Instance | TIM_TypeDef * | 指向 TIM1 寄存器块的基地址,最终访问真实硬件寄存器 |
Init | TIM_Base_InitTypeDef | 保存预分频、计数方式、周期、时钟分频、重复计数和自动重装预装载等配置值 |
TIM_TypeDef 是寄存器布局结构体,TIM_Base_InitTypeDef 是初始化参数结构体,二者不能混淆。前者回答“寄存器在哪里”,后者回答“准备写入什么配置”。
1.3 高级控制定时器
1.3.1 高级控制定时器寄存器描述
TIM1 和 TIM8 属于高级控制定时器。它们除基本定时功能外,还具有互补输出、死区、刹车输入和重复计数器等功能。当前实验只使用基本计数与更新中断,但 HAL 句柄仍指向完整的 TIM1 寄存器组。
1.3.1.1 导入代码
工程通过 tim.h 声明 extern TIM_HandleTypeDef htim1;,由 tim.c 定义句柄并完成初始化。main.c 调用 MX_TIM1_Init() 后,再调用 HAL_TIM_Base_Start_IT()。
HAL 库把大量寄存器操作封装成结构体、宏和函数。经典 51 单片机外设较少,初学时常直接写少量寄存器;STM32 同时包含定时器、串口、ADC、DMA 等复杂外设,使用库可以减少重复工作。不过库函数最终仍会读写寄存器,理解这条映射关系有助于排错,而不要求一开始记住每一个寄存器位。
1.3.1.2 结构体名称
三类名称的关系如下:
TIM_HandleTypeDef
├─ Instance ──> TIM_TypeDef (硬件寄存器映射)
└─ Init ──> TIM_Base_InitTypeDef(初始化参数集合)
1.3.1.3 结构体变量
htim1.Instance = TIM1 中的 TIM1 是宏,实质上是把 TIM1 基地址转换为 TIM_TypeDef *。因此 htim1.Instance->PSC = 7199; 最终就是向 TIM1 的预分频寄存器写入数值。因为寄存器成员带有 volatile 属性,编译器每次都必须真的访问硬件,不能把读写随意优化掉。
1.3.1.4 定时器初始化配置
本实验的主要配置为:
| 配置项 | 数值 | 作用 |
|---|---|---|
Prescaler | 7199 | 72 MHz 分频为 10 kHz |
CounterMode | TIM_COUNTERMODE_UP | 向上计数 |
Period | 9999 | 计满 10000 个计数后更新 |
ClockDivision | TIM_CLOCKDIVISION_DIV1 | 数字滤波采样时钟不再分频;不是计数器的 PSC |
RepetitionCounter | 0 | 每次溢出都产生更新,高级定时器有效 |
AutoReloadPreload | TIM_AUTORELOAD_PRELOAD_ENABLE | ARR 先进入影子寄存器,在更新事件时统一生效 |
1.3.1.5 定时器初始化硬件
HAL_TIM_Base_MspInit() 负责芯片相关的底层资源:
- 使能 TIM1 外设时钟;
- 设置 TIM1 更新中断的 NVIC 优先级;
- 使能
TIM1_UP_IRQn。
本实验只做内部计时,没有使用 TIM1 通道引脚,所以不需要配置定时器 GPIO。HAL 中的 __weak HAL_TIM_Base_MspInit() 是可被工程内同名强定义覆盖的默认空实现;CubeMX 在 tim.c 中生成强定义,链接时优先采用它。
1.3.1.6 定时器初始化函数
HAL_TIM_Base_Init() 的主要步骤是:
- 检查句柄与参数;
- 首次初始化时设置锁和状态,并调用
HAL_TIM_Base_MspInit(); - 调用
TIM_Base_SetConfig()写入基础寄存器; - 清理 DMA 突发状态,把句柄状态置为就绪。
这说明 HAL 初始化分成“硬件资源准备”和“定时器寄存器配置”两层。
1.3.1.7 定时器配置函数
TIM_Base_SetConfig() 的核心顺序为:
- 读出
CR1; - 清除旧的
DIR/CMS,写入计数模式; - 配置时钟分频
CKD与自动重装预装载ARPE; - 写
ARR、PSC,高级定时器再写RCR; - 向
EGR.UG写 1,强制产生更新事件,使预分频器等配置立即装载。
这里的“读—改—写”可以避免更改某一字段时破坏 CR1 中其他无关位。初始化阶段产生的更新事件可能置位 SR.UIF,因此正式启动中断前应确保状态标志按 HAL 流程得到妥善处理。