1. 项目概述:为什么定时器是STM32的“心脏”?
如果你玩过一阵子STM32,肯定对GPIO、串口这些基础外设不陌生了。但当你开始做点“正经”项目,比如让电机精准转动、让LED呼吸灯平滑变化、或者需要每隔固定时间采集一次传感器数据时,你会发现一个绕不开的核心——定时器。它不像GPIO那样直观,但却是实现精准时间控制、生成复杂波形、测量外部信号频率/脉宽的关键。可以说,定时器是STM32从“点灯单片机”迈向“实时控制系统”的基石。
STM32的定时器家族非常庞大,从基本定时器、通用定时器到高级定时器,功能层层递进。新手常常被数据手册里复杂的框图和各种模式搞得头晕。这篇文章,我就以一个过来人的身份,结合我踩过的无数个坑,带你彻底搞懂STM32的定时器。我们不只讲怎么用HAL库函数配置,更要讲清楚寄存器层面的逻辑、不同模式的应用场景,以及那些官方手册里不会写的调试技巧。目标是让你看完后,不仅能照着“抄作业”,更能理解背后的原理,做到举一反三,真正把定时器这个强大的工具用活。
2. 定时器家族全解析:从基本款到旗舰版
STM32的定时器不是单一的外设,而是一个系列。选型不对,后续开发会非常别扭。我们得先搞清楚手头的芯片有哪些“牌”可以打。
2.1 定时器分类与核心差异
根据功能和复杂度,STM32的定时器主要分为三大类:
基本定时器 (Basic Timers): 比如TIM6和TIM7。这是最精简的版本,功能单一,通常只用于纯粹的定时,产生中断或DMA请求。它没有输入捕获和输出比较这些“花哨”的功能,核心就是一个16位的自动重装载寄存器(ARR)和一个预分频器(PSC)。你可以把它理解为一个精准的“闹钟”,到点就“响”(触发中断)。它常用于为DAC提供触发时钟,或者作为其他定时器的时基。
通用定时器 (General-purpose Timers): 这是最常用、最庞大的家族,比如TIM2, TIM3, TIM4, TIM5(32位),以及TIM9到TIM14等。它们具备定时器的全部基础功能:
- 定时与计数:核心功能。
- 输入捕获:可以测量外部信号的脉冲宽度或频率。比如你想测一个旋转编码器的转速,或者一个PWM信号的占空比,就得用它。
- 输出比较:可以产生单次或连续的脉冲,是生成PWM波的基础。驱动舵机、控制电机转速、做呼吸灯都靠它。
- 编码器接口:部分通用定时器直接支持正交编码器模式,硬件自动解码A、B相,读取位置和方向,做小车底盘必用。
- 从模式:可以让定时器被外部信号(如另一个定时器、外部引脚)同步或复位,实现复杂的联动。
高级定时器 (Advanced Timers): 比如TIM1和TIM8。它们在通用定时器的基础上,增加了用于电机控制和数字电源的“杀手锏”功能:
- 互补输出带死区插入:这是驱动三相全桥电路(如无刷电机、逆变器)的核心。可以生成6路互补的PWM,并自动插入可编程的死区时间,防止上下桥臂直通短路,硬件实现,安全又精准。
- 刹车输入:紧急情况下(如过流),一个外部信号可以瞬间关闭所有PWM输出,将系统置于安全状态。
- 更灵活的重装载和触发机制。
选择建议:对于大多数应用,通用定时器(TIM2-TIM5)是主力。做电机FOC控制,必须用高级定时器(TIM1/TIM8)。基本定时器则用于对功能要求极简的纯定时场景。
2.2 核心寄存器与时钟树剖析
很多人调不通定时器,是因为没搞清时钟从哪里来。STM32的时钟树有点复杂,但对于定时器,我们只需关注一条路径:系统时钟 -> APB总线 -> 定时器时钟。
这里有个关键点:在标准库或HAL库的默认初始化后,定时器的时钟源通常是内部时钟(CK_INT),也就是来自APB总线。而APB总线的时钟又源于系统时钟(SYSCLK)经过分频。你需要查数据手册,确认你使用的定时器挂载在APB1还是APB2总线上,因为它们的最大频率不同(APB1通常36/72MHz,APB2通常72MHz)。
更关键的是预分频器 (PSC)和自动重装载寄存器 (ARR)的理解。这是定时器定时的核心公式:
定时时间 T = (ARR + 1) * (PSC + 1) / Tclk
Tclk: 定时器的输入时钟频率(单位Hz)。PSC: 预分频器值,16位,范围0-65535。它是对输入时钟进行分频。PSC=0表示1分频(即不分频),PSC=1表示2分频,以此类推。所以实际分频系数是(PSC+1)。ARR: 自动重装载值,16位或32位(取决于定时器)。计数器从0计数到ARR,就产生一次溢出更新事件。ARR=0时,计数器计数范围是0-0(即1个数),所以实际计数周期是(ARR+1)。
注意:这个“+1”是新手最容易出错的地方!很多人在计算1ms定时时,直接用72000000Hz / 1000Hz = 72000,然后设置ARR=71999,PSC=0。这是对的,因为ARR+1=72000。如果设置ARR=72000,实际定时就会是1.000013ms,存在微小误差。在要求精确定时(如PID控制)时,这个误差必须考虑。
举个例子:系统时钟72MHz,TIM2挂在APB1上,APB1预分频器为2(实际给定时器的时钟是36MHz?错!)。这里有个“倍频”机制:当APBx的分频系数为1时,定时器时钟等于APBx时钟;否则,定时器时钟等于APBx时钟的2倍。所以,如果APB1时钟是36MHz(72MHz/2),那么TIM2的时钟Tclk实际上是72MHz。这一点在CubeMX的Clock Configuration界面里会显示得很清楚,但自己算的时候务必留意。
假设我们需要一个1ms的定时中断,Tclk = 72MHz。 目标频率F = 1000Hz。 所需计数总数N = Tclk / F = 72000000 / 1000 = 72000。 我们需要让(PSC+1) * (ARR+1) = 72000。 为了充分利用计数器分辨率,通常先设定ARR为最大值(如65535),再反推PSC。这里72000 > 65535,所以ARR不能到最大。我们可以让ARR+1 = 1000,PSC+1 = 72。即设置ARR = 999,PSC = 71。这样T = 1000 * 72 / 72MHz = 0.001s = 1ms。
3. 四大核心功能模式深度实操
理解了基础,我们进入实战。定时器的四大核心功能:定时中断、PWM输出、输入捕获、编码器接口,每一个都有其独特的配置要点和坑。
3.1 模式一:定时中断——精准的“心跳”
这是最基础的模式。配置步骤看似简单,但细节决定成败。
1. 时钟与基本参数配置使用CubeMX或手动初始化,核心是设置PSC和ARR。如上例,实现1ms中断。
// HAL库示例 (以TIM2为例) htim2.Instance = TIM2; htim2.Init.Prescaler = 71; // PSC = 71 htim2.Init.CounterMode = TIM_COUNTERMODE_UP; // 向上计数 htim2.Init.Period = 999; // ARR = 999 htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE; // 推荐使能 if (HAL_TIM_Base_Init(&htim2) != HAL_OK) { Error_Handler(); }关键点:AutoReloadPreload这个参数非常重要。如果使能(ENABLE),你对ARR的修改会在下一次更新事件时才生效,这可以防止在计数器运行时修改ARR导致当前周期错乱。如果禁用(DISABLE),修改ARR会立即生效。在需要动态调整定时的场合(如改变PWM频率),必须理解并妥善处理这个设置。
2. 中断启停与回调函数初始化后,需要启动定时器并开启更新中断。
HAL_TIM_Base_Start_IT(&htim2); // 启动定时器并开启中断中断服务函数由HAL库管理,我们需要重写更新中断的回调函数:
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if (htim->Instance == TIM2) { // 你的1ms任务代码放在这里 // 例如:翻转一个LED,执行一次控制循环 } }避坑指南:
- 中断服务函数要短:绝对不要在中断里做延时、打印等耗时操作。只设置标志位,主循环里查询处理。
- 注意中断优先级:如果系统中有多个中断(如串口、外部中断),需要合理配置NVIC优先级,防止高优先级中断阻塞定时器中断,导致定时“变慢”。
- 调试技巧:在回调函数里翻转一个空闲的GPIO,用示波器测量其频率,可以非常直观地验证定时是否准确,这是硬件调试的黄金手段。
3.2 模式二:PWM输出——驱动世界的波形
PWM是控制亮度、速度、位置的利器。STM32的定时器输出PWM非常灵活。
1. 输出通道配置以TIM3的通道1(PA6)输出PWM为例,需要配置该通道为PWM模式1或2。
TIM_OC_InitTypeDef sConfigOC = {0}; sConfigOC.OCMode = TIM_OCMODE_PWM1; // 模式1:CNT<CCR时有效电平 sConfigOC.Pulse = 500; // 这就是比较寄存器CCR的值,决定占空比 sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH; // 有效电平为高 sConfigOC.OCFastMode = TIM_OCFAST_DISABLE; if (HAL_TIM_PWM_ConfigChannel(&htim3, &sConfigOC, TIM_CHANNEL_1) != HAL_OK) { Error_Handler(); }关键参数解析:
OCMode:PWM1和PWM2的区别在于输出电平极性。简单记:PWM1下,CNT<CCR时输出有效电平(由OCPolarity定义);PWM2则相反。通常用PWM1,配合OCPOLARITY_HIGH,表示占空比期间输出高电平。Pulse: 即CCR寄存器的值。占空比 =Pulse / (ARR + 1)。上例中ARR=999,Pulse=500,占空比约为50%。OCPolarity: 有效电平极性。驱动一些低电平有效的器件(如某些LED模块、蜂鸣器)时,需要设置为TIM_OCPOLARITY_LOW。
2. 动态调整占空比与频率启动PWM后,动态修改占空比很简单:
__HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1, new_pulse_value); // 或者 TIM3->CCR1 = new_pulse_value; // 直接操作寄存器,速度更快但是,动态修改频率(即修改ARR或PSC)就有讲究了:
- 修改ARR(周期):如果
AutoReloadPreload已使能,直接修改htim3.Init.Period并调用HAL_TIM_Base_Init是无效的,因为HAL库不会重新初始化。正确做法是:
修改ARR会同时影响频率和占空比分辨率。因为占空比 = CCR / (ARR+1),ARR变了,同样的CCR值对应的占空比就变了。所以改频率后,通常需要按比例重新计算并设置CCR。__HAL_TIM_SET_AUTORELOAD(&htim3, new_arr_value); // 如果需要立即生效,且当前计数周期已受影响,有时需要强制产生一个更新事件 // __HAL_TIM_GENERATE_SW_EVENT(&htim3, TIM_EVENTSOURCE_UPDATE); - 修改PSC(预分频):可以动态修改,且不影响当前计数周期,会在下一个周期生效。但同样要注意占空比的影响。
__HAL_TIM_SET_PRESCALER(&htim3, new_psc_value);
实操心得:对于电机控制等需要高动态性能的场景,建议使用高级定时器的“预装载”功能。在
TIMx_EGR寄存器中设置UG位(更新事件生成),可以同时更新ARR、PSC和CCR的缓冲器,确保它们在同一个更新事件中同步生效,避免输出波形出现“毛刺”或“断裂”。
3.3 模式三:输入捕获——测量时间的高手
输入捕获功能可以用来测量脉冲宽度(高电平时间、低电平时间)或信号频率。其原理是:在输入信号的边沿(上升沿或下降沿)触发时,将当前计数器的值锁存到捕获/比较寄存器(CCR)中。通过计算两次捕获值之差,就能得到边沿之间的时间间隔。
1. 单通道脉冲宽度测量以测量PA0(TIM2_CH1)上的高电平脉宽为例。
- 步骤1:配置通道为输入捕获模式,上升沿触发。
- 步骤2:在上升沿中断中,清除计数器,开始计时。
- 步骤3:配置为下降沿触发(或在中断中切换边沿)。
- 步骤4:在下降沿中断中,读取CCR的值,这个值就是高电平期间计数器的计数值,乘以计数周期就是高电平时间。
HAL库提供了便捷的HAL_TIM_IC_Start_IT函数,但理解其背后的状态机很重要。更常见的做法是使用PWM输入模式,这是STM32定时器的一个隐藏技能。它利用两个通道(通常是TI1和TI2)自动测量周期和占空比,硬件自动完成上述步骤,只需读取两个CCR寄存器即可,非常精准高效。
2. 频率测量频率测量有两种常用方法:
- 输入捕获法:捕获两个相邻上升沿,计算时间间隔的倒数。适用于低频信号。
- 外部时钟模式2 + 溢出中断法:将待测信号作为定时器的时钟源(
ETR引脚),在固定时间窗口(如1秒)内,统计定时器溢出的次数和剩余的计数值。适用于高频信号。这种方法需要启用定时器溢出更新中断。
避坑指南:
- 噪声与毛刺:输入信号如果有毛刺,会导致误触发。可以在输入引脚前加RC低通滤波,或者在软件中采用连续多次捕获取平均值的滤波算法。
- 溢出处理:当脉冲宽度超过定时器的一个计数周期(ARR)时,会发生溢出。必须在捕获中断中检查并累加溢出次数。代码逻辑要严谨:
// 在更新中断(溢出中断)中 void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if (htim->Instance == TIM2) { capture_overflow_count++; // 溢出计数器加1 } } // 在捕获中断中计算总时间 total_ticks = (capture_overflow_count * (ARR + 1)) + current_capture_value; - 分辨率与量程权衡:提高预分频(PSC)可以降低计数频率,扩大测量量程(能测更宽的脉冲),但会降低时间分辨率。需要根据实际测量范围和要求进行权衡。
3.4 模式四:编码器接口——读取位置与速度
这是做机器人、小车等移动平台必备的功能。正交编码器输出A、B两相90度相位差的方波。STM32的定时器硬件编码器接口可以自动根据A、B相的边沿和相对相位,判断转向并增减计数器,极大减轻了CPU负担。
配置要点:
- 模式选择:将定时器设置为“编码器模式”。在HAL库中,调用
HAL_TIM_Encoder_Init。 - 通道配置:通常将通道1和通道2都配置为输入,并选择极性。
TIM_ENCODERMODE_TI1和TIM_ENCODERMODE_TI2决定了计数方式(仅在TI1边沿、仅在TI2边沿、还是在TI1和TI2的边沿都计数)。TIM_ENCODERMODE_TI12是最常用的,分辨率最高(4倍频)。 - 滤波器:编码器信号可能含有抖动,可以配置输入滤波器来平滑信号,但会引入微小延迟。
- ARR值:设置ARR为最大值(如65535),这样计数器就是一个从0到65535的循环计数器。读取的计数值就是相对位置。
读取与计算:
int16_t current_count = __HAL_TIM_GET_COUNTER(&htim4); // 注意类型,可能是16位或32位如何计算速度?常用方法是M法测速:在固定的采样周期T(比如10ms)内,读取编码器计数值的变化量 ΔCNT。 速度(脉冲数/秒) = ΔCNT / T。 如果编码器是N线,并且使用了4倍频,那么轮子转一圈产生的脉冲数是4 * N。结合轮子周长,就可以算出实际线速度。
避坑指南:
- 计数器溢出与方向:
current_count是16位有符号数(如果定时器是16位)。当正向转动超过65535/2,或反向转动低于0时,需要结合溢出标志和方向标志进行软件补偿,或者使用32位计数的定时器(如TIM2/TIM5)。 - 中断与主循环同步:速度计算通常在定时中断中进行。在中断里读取
current_count,与上一次的值做差,然后更新上一次的值。注意变量类型和访问的原子性(防止主循环和中断同时修改变量),可以使用volatile关键字或关中断操作。 - 机械抖动与噪声:除了硬件滤波,软件上可以做速度的低通滤波(如一阶滞后滤波)来使速度值更平滑。
4. 高级应用与联动技巧
当你掌握了单个定时器的用法后,可以玩更高级的:让多个定时器协同工作。
4.1 定时器主从模式:精准同步的秘诀
主从模式可以让一个定时器(从定时器)的启动、停止、计数被另一个定时器(主定时器)的事件精确控制。这是实现复杂定时序列的关键。
典型应用场景:
- 高精度延时触发:主定时器产生一个精确的脉冲后,经过一段可编程的延时,触发从定时器启动。这在需要严格时序控制的多个动作中非常有用。
- 同步ADC采样:在电机FOC控制中,需要在一个PWM周期中心点对电流进行采样。可以配置高级定时器(TIM1)产生PWM,并使其在计数器达到某个值(如ARR/2)时产生一个触发输出(TRGO),这个触发信号连接到ADC的触发源,从而实现硬件级别的精准同步采样,不受软件中断延迟影响。
- 定时器级联:将一个定时器的更新事件作为另一个定时器的时钟源,从而扩展定时范围。例如,TIM2作为主定时器,其更新事件触发TIM3计数,这样TIM3的计数频率就是TIM2更新事件的频率,可以实现超长定时。
配置方法(以TIM2主、TIM3从为例):
- 配置主定时器 (TIM2): 使其在更新事件时,在
TIM2_TRGO上输出一个触发信号。// 在TIM2初始化代码中 TIM_MasterConfigTypeDef sMasterConfig = {0}; sMasterConfig.MasterOutputTrigger = TIM_TRGO_UPDATE; // 选择更新事件作为触发输出 sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE; HAL_TIMEx_MasterConfigSynchronization(&htim2, &sMasterConfig); - 配置从定时器 (TIM3): 将其触发源设置为
ITR1(假设TIM2的TRGO连接到TIM3的ITR1,具体连接关系需查芯片参考手册的“定时器内部触发连接”表格)。
这样,每当TIM2产生更新事件,TIM3的计数器就会启动/复位一次(取决于具体从模式)。// 在TIM3初始化代码中 TIM_SlaveConfigTypeDef sSlaveConfig = {0}; sSlaveConfig.SlaveMode = TIM_SLAVEMODE_TRIGGER; // 从模式:触发模式 sSlaveConfig.InputTrigger = TIM_TS_ITR1; // 触发输入选择 HAL_TIM_SlaveConfigSynchronization(&htim3, &sSlaveConfig);
4.2 输出比较与输入捕获的进阶玩法
- 输出比较的非PWM应用:输出比较模式不仅可以生成PWM,还可以在计数器匹配时产生中断或翻转输出引脚(Toggle模式)。这可以用来生成精确的单个脉冲或频率可变的方波,而无需重新配置为PWM模式。
- PWM输入模式:前文提到,这是测量频率和占空比的“神器”。它占用两个输入通道(TI1和TI2),自动将一个通道配置为上升沿捕获(测周期),另一个为下降沿捕获(测高电平时间)。配置成功后,只需读取
CCR1和CCR2寄存器,即可分别得到周期值和脉宽值,软件逻辑极其简单。 - 互补输出与死区插入:这是高级定时器的专属功能。在驱动H桥或三相逆变桥时,同一桥臂的上下两个开关管(如Q1和Q2)的PWM信号必须是互补的,且中间必须插入一段两个管子都关闭的“死区时间”,防止上下管直通短路烧毁。STM32的高级定时器可以硬件生成带死区的互补PWM,只需配置
TIMx_BDTR寄存器的DTG位(死区发生器),大大提高了系统的可靠性和开发效率。
5. 调试实战与常见问题排查
理论懂了,代码写了,但东西不工作,是最让人头疼的。下面是我总结的定时器调试“三板斧”和常见问题。
5.1 调试三板斧
- 示波器/逻辑分析仪是王道:任何定时器相关的问题,最终都要落到波形上看。用示波器测量定时器对应GPIO输出的PWM波形,看频率、占空比、毛刺是否符合预期。测量输入捕获引脚的信号,看是否干净,边沿是否陡峭。这是最直接、最有效的调试手段。
- 软件仿真查看寄存器:在Keil或IAR的调试模式下,打开
Peripherals -> Timers -> TIMx窗口,可以实时查看和修改定时器的所有寄存器值(CNT, ARR, PSC, CCRx等)。单步执行代码,观察寄存器的变化是否与预期一致,可以快速定位配置错误。 - 利用中断翻转测试引脚:在中断服务函数或回调函数的开头和结尾,用
HAL_GPIO_TogglePin快速翻转一个测试用的GPIO。用示波器测量这个引脚,可以清晰看到中断是否被触发、中断的响应时间、以及中断服务函数的执行时间。这对于优化代码和排查中断冲突至关重要。
5.2 常见问题速查表
| 问题现象 | 可能原因 | 排查思路与解决方案 |
|---|---|---|
| 定时器根本不启动,无中断/无输出 | 1. 定时器时钟未使能。 2. 未调用启动函数( HAL_TIM_Base_Start_IT,HAL_TIM_PWM_Start等)。3. GPIO模式配置错误(输出模式应为复用推挽输出)。 4. 引脚复用功能未映射到定时器。 | 1. 检查__HAL_RCC_TIMx_CLK_ENABLE()是否调用。2. 确认启动函数在初始化后调用,且参数正确。 3. 用CubeMX检查GPIO配置,或手动设置为 GPIO_MODE_AF_PP。4. 查数据手册,确认该引脚是否支持该定时器通道,并正确配置AF(复用功能)。 |
| 定时中断时间不准 | 1. 时钟源频率计算错误(忽略了APB倍频)。 2. PSC和ARR计算错误(忘了+1)。 3. 中断服务函数执行时间过长,或高优先级中断阻塞。 4. 系统时钟配置错误。 | 1. 用CubeMX的时钟图确认最终定时器时钟频率。 2. 复核定时计算公式 T = (ARR+1)*(PSC+1)/Fclk。3. 优化中断服务函数,检查NVIC优先级配置。 4. 用示波器测量系统主时钟(MCO输出)进行校准。 |
| PWM无输出或占空比不可调 | 1. 输出通道未使能或未启动。 2. CCR值(Pulse)为0或大于ARR。 3. 输出极性配置反了,有效电平为低但未接下拉电阻。 4. GPIO被其他外设占用。 | 1. 确认调用HAL_TIM_PWM_Start(&htim, TIM_CHANNEL_x)。2. 确保 0 <= CCR <= ARR。3. 用示波器看波形,尝试切换 OCPolarity。4. 检查CubeMX的引脚分配视图,解决冲突。 |
| 输入捕获值跳动大/不准 | 1. 输入信号有噪声或振铃。 2. 未配置输入滤波器。 3. 中断处理中未考虑计数器溢出。 4. 边沿检测设置错误。 | 1. 硬件上增加RC滤波或施密特触发器。 2. 适当增大定时器输入捕获通道的滤波器参数( ICFilter)。3. 在更新中断中处理溢出计数。 4. 用示波器确认信号边沿,检查 ICPolarity设置。 |
| 编码器计数方向反或数值乱跳 | 1. A、B相引脚接反。 2. 编码器模式( EncoderMode)选择不当。3. 机械抖动或信号质量差。 4. 未处理计数器溢出(16位计数器)。 | 1. 交换A、B相接线测试。 2. 尝试不同的 EncoderMode(TI1, TI2, TI12)。3. 增加硬件滤波和软件去抖算法。 4. 使用32位定时器,或在软件中扩展为32位计数。 |
| 动态修改ARR/PSC后波形异常 | 1.AutoReloadPreload未使能,修改立即生效打断当前周期。2. 修改ARR后未同步修改CCR,导致占空比突变。 3. 在中断中修改,未考虑时序。 | 1. 使能预装载,让修改在下次更新生效。 2. 改频率时,按比例重新计算并设置CCR: new_ccr = old_ccr * new_arr / old_arr。3. 在定时器更新事件中断中或安全的时间点修改这些寄存器。 |
5.3 性能优化与资源管理心得
- 中断风暴预防:当输入捕获信号频率非常高时,可能会产生密集的中断,消耗大量CPU资源。对于高频信号测量,考虑使用DMA将捕获到的CCR值直接搬运到内存数组,或者使用定时器的溢出计数结合低频查询的方式。
- 32位定时器的使用:对于需要超长定时或高分辨率编码器计数的场合,优先选用TIM2或TIM5(如果芯片支持),它们是32位的通用定时器,计数范围高达0~4294967295,省去了软件处理溢出的麻烦。
- HAL库与寄存器操作的权衡:HAL库配置方便,但效率较低,代码体积大。在中断服务函数、高频调用的PWM占空比更新等对实时性要求高的场景,直接操作寄存器(如
TIMx->CCR1 = value;)是更好的选择。可以混合编程,初始化用HAL,关键操作用寄存器。 - 低功耗模式下的定时器:在STOP等低功耗模式下,大多数定时器会停止。如果需要定时唤醒,要使用独立的低功耗定时器(如LPTIM)或RTC的唤醒功能。