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

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

133.5.5定时器中断代码寄存器分析(2)

/*
 * 整体目的:继续沿着 06TIM 工程理解 TIM1 的计数模式、更新中断与事件寄存器。
 * 课程边界:本节是在阅读 HAL 封装后的寄存器结果,不新增业务功能;
 *           因此只注释工程侧配置和关键寄存器关系,不复制、逐行注释 HAL 库函数。
 * 关键关系:CounterMode 影响 CR1.DIR/CMS,Start_IT 使能 DIER.UIE 与 CR1.CEN,
 *           更新事件置位 SR.UIF,HAL IRQ 处理后进入周期完成回调。
 */

/* ==================== Core/Src/tim.c:工程侧配置入口 ==================== */

TIM_HandleTypeDef htim1;                    /* 定义 TIM1 句柄,把初始化参数与真实寄存器地址联系起来。 */

void MX_TIM1_Init(void)                     /* 配置上一节使用的 1 秒 TIM1 更新周期。 */
{                                            /* TIM1 初始化函数体开始。 */
    htim1.Instance = TIM1;                  /* Instance 指向 TIM1 基地址 0x40012C00。 */
    htim1.Init.Prescaler = 7199U;           /* PSC=7199,实际将 72 MHz 除以 7200。 */
    htim1.Init.CounterMode = TIM_COUNTERMODE_UP; /* 选择向上计数,对应边沿对齐且 DIR=0。 */
    htim1.Init.Period = 9999U;              /* ARR=9999,计数范围为 0~9999,共 10000 拍。 */
    htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; /* CKD 不分频,不等同于 PSC。 */
    htim1.Init.RepetitionCounter = 0U;      /* RCR=0,每次计数溢出都产生更新事件。 */
    htim1.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE; /* 使能 ARR 预装载。 */
    if (HAL_TIM_Base_Init(&htim1) != HAL_OK) /* 让 HAL 根据句柄配置寄存器;库内部不在本节展开。 */
    {                                        /* 初始化失败分支开始。 */
        Error_Handler();                     /* 配置失败时停止正常业务。 */
    }                                        /* 初始化结果判断结束。 */
}                                            /* TIM1 初始化函数结束。 */

/* ==================== Core/Src/main.c:启动与用户回调 ==================== */

HAL_TIM_Base_Start_IT(&htim1);             /* 启动 TIM1,并允许更新事件提出中断请求。 */

void HAL_TIM_PeriodElapsedCallback(         /* HAL 确认更新中断后调用的应用层回调。 */
    TIM_HandleTypeDef *htim)                /* 通过参数判断究竟是哪一路定时器。 */
{                                            /* 周期完成回调开始。 */
    if (htim == &htim1)                    /* 按配套源码比较句柄地址,只响应全局 TIM1 句柄。 */
    {                                        /* TIM1 事件分支开始。 */
        HAL_GPIO_TogglePin(LED1_GPIO_Port, LED1_Pin); /* 每秒翻转一次 LED1,验证事件链路。 */
    }                                        /* TIM1 事件分支结束。 */
}                                            /* 回调结束,CPU 恢复被中断的前台程序。 */

/* ==================== 寄存器位关系:用于读代码,不是替代 HAL 的完整实现 ==================== */

#define TIM_CR1_CEN (1UL << 0)              /* CR1 位 0:计数器使能,置 1 后 CNT 才开始变化。 */
#define TIM_CR1_DIR (1UL << 4)              /* CR1 位 4:边沿对齐模式下选择向上或向下计数。 */
#define TIM_CR1_CMS (3UL << 5)              /* CR1 位 6:5:选择三种中心对齐模式。 */
#define TIM_CR1_ARPE (1UL << 7)             /* CR1 位 7:控制 ARR 是否使用预装载。 */
#define TIM_DIER_UIE (1UL << 0)             /* DIER 位 0:允许更新事件请求 CPU 中断。 */
#define TIM_SR_UIF (1UL << 0)               /* SR 位 0:硬件用它记录更新事件已经发生。 */
#define TIM_EGR_UG (1UL << 0)               /* EGR 位 0:软件写 1 强制产生更新事件。 */

/*
 * 阅读 HAL 时可以理解为以下“读—改—写”关系:
 * 先清除 DIR/CMS,再写入 CounterMode,能避免旧模式位残留。
 */
uint32_t tmpcr1 = htim1.Instance->CR1;     /* 读出 CR1 当前值,保留与计数模式无关的控制位。 */
tmpcr1 &= ~(TIM_CR1_DIR | TIM_CR1_CMS);    /* 清除旧的方向位和中心对齐选择位。 */
tmpcr1 |= htim1.Init.CounterMode;           /* 把工程选择的新计数模式合并到临时值。 */
htim1.Instance->CR1 = tmpcr1;              /* 将组合后的值一次写回真实 TIM1_CR1。 */

一、定时器中断代码寄存器配置分析

1.1 函数解析

TIM_Base_SetConfig() 通过临时变量 tmpcr1 修改控制寄存器:

tmpcr1 = TIMx->CR1;
tmpcr1 &= ~(TIM_CR1_DIR | TIM_CR1_CMS);
tmpcr1 |= Structure->CounterMode;
TIMx->CR1 = tmpcr1;

先清除旧的 DIR 和 CMS,再用按位或写入模式值,能够保证切换模式时不残留旧位。

HAL 宏数值CMS[1:0]DIR模式
TIM_COUNTERMODE_UP0x00000向上计数
TIM_COUNTERMODE_DOWN0x10001向下计数
TIM_COUNTERMODE_CENTERALIGNED10x2001忽略中心对齐模式 1
TIM_COUNTERMODE_CENTERALIGNED20x4010忽略中心对齐模式 2
TIM_COUNTERMODE_CENTERALIGNED30x6011忽略中心对齐模式 3

中心对齐时 DIR 由硬件反映当前方向,软件不能用它选择方向。

1.2 TIM1和TIM8寄存器描述

CR1 是最基础的控制寄存器。STM32F1 参考手册中的主要位如下:

位名称作用
0CEN计数器使能,1 为运行
1UDIS更新禁止;1 时阻止更新事件对寄存器和中断/DMA的影响
2URS更新请求源;限制哪些事件能够产生更新中断/DMA请求
3OPM单脉冲模式;下一次更新事件后停止
4DIR边沿对齐时选择向上/向下;中心对齐时只读
6:5CMS中心对齐模式选择
7ARPEARR 预装载使能
9:8CKD数字滤波/死区所用 tDTS 的时钟分频

CMS 三种模式的差别主要在比较标志置位方向:模式 1 只在向下计数时置位,模式 2 只在向上计数时置位,模式 3 在两个方向都置位。更新事件仍发生在计数边界。

二、定时器中断代码寄存器分析

2.1 STM32

定时器中断涉及一条完整的“条件与许可”链:

CNT到达更新条件
      │
      ▼
SR.UIF = 1  ──────┐
                  ├─ 两者同时成立 → NVIC收到TIM更新中断请求
DIER.UIE = 1 ─────┘
                          │
                          ▼
             HAL_TIM_IRQHandler
                          │
                  清标志并调用回调

SR 说明发生了什么,DIER 决定哪些事件能够申请中断或 DMA。只置位标志而未开启许可,不会进入对应中断;只开启许可而事件没发生,也不会进入。

DIER 位含义:

位名称含义
0UIE更新中断使能
1~4CC1IE~CC4IE通道 1~4 捕获/比较中断使能
5COMIE换相中断使能
6TIE触发中断使能
7BIE刹车中断使能
8UDE更新 DMA 请求使能
9~12CC1DE~CC4DE通道 1~4 DMA 请求使能
13COMDE换相 DMA 请求使能
14TDE触发 DMA 请求使能

SR 中与之配套的标志包括 UIF、CC1IF~CC4IF、COMIF、TIF、BIF;位 9~12 的 CC1OF~CC4OF 表示输入捕获溢出,即上一次捕获值还未处理又来了新边沿。

HAL_TIM_Base_Start_IT() 先使能 DIER.UIE,再使能 CR1.CEN。而 HAL_TIM_IRQHandler() 会同时检查事件标志和中断许可,清除相应标志后调用对应回调函数。

2.2 TIM1和TIM8事件产生寄存器

EGR 是事件产生寄存器,软件向某一位写 1 就能产生相应事件;这些位通常是只写触发位,读回没有普通状态寄存器的意义。

位名称作用
0UG产生更新事件;重新初始化计数器并装载预分频器,是否产生更新请求受 URS/UDIS 影响
1~4CC1G~CC4G产生对应通道的捕获/比较事件
5COMG产生换相事件
6TG产生触发事件
7BG产生刹车事件

TIMx->EGR = TIM_EGR_UG; 并不是“启动定时器”,它的任务是让 PSC 等缓冲配置立即生效。真正启动计数的是 CR1.CEN=1。

2.3 TIM1和TIM8捕获/比较模式寄存器1

CCMR1 同时管理通道 1 和通道 2,而且同一组位会因通道处于输出或输入模式而改变含义:

通道选择位输出模式常用位输入模式常用位
CH1CC1S[1:0]OC1FE、OC1PE、OC1M[2:0]、OC1CEIC1PSC[1:0]、IC1F[3:0]
CH2CC2S[1:0]OC2FE、OC2PE、OC2M[2:0]、OC2CEIC2PSC[1:0]、IC2F[3:0]

CCxS=00 表示输出,非零值把通道接到直接输入、间接输入或内部触发输入。输出模式下,OCxM 选择冻结、匹配置位/清零、翻转、强制电平、PWM1 或 PWM2;OCxPE 控制比较寄存器预装载。输入模式下,ICxF 决定数字滤波采样规则,ICxPSC 决定每 1、2、4 或 8 个有效边沿捕获一次。

flowchart LR
    P[外部引脚TIx] --> S{CCxS选择}
    T[内部触发] --> S
    S --> F[数字滤波ICxF]
    F --> E[边沿检测与分频]
    E --> C[锁存CNT到CCRx]
    C --> I[SR.CCxIF与中断]
    O[比较值CCRx] --> M[OCxM输出比较与PWM]
    M --> Q[通道输出]

所以阅读 CCMR1 时必须先看 CC1S/CC2S,否则很容易把输入滤波位误当成 PWM 模式位。