跳转到内容
新建笔记

STM32F1 定时器:PWM、寄存器与 HAL 配置

STM32 定时器把时钟变成计数,再把计数转换成更新事件、输出波形或输入时间戳。理解这一条路径,比逐项背 CubeMX 选项更容易判断配置是否正确。

本页以 STM32F103 和 STM32F1 HAL 为范围;TIM1 是高级定时器,TIM2/3/4 是通用定时器。不同容量的 F103 还可能有其他实例,但不能把某个系列的 TIM2 位宽或功能直接套过来。先用 时钟树说明 确定实际 fTIMf_{\mathrm{TIM}}。

CubeMX 的 TIM2 历史配置:CH1和CH2为PWM,PSC为72减1,ARR为65535,向上计数,通道1比较值为0

图中是某个工程的配置记录,并不是完整板级配置。界面没有显示 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 SourceETR 用作输出参考清除来源的配置,不等同于把 CNT 一律清零
PSC = 72−1,Up,ARR = 65535若 fTIM=72 MHzf_{\mathrm{TIM}}=72\,\mathrm{MHz},则计数为 1 MHz,向上计数周期为 65.536 ms,即约 15.2588 Hz
CKD = No DivisiontDTS=tCK_INTt_{\mathrm{DTS}}=t_{\mathrm{CK\_INT}};它不再对普通 CNT 时钟做一次 PSC 式分频
auto-reload preload = DisableARR 预装载关闭;运行中直接改变 ARR 可能改变当前周期
PWM1,Pulse = 0,High对向上计数而言有效时间为 0;输出还要满足通道、GPIO 和计数器启动条件
Output compare preload = Enable对 CCR 的写入先进入预装载,随后在更新事件装入有效寄存器
Fast Mode = Disable使用普通触发到输出的响应路径

内部时钟下:

fCNT=fTIMPSC+1.f_{\mathrm{CNT}}=\frac{f_{\mathrm{TIM}}}{\mathrm{PSC}+1}.

向上计数、边沿对齐时,CNT 从 0 到 ARR 后回到 0:

fPWM=fTIM(PSC+1)(ARR+1).f_{\mathrm{PWM}}=\frac{f_{\mathrm{TIM}}}{(\mathrm{PSC}+1)(\mathrm{ARR}+1)}.

PWM1、高有效、向上计数且 0≤CCR≤ARR+10\le\mathrm{CCR}\le\mathrm{ARR}+1 时,有效占空比为:

D=CCRARR+1.D=\frac{\mathrm{CCR}}{\mathrm{ARR}+1}.

例如输入 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 的完整往返包含 2ARR2\mathrm{ARR} 个计数时钟(ARR > 0),所以不能简单照搬上面的 ARR + 1 公式。更新事件可能出现在上下两个边界;其频率与 PWM 完整周期频率也要分开。

比较首先产生内部有效/无效状态 OCxREF。CCxP 再决定引脚上的有效电平是高还是低。下表明确了相等时的行为:

模式计数方向OCxREF 有效OCxREF 无效
PWM1向上CNT < CCRCNT ≥ CCR
PWM1向下CNT ≤ CCRCNT > CCR
PWM2向上CNT ≥ CCRCNT < CCR
PWM2向下CNT > CCRCNT ≤ CCR

在 ARR = 999、CCR = 500 的向上计数例中,PWM1 的前 500 个计数时钟有效。High 极性时这些时钟输出高电平;Low 极性时输出低电平。切换极性改变引脚波形,并不自动把内部 OCxREF 的定义改成另一个 PWM 模式。RM0008:TIMx_CCMR1 的 OC1M 定义

原稿中的下图保留用于对照:它漏写相等边界,而且向下计数的比较关系有误,应以上面的文字表为准。

原稿PWM1和PWM2比较表,向下计数条件有误且缺少相等边界,正确关系见本页上表

PSC 的分频值有缓冲;ARR 是否预装载由 ARPE 控制;CCR 的输出预装载由 OCxPE 控制。写寄存器、产生更新事件、CPU 收到更新中断,是相关但不同的操作。

在初始化阶段,常先停止计数、写 PSC/ARR/CCR,产生 EGR.UG 使预装载生效,再清理不需要的旧标志后启动。UG 还可能重置计数/预分频状态并产生 UIF,不能把它当成没有其他影响的“刷新”按钮。URS、UDIS、计数模式和 RCR 会进一步影响更新请求,见 H7 定时器进阶。

CR2.MMS 选择定时器送给其他模块的触发输出 TRGO:

MMS 选择输出来源
ResetEGR.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 头文件

寄存器偏移主要内容
CR10x00CEN bit0、UDIS bit1、URS bit2、OPM bit3、DIR bit4、CMS bits6:5、ARPE bit7、CKD bits9:8
CR20x04CCDS bit3、MMS bits6:4、TI1S bit7;高级定时器的 OISx/OISxN 也位于此处
SMCR0x08SMS bits2:0、TS bits6:4、MSM bit7、ETF bits11:8、ETPS bits13:12、ECE bit14、ETP bit15
DIER0x0CUIE、CCxIE、TIE 等中断使能;UDE、CCxDE 等 DMA 请求使能
SR0x10UIF、CCxIF、TIF 等状态;CCxOF 表示过捕获,不是普通计数器溢出
EGR0x14UG、CCxG、TG 等软件事件产生位
CCMR10x18通道1/2复用为输出时的 OCxM、OCxPE、OCxFE,或输入时的 ICxF、ICxPSC;CCxS 选择方向/映射
CCMR20x1C通道3/4,与 CCMR1 类似
CCER0x20CCxE、CCxP;支持互补输出时还有 CCxNE、CCxNP
CNT0x24当前计数值,方向和重载范围由模式决定
PSC0x2816 位预分频重装值,分频系数为 PSC + 1
ARR0x2C自动重装值,位宽以具体定时器为准
RCR0x30支持该功能的实例所使用的重复计数重装值
CCR1~CCR40x34 / 0x38 / 0x3C / 0x40输出时保存比较值,输入时锁存捕获值
BDTR0x44高级定时器死区 DTG、LOCK、OSSI/OSSR、Break 和 MOE;OIS 位不在这里
DCR0x48DMA burst 基地址 DBA 和传输长度 DBL
DMAR0x4C定时器 DMA burst 访问窗口
OR0x50统一结构中的选项寄存器成员;是否实现及意义必须查具体实例

直接配置模式字段时,应先清相应掩码,再写目标值。对 CCMR1 只执行 |= 6 << 4 无法消除旧的 OC1M 配置,也不能保证 CC1S 已经切回输出。

SR 中常见标志采用写 0 清除的语义。SR=0 会清除所有相关标志;SR &= ~flag 则存在读出后其他事件到达、随后被写回的 0 一并清除的窗口。STM32F1 HAL 的清标志宏采用向目标位写 0、向其余位写 1 的形式。即使正确清标志,同一标志置位期间的多个事件仍可能合并,不能把状态位当作事件队列。HAL 定时器标志宏

TIM_OC_InitTypeDef 的字段描述比较输出,但某些字段只适用于带高级输出功能的实例。

字段配置内容对应硬件
OCModeTIMING、ACTIVE、INACTIVE、TOGGLE、PWM1、PWM2、FORCED_ACTIVE、FORCED_INACTIVECCMRx.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
PWMPWM_ConfigChannel / PWM_Start输出 GPIO 复用;硬件持续输出一般不需要每周期中断
输入捕获IC_ConfigChannel / IC_Start_IT输入引脚、边沿/滤波、捕获中断与过捕获处理
翻转输出OC_ConfigChannel / OC_Start 或 OC_Start_ITTOGGLE 在比较匹配时翻转;完整方波周期通常包含两次翻转
互补 PWMPWM_Start 与 PWMN_Start具体 CHx/CHxN、GPIO、死区、Break、MOE 及功率级需求

只调用 HAL_TIMEx_PWMN_Start 启动的是互补侧,不能据此声称主侧也已启动。原来的 TIM2、TIM3、TIM1 启动片段属于不同选择,不能在只初始化 TIM2 后随意换用其他句柄。

中断链及回调签名见 HAL 中断分派。要测量脉冲周期和转速,继续看 F103 转速测量。