STM32 定时器把时钟变成计数,再把计数转换成更新事件、输出波形或输入时间戳。理解这一条路径,比逐项背 CubeMX 选项更容易判断配置是否正确。
本页以 STM32F103 和 STM32F1 HAL 为范围;TIM1 是高级定时器,TIM2/3/4 是通用定时器。不同容量的 F103 还可能有其他实例,但不能把某个系列的 TIM2 位宽或功能直接套过来。先用 时钟树说明 确定实际 。
读懂原来的 CubeMX 配置
跳转到“读懂原来的 CubeMX 配置”
图中是某个工程的配置记录,并不是完整板级配置。界面没有显示 RCC 时钟树、芯片料号、引脚映射和初始化调用,因此不能据图直接证明 PWM 的实际频率。
| 图中设置 | 含义与需要核对的条件 |
|---|---|
| Slave Mode = Disable | 没有配置从模式控制;不意味着定时器时钟自动正确 |
| Trigger Source = Disable | 没有选择图中所示的从模式触发输入;它与 TRGO 输出选择是两个方向 |
| Clock Source = Disable | 是该版本界面的状态,不能仅凭该词判断 RCC 或计数时钟;核对生成代码及 SMCR。普通内部计数需要相应时钟与内部计数路径 |
| Channel1/2 = PWM Generation | 两个通道配置 PWM;每个通道有独立 CCR 和极性,共用此 TIM 的 PSC、CNT、ARR |
| Channel3/4 = Disable | 对应通道未启用 |
| Combined Channels / XOR / One Pulse | 图中未启用;这些选项的可用性依具体定时器实例 |
| Use ETR as Clearing Source | ETR 用作输出参考清除来源的配置,不等同于把 CNT 一律清零 |
| PSC = 72−1,Up,ARR = 65535 | 若 ,则计数为 1 MHz,向上计数周期为 65.536 ms,即约 15.2588 Hz |
| CKD = No Division | ;它不再对普通 CNT 时钟做一次 PSC 式分频 |
| auto-reload preload = Disable | ARR 预装载关闭;运行中直接改变 ARR 可能改变当前周期 |
| PWM1,Pulse = 0,High | 对向上计数而言有效时间为 0;输出还要满足通道、GPIO 和计数器启动条件 |
| Output compare preload = Enable | 对 CCR 的写入先进入预装载,随后在更新事件装入有效寄存器 |
| Fast Mode = Disable | 使用普通触发到输出的响应路径 |
PSC、ARR、CCR 各管什么
跳转到“PSC、ARR、CCR 各管什么”内部时钟下:
向上计数、边沿对齐时,CNT 从 0 到 ARR 后回到 0:
PWM1、高有效、向上计数且 时,有效占空比为:
例如输入 72 MHz、PSC = 71、ARR = 999、CCR = 500,得到 1 MHz 计数、1 kHz PWM、50% 有效占空比。CCR = 0 对应 0%。若需要 100%,可以在 CCR 可表示 ARR + 1 的条件下使用该值;ARR 已是 16 位最大值 65535 时,16 位 CCR 无法写入 65536,不能把溢出后的 0 当成 100%。
中心对齐计数沿两个方向运行。RM0008 的完整往返包含 个计数时钟(ARR > 0),所以不能简单照搬上面的 ARR + 1 公式。更新事件可能出现在上下两个边界;其频率与 PWM 完整周期频率也要分开。
PWM1、PWM2 和极性
跳转到“PWM1、PWM2 和极性”比较首先产生内部有效/无效状态 OCxREF。CCxP 再决定引脚上的有效电平是高还是低。下表明确了相等时的行为:
| 模式 | 计数方向 | OCxREF 有效 | OCxREF 无效 |
|---|---|---|---|
| PWM1 | 向上 | CNT < CCR | CNT ≥ CCR |
| PWM1 | 向下 | CNT ≤ CCR | CNT > CCR |
| PWM2 | 向上 | CNT ≥ CCR | CNT < CCR |
| PWM2 | 向下 | CNT > CCR | CNT ≤ CCR |
在 ARR = 999、CCR = 500 的向上计数例中,PWM1 的前 500 个计数时钟有效。High 极性时这些时钟输出高电平;Low 极性时输出低电平。切换极性改变引脚波形,并不自动把内部 OCxREF 的定义改成另一个 PWM 模式。RM0008:TIMx_CCMR1 的 OC1M 定义
原稿中的下图保留用于对照:它漏写相等边界,而且向下计数的比较关系有误,应以上面的文字表为准。

预装载与更新事件
跳转到“预装载与更新事件”PSC 的分频值有缓冲;ARR 是否预装载由 ARPE 控制;CCR 的输出预装载由 OCxPE 控制。写寄存器、产生更新事件、CPU 收到更新中断,是相关但不同的操作。
在初始化阶段,常先停止计数、写 PSC/ARR/CCR,产生 EGR.UG 使预装载生效,再清理不需要的旧标志后启动。UG 还可能重置计数/预分频状态并产生 UIF,不能把它当成没有其他影响的“刷新”按钮。URS、UDIS、计数模式和 RCR 会进一步影响更新请求,见 H7 定时器进阶。
TRGO、MSM 与快速模式
跳转到“TRGO、MSM 与快速模式”CR2.MMS 选择定时器送给其他模块的触发输出 TRGO:
| MMS 选择 | 输出来源 |
|---|---|
| Reset | EGR.UG 路径;从模式触发复位时还涉及传播延迟 |
| Enable | 内部计数使能 CNT_EN |
| Update | 更新事件 |
| Compare Pulse | 捕获/比较 1 事件产生的脉冲 |
| OC1REF~OC4REF | 相应输出比较参考信号 |
SMCR.MSM=1 用于协调当前定时器和它的下游定时器:延迟 TRGI 事件对当前定时器的作用,使同步更一致。它不是“把 TRGO 延迟到下一次更新事件”。还要核对芯片的 ITR 连接表,TRGO 并不能任意连接到全部定时器。RM0008:TIMx_SMCR 与定时器同步
例如以 OC2REF 作为 TRGO 时,改变通道 2 的引脚极性不会反转内部 OC2REF。在 F103 默认映射中,TIM2_CH2 可以位于 PA1,但实际引脚仍由封装和 AFIO 重映射决定。
OCxFE 快速模式作用于触发输入到比较输出的路径,并只在规定的 PWM1/PWM2 条件下有效。RM0008 描述的相应最小延迟由 5 个时钟缩短为 3 个时钟;它既不提高 PWM 基频,也不代表引脚“零延迟立即变化”。它通常在触发式单脉冲等场景中才需要特别考虑。
寄存器地图
跳转到“寄存器地图”CMSIS 的 TIM_TypeDef 用统一结构描述寄存器地址;每个成员使用 32 位 C 类型访问,不代表每个硬件寄存器都具有 32 位有效字段,也不代表所有 TIM 实例实现所有成员。F103 的 TIM2~TIM5 计数器属于 16 位实现。ST F103xB CMSIS 头文件
| 寄存器 | 偏移 | 主要内容 |
|---|---|---|
| CR1 | 0x00 | CEN bit0、UDIS bit1、URS bit2、OPM bit3、DIR bit4、CMS bits6:5、ARPE bit7、CKD bits9:8 |
| CR2 | 0x04 | CCDS bit3、MMS bits6:4、TI1S bit7;高级定时器的 OISx/OISxN 也位于此处 |
| SMCR | 0x08 | SMS bits2:0、TS bits6:4、MSM bit7、ETF bits11:8、ETPS bits13:12、ECE bit14、ETP bit15 |
| DIER | 0x0C | UIE、CCxIE、TIE 等中断使能;UDE、CCxDE 等 DMA 请求使能 |
| SR | 0x10 | UIF、CCxIF、TIF 等状态;CCxOF 表示过捕获,不是普通计数器溢出 |
| EGR | 0x14 | UG、CCxG、TG 等软件事件产生位 |
| CCMR1 | 0x18 | 通道1/2复用为输出时的 OCxM、OCxPE、OCxFE,或输入时的 ICxF、ICxPSC;CCxS 选择方向/映射 |
| CCMR2 | 0x1C | 通道3/4,与 CCMR1 类似 |
| CCER | 0x20 | CCxE、CCxP;支持互补输出时还有 CCxNE、CCxNP |
| CNT | 0x24 | 当前计数值,方向和重载范围由模式决定 |
| PSC | 0x28 | 16 位预分频重装值,分频系数为 PSC + 1 |
| ARR | 0x2C | 自动重装值,位宽以具体定时器为准 |
| RCR | 0x30 | 支持该功能的实例所使用的重复计数重装值 |
| CCR1~CCR4 | 0x34 / 0x38 / 0x3C / 0x40 | 输出时保存比较值,输入时锁存捕获值 |
| BDTR | 0x44 | 高级定时器死区 DTG、LOCK、OSSI/OSSR、Break 和 MOE;OIS 位不在这里 |
| DCR | 0x48 | DMA burst 基地址 DBA 和传输长度 DBL |
| DMAR | 0x4C | 定时器 DMA burst 访问窗口 |
| OR | 0x50 | 统一结构中的选项寄存器成员;是否实现及意义必须查具体实例 |
直接配置模式字段时,应先清相应掩码,再写目标值。对 CCMR1 只执行 |= 6 << 4 无法消除旧的 OC1M 配置,也不能保证 CC1S 已经切回输出。
SR 中常见标志采用写 0 清除的语义。SR=0 会清除所有相关标志;SR &= ~flag 则存在读出后其他事件到达、随后被写回的 0 一并清除的窗口。STM32F1 HAL 的清标志宏采用向目标位写 0、向其余位写 1 的形式。即使正确清标志,同一标志置位期间的多个事件仍可能合并,不能把状态位当作事件队列。HAL 定时器标志宏
HAL 输出比较结构体
跳转到“HAL 输出比较结构体”TIM_OC_InitTypeDef 的字段描述比较输出,但某些字段只适用于带高级输出功能的实例。
| 字段 | 配置内容 | 对应硬件 |
|---|---|---|
| OCMode | TIMING、ACTIVE、INACTIVE、TOGGLE、PWM1、PWM2、FORCED_ACTIVE、FORCED_INACTIVE | CCMRx.OCxM;TIMING 保持输出参考而仍可产生比较事件 |
| Pulse | 比较计数值 | CCRx |
| OCPolarity | 主输出高/低有效 | CCER.CCxP |
| OCNPolarity | 互补输出有效极性 | CCER.CCxNP |
| OCFastMode | 快速触发响应 | CCMRx.OCxFE |
| OCIdleState | 主输出规定的空闲状态 | CR2.OISx |
| OCNIdleState | 互补输出规定的空闲状态 | CR2.OISxN |
空闲状态还要结合 MOE、OSSI/OSSR、极性、引脚复用和外部驱动电路理解。“设为 RESET”不能单独证明某个功率级已经处于安全状态。
一个边界明确的 PWM 初始化例子
跳转到“一个边界明确的 PWM 初始化例子”以下函数面向 F103xB 的默认 TIM3_CH1→PA6 映射,假定应用已完成 HAL_Init(),并已把 TIM3 内核时钟配置为 72 MHz。该函数拥有 TIM3 和 PA6,不能在其他模块正在使用它们时重复调用。它展示 1 kHz、50% PWM 的配置;不是含时钟树、启动文件和错误处理主循环的完整固件。
#include "stm32f1xx_hal.h"
TIM_HandleTypeDef htim3_pwm;
HAL_StatusTypeDef tim3_pa6_pwm_start(void){ __HAL_RCC_AFIO_CLK_ENABLE(); __HAL_RCC_GPIOA_CLK_ENABLE(); __HAL_RCC_TIM3_CLK_ENABLE(); __HAL_RCC_TIM3_FORCE_RESET(); __HAL_RCC_TIM3_RELEASE_RESET(); __HAL_AFIO_REMAP_TIM3_DISABLE();
GPIO_InitTypeDef pin = {0}; pin.Pin = GPIO_PIN_6; pin.Mode = GPIO_MODE_AF_PP; pin.Speed = GPIO_SPEED_FREQ_LOW; HAL_GPIO_Init(GPIOA, &pin);
htim3_pwm.Instance = TIM3; htim3_pwm.Init.Prescaler = 71; htim3_pwm.Init.CounterMode = TIM_COUNTERMODE_UP; htim3_pwm.Init.Period = 999; htim3_pwm.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; htim3_pwm.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE; if (HAL_TIM_PWM_Init(&htim3_pwm) != HAL_OK) { return HAL_ERROR; }
TIM_ClockConfigTypeDef clock = {0}; clock.ClockSource = TIM_CLOCKSOURCE_INTERNAL; if (HAL_TIM_ConfigClockSource(&htim3_pwm, &clock) != HAL_OK) { return HAL_ERROR; }
TIM_OC_InitTypeDef channel = {0}; channel.OCMode = TIM_OCMODE_PWM1; channel.Pulse = 500; channel.OCPolarity = TIM_OCPOLARITY_HIGH; channel.OCFastMode = TIM_OCFAST_DISABLE; if (HAL_TIM_PWM_ConfigChannel(&htim3_pwm, &channel, TIM_CHANNEL_1) != HAL_OK) { return HAL_ERROR; } __HAL_TIM_CLEAR_FLAG(&htim3_pwm, TIM_FLAG_UPDATE); return HAL_TIM_PWM_Start(&htim3_pwm, TIM_CHANNEL_1);}运行时若写 __HAL_TIM_SET_COMPARE(&htim3_pwm, TIM_CHANNEL_1, 250),在本例预装载条件下,新比较值于更新事件生效,对应 25% 有效占空比。应用应先检查请求范围,避免将任意上层数值截断后直接写寄存器。
其他工作方式应匹配对应初始化与启动入口:
| 目的 | 典型 HAL 入口 | 还要具备什么 |
|---|---|---|
| 仅计数 | Base_Init / Base_Start | 时钟和计数配置 |
| 更新中断 | Base_Start_IT | 正确向量、NVIC、IRQHandler 和 PeriodElapsedCallback |
| PWM | PWM_ConfigChannel / PWM_Start | 输出 GPIO 复用;硬件持续输出一般不需要每周期中断 |
| 输入捕获 | IC_ConfigChannel / IC_Start_IT | 输入引脚、边沿/滤波、捕获中断与过捕获处理 |
| 翻转输出 | OC_ConfigChannel / OC_Start 或 OC_Start_IT | TOGGLE 在比较匹配时翻转;完整方波周期通常包含两次翻转 |
| 互补 PWM | PWM_Start 与 PWMN_Start | 具体 CHx/CHxN、GPIO、死区、Break、MOE 及功率级需求 |
只调用 HAL_TIMEx_PWMN_Start 启动的是互补侧,不能据此声称主侧也已启动。原来的 TIM2、TIM3、TIM1 启动片段属于不同选择,不能在只初始化 TIM2 后随意换用其他句柄。