定时器中断代码寄存器分析1

Tutorial: 电子电路 Category: 嵌入式 Published: 2026-10-08 01:26:37 Views: 1 Likes: 0 Comments: 0

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=000 → ARR → 0(PSC+1)(ARR+1)/f_TIM
向下计数DIR=1,CMS=00ARR → 0 → ARR(PSC+1)(ARR+1)/f_TIM
中心对齐CMS≠000 → ARR → 02×ARR×(PSC+1)/f_TIM

中心对齐模式会在上下边界改变方向,不会从 ARR 突然跳到 0,所以特别适合需要对称 PWM 的场合。中心对齐公式中的端点计数细节与具体事件定义有关,计算 PWM 基频时应以参考手册的计数时序图为准。

常用频率与单周期时间可直接换算:

频率单周期时间
1 Hz1 s
100 Hz10 ms
1 kHz1 ms
10 kHz100 μs
100 kHz10 μs
1 MHz1 μ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 层的定时器句柄。最重要的两个成员是:

成员类型含义
InstanceTIM_TypeDef *指向 TIM1 寄存器块的基地址,最终访问真实硬件寄存器
InitTIM_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 定时器初始化配置

本实验的主要配置为:

配置项数值作用
Prescaler719972 MHz 分频为 10 kHz
CounterModeTIM_COUNTERMODE_UP向上计数
Period9999计满 10000 个计数后更新
ClockDivisionTIM_CLOCKDIVISION_DIV1数字滤波采样时钟不再分频;不是计数器的 PSC
RepetitionCounter0每次溢出都产生更新,高级定时器有效
AutoReloadPreloadTIM_AUTORELOAD_PRELOAD_ENABLEARR 先进入影子寄存器,在更新事件时统一生效
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() 的主要步骤是:

  1. 检查句柄与参数;
  2. 首次初始化时设置锁和状态,并调用 HAL_TIM_Base_MspInit();
  3. 调用 TIM_Base_SetConfig() 写入基础寄存器;
  4. 清理 DMA 突发状态,把句柄状态置为就绪。

这说明 HAL 初始化分成“硬件资源准备”和“定时器寄存器配置”两层。

1.3.1.7 定时器配置函数

TIM_Base_SetConfig() 的核心顺序为:

  1. 读出 CR1;
  2. 清除旧的 DIR/CMS,写入计数模式;
  3. 配置时钟分频 CKD 与自动重装预装载 ARPE;
  4. 写 ARR、PSC,高级定时器再写 RCR;
  5. 向 EGR.UG 写 1,强制产生更新事件,使预分频器等配置立即装载。

这里的“读—改—写”可以避免更改某一字段时破坏 CR1 中其他无关位。初始化阶段产生的更新事件可能置位 SR.UIF,因此正式启动中断前应确保状态标志按 HAL 流程得到妥善处理。