1. 为什么你写的定时器代码总在“差不多”时出错?
我第一次在STM32上用TIM2做1ms精确延时时,把PSC设成7199、ARR设成99,烧录后用示波器一测——实际周期是1.023ms。当时以为是晶振误差,换了三颗不同批次的8MHz晶振,结果偏差反而更大:1.041ms、1.038ms、1.052ms。后来才发现,问题根本不在晶振,而在我脑子里那套“PSC × ARR = 总计数”的直觉算法——它只对了一半,而且恰恰漏掉了最要命的那半。
这绝不是个例。翻遍ST官方论坛、CSDN和电子发烧友社区,关于“STM32定时器不准”的提问常年稳居TOP5,回复里高频出现的词是:“检查时钟树”“确认APB分频”“看手册第几页”。但没人告诉你,真正卡住90%工程师的,从来不是看不懂寄存器定义,而是算不清三个数字之间的数学关系:PSC(预分频器)、ARR(自动重装载值)和时钟源频率。这三个数像三根咬合的齿轮,少一根或错一齿,整个定时逻辑就脱节。更麻烦的是,它们的计算依赖链极长:从HSE/HSI起振→系统时钟SYSCLK配置→APB总线分频→最终到达定时器的输入时钟→再经PSC二次分频→最后由ARR决定溢出周期。中间任意一环理解偏差,结果就漂移。
你可能已经背熟了公式:Tout = (PSC + 1) × (ARR + 1) / CK_CNT。但关键问题是:CK_CNT 是多少?它真的等于你认为的“APB1时钟”吗?比如TIM2挂载在APB1总线上,而APB1预分频器(RCC_CFGR.PPRE1)被设为2(即APB1时钟 = SYSCLK / 2),那么当SYSCLK=72MHz时,APB1时钟确实是36MHz——但TIM2的输入时钟却是72MHz,不是36MHz。这个反直觉的事实,正是ST芯片手册里用加粗斜体强调的规则:“If the APB prescaler is configured to divide by 1, the timer clock equals the APB clock; otherwise it equals twice the APB clock.”(如果APB预分频为1,定时器时钟等于APB时钟;否则为APB时钟的两倍)。这条规则藏在RM0008参考手册第102页的角落,却让无数人栽在第一个计算环节。
所以本文不讲寄存器怎么写、CubeMX怎么点,只聚焦一个动作:把PSC、ARR、时钟源这三个数掰开、揉碎、重新组装,让你每次配置前都能在草稿纸上写出确定无疑的结果。我会用真实调试记录还原三次典型错误场景——不是告诉你“应该设多少”,而是带你走一遍“为什么必须这样设”的推演过程。如果你曾因定时器不准而反复改代码、换晶振、怀疑硬件,这篇文章就是为你写的。
2. PSC陷阱:你以为的“分频系数”其实是“减1值”
2.1 从寄存器定义看本质:PSC不是分频器,而是计数器初值偏移量
打开STM32F103的数据手册(DS5383),翻到TIMx_PSC寄存器描述页,第一行写着:“Prescaler reload value. This register is loaded to the active prescaler register after the update event.” 翻译过来是:“预分频器重载值。该寄存器在更新事件后加载到活动预分频器寄存器中。” 注意关键词:reload value(重载值),不是“prescaler value(分频值)”。这意味着PSC寄存器存储的不是一个直接用于除法的系数,而是一个计数器达到该值后触发重载的阈值。
再看时序图:定时器时钟CK_CNT每来一个脉冲,内部预分频计数器(PSC Counter)就加1;当计数器值等于PSC寄存器值时,产生一次“预分频溢出”,同时计数器清零,且向后续的计数器(CNT)发送一个脉冲。因此,真正的分频系数 = PSC寄存器值 + 1。例如PSC=0时,计数器从0开始,下一个脉冲就溢出(0→1时等于PSC),所以分频系数为1;PSC=99时,计数器从0计到99共100个状态(0,1,2,...,99),才产生一次溢出,分频系数为100。
这个“+1”看似简单,却是第一大陷阱。我见过太多人在CubeMX里把PSC设为99,然后在代码注释里写“// 分频100”,这本身没错;但当他手动写寄存器时,却直接写TIM2->PSC = 100,以为“分频100就填100”,结果实际分频成了101。更隐蔽的是,在动态修改PSC时,有人会写:
// 错误写法:想把分频从100改成50 TIM2->PSC = 50; // 实际分频变成51,且未触发更新事件这会导致定时器在运行中突变分频,CNT计数值跳变,输出波形毛刺。正确做法必须配合UG位(Update Generation):
// 正确写法 TIM2->PSC = 49; // 分频50 → 填49 TIM2->EGR |= TIM_EGR_UG; // 强制更新事件,使新PSC生效2.2 实战验证:用示波器抓取PSC切换瞬间的波形畸变
为了验证上述行为,我设计了一个对比实验:用TIM2通道1输出PWM,初始PSC=7199(分频7200),ARR=99,目标周期1ms;然后在按键中断里执行PSC切换。示波器探头接在PA0(TIM2_CH1),触发模式设为“边沿上升沿”,时间基准调至2μs/div。
错误操作(仅改PSC寄存器):
TIM2->PSC = 3599;
波形显示:在切换时刻,PWM高电平突然拉长约1.5个周期,随后恢复正常。这是因为CNT计数器仍在按旧PSC节奏运行,新PSC值未生效,直到下一个自然更新事件(ARR溢出)才同步,导致中间出现“计数失步”。正确操作(改PSC+强制更新):
TIM2->PSC = 3599; TIM2->EGR |= TIM_EGR_UG;波形显示:切换时刻PWM周期瞬时变为0.5ms,无任何毛刺。说明新分频立即生效,CNT计数器被强制重置。
这个实验揭示了PSC的本质:它不是一个静态配置参数,而是与CNT计数器强耦合的动态控制量。任何PSC修改都必须视为一次“计数器重置指令”,而非单纯数值变更。这也是为什么HAL库的HAL_TIMEx_MasterConfigSynchronization()函数里,对PSC的修改总是伴随__HAL_TIM_SET_PRESCALER()和__HAL_TIM_GENERATE_EVENT()的组合调用——底层逻辑就是确保计数器状态一致性。
提示:在实时性要求高的场合(如电机FOC控制),避免在主循环中频繁修改PSC。若需动态调速,优先使用ARR调节占空比,或采用PWM模式下的CCRx寄存器独立控制各通道,而非动摇整个定时器的时基。
2.3 经验法则:PSC选型的三个硬约束条件
PSC值不是越大越好,也不是越小越准,它受三个物理约束:
分辨率约束:PSC决定了最小可调时间单位。例如CK_CNT=72MHz时,PSC=0对应最小计时单位1/72MHz≈13.9ns;PSC=7199对应1μs。若你需要100ns精度的波形,PSC必须≤719(因为(719+1)/72M≈10μs,不行;实际需PSC≤7,因(7+1)/72M≈111ns)。此时ARR必须承担更多计数任务,但ARR最大值为65535,所以最大周期受限于
Tmax = (PSC+1)×65536/CK_CNT。溢出频率约束:PSC过大会导致预分频溢出频率过低,影响中断响应及时性。例如CK_CNT=72MHz,PSC=65535,则预分频溢出频率=72M/(65535+1)≈1.098kHz。若你的应用需要10kHz以上中断(如音频采样),此PSC不可用。
寄存器宽度约束:STM32F1系列PSC寄存器为16位,最大值65535。但实际有效范围受CK_CNT限制。例如CK_CNT=1MHz时,若PSC=65535,则预分频后时钟=1M/65536≈15.26Hz,已低于多数外设需求。此时应降低CK_CNT(如改用HSI而非HSE),而非硬塞满PSC。
我的经验是:先定ARR,再反推PSC。因为ARR直接决定定时周期,而PSC是辅助ARR实现精度的工具。例如要实现10ms定时,CK_CNT=72MHz,则总脉冲数需72M×0.01=720000。若ARR设为999(常用整千值),则PSC=(720000/(999+1))-1=719;若ARR设为65535,则PSC=(720000/65536)-1≈0(实际取0)。前者PSC=719,后者PSC=0,显然前者更易调试(PSC非零便于观察分频效果),且ARR留有余量供动态调整。
3. ARR误区:重装载值不是“计数上限”,而是“匹配阈值”
3.1 从计数器工作模式解构ARR:向上计数 vs 向下计数的根本差异
ARR(Auto-Reload Register)常被误解为“计数器最大值”。这种理解在向上计数模式(Upcounting)下看似成立,但在向下计数(Downcounting)或中心对齐(Center-aligned)模式下完全失效。翻开参考手册第105页的计数器时序图,关键细节浮现:CNT计数器从0开始递增,当CNT值等于ARR时,触发更新事件(UEV),CNT清零,同时产生中断或DMA请求。注意,是“等于ARR时清零”,不是“大于ARR时溢出”。
这意味着:
- 在向上计数模式下,CNT值序列为:0→1→2→...→ARR→0→1→...
- 实际计数值范围是0到ARR(含),共ARR+1个状态。
- 因此,一个完整周期包含ARR+1个CK_CNT脉冲。
这个“+1”再次出现,但它和PSC的“+1”逻辑不同:PSC的+1源于计数器从0开始计数(0到PSC共PSC+1个状态),ARR的+1源于“等于即重载”的触发机制(0到ARR共ARR+1次计数)。二者叠加,公式Tout = (PSC+1) × (ARR+1) / CK_CNT中的两个+1,分别来自两个独立的硬件行为。
更易混淆的是向下计数模式:CNT从ARR开始递减,当CNT=0时触发UEV并重载为ARR。此时一个周期仍是ARR+1个脉冲(ARR→ARR-1→...→0,共ARR+1步)。中心对齐模式则更复杂:CNT先向上计数到ARR,再向下计数到0,一个周期含2×ARR个脉冲(不含重载点)。ARR在此模式下不再代表周期长度,而是决定计数范围的边界值。
3.2 深度实测:ARR=0时的诡异行为与硬件真相
为验证ARR的边界行为,我做了极端测试:将TIM2配置为向上计数,PSC=0(不分频),CK_CNT=72MHz,然后尝试ARR=0。
- 现象:LED闪烁频率不是理论值72MHz(周期13.9ns),而是约36MHz(周期27.8ns)。
- 分析:查阅RM0008第106页“Counter modes”章节,发现明确说明:“When the counter is enabled and the auto-reload value is zero, the counter counts from 0 to 0, i.e., it stays at 0.”(当计数器使能且ARR为0时,计数器从0计到0,即保持在0)。这意味着CNT永远停在0,无法产生UEV,定时器实质上被冻结。
但为什么LED还闪烁?继续追踪:原来我启用了TIM2的更新中断(UIE),而ARR=0时,UEV虽不产生,但计数器使能瞬间会生成一次“初始化更新事件”(Initial Update Event),触发一次中断。此后再无中断,LED只闪一次。那36MHz是怎么来的?其实是调试器读取CNT寄存器时的副作用——每次读CNT,硬件会临时启动计数器一个周期以获取值,导致伪周期性。
真正ARR=0的可用场景是单脉冲模式(One Pulse Mode):设置OPM=1,ARR=0,CNT=0,当触发信号到来时,CNT从0跳到1,立即触发UEV并关闭计数器。此时ARR=0表示“只计1个脉冲”,符合“ARR+1=1”的逻辑。
这个测试揭示ARR的核心属性:它不是计数器的“上限”,而是“匹配目标值”。计数器的行为由ARR与CNT的比较结果驱动,而非简单的数值大小关系。这也是为什么在输入捕获模式中,CCR1寄存器(Capture Compare Register)的值必须小于ARR——因为当CNT=CCR1时触发捕获,若CCR1≥ARR,则CNT永远达不到该值(CNT在等于ARR时已清零)。
3.3 工程实践:ARR动态修改的安全窗口与同步技巧
在需要动态改变定时周期的应用中(如变频电机控制),ARR修改比PSC更危险,因为CNT当前值直接影响新ARR生效时机。例如当前CNT=50,ARR_old=99,ARR_new=49。若直接写TIM2->ARR = 49,则CNT继续从50递增,当CNT=49时不会触发UEV(因50>49,CNT只会越来越大),直到CNT溢出回0,再计到49才触发——这期间多出近50个CK_CNT周期的延迟。
HAL库提供__HAL_TIM_SET_AUTORELOAD()宏,但它只是写寄存器,不保证同步。安全做法是:
- 先禁用定时器:
__HAL_TIM_DISABLE(&htim2); - 修改ARR:
htim2.Instance->ARR = 49; - 清零CNT:
htim2.Instance->CNT = 0; - 重新使能:
__HAL_TIM_ENABLE(&htim2);
但此法有1-2个CK_CNT的禁用窗口,对连续波形不利。更优方案是利用影子寄存器(Shadow Register)机制:STM32所有定时器的ARR都带影子寄存器,当TIMx_CR1寄存器的ARPE位(Auto-Reload Preload Enable)置1时,写入ARR的值先存入影子寄存器,待下一个UEV时才拷贝到活动寄存器。因此,正确流程是:
// 开启影子寄存器(通常初始化时已设) __HAL_TIM_AUTORELOAD_PRELOAD_CONFIG(&htim2, TIM_AUTORELOAD_PRELOAD_ENABLE); // 修改ARR(写入影子寄存器) htim2.Instance->ARR = 49; // 强制生成UEV,使影子值生效 __HAL_TIM_GENERATE_EVENT(&htim2, TIM_EVENTSOURCE_UPDATE);此时CNT不受影响,新ARR在下一个自然周期生效,无毛刺。
注意:影子寄存器需配合UG位使用。若ARPE=0,写ARR直接生效,但CNT可能处于任意值,风险极高。我的项目规范是:所有ARR修改必须ARPE=1,且通过UG触发同步。
4. 时钟源迷雾:APB分频器如何偷偷给定时器“加速”
4.1 破解ST芯片手册的隐藏规则:APB倍频机制的物理根源
这是三大陷阱中最反直觉的一个。几乎所有初学者都默认:“TIM2挂在APB1总线上,APB1时钟是多少,TIM2时钟就是多少。” 但ST芯片手册第102页用加粗字体写着:“The timer clock frequencies are automatically multiplied by 2 when the APBx prescaler is not equal to 1.”(当APBx预分频器不为1时,定时器时钟频率自动乘以2)。
为什么要有这个规则?根源在于总线架构的时序补偿。APB总线是同步外设总线,其时钟频率不能超过CPU主频的一半(Fcpu/2),否则数据采样建立时间不足。当APB预分频为2时(即APB时钟=Fcpu/2),若定时器直接使用APB时钟,则其最高计数频率仅为Fcpu/2,远低于CPU能力。为发挥定时器性能,ST在APB总线与定时器之间插入了一个“时钟倍增器”:当APB预分频≠1时,硬件自动将APB时钟×2作为定时器输入时钟,使其最高频率可达Fcpu,与CPU同频。
验证方法:用STM32CubeMX配置系统时钟,SYSCLK=72MHz,APB1预分频=2(APB1=36MHz),然后查看TIM2的时钟频率——CubeMX会明确标注“TIM2CLK = 72 MHz”。这就是倍频生效的证据。
这个规则有且仅有一个例外:当APB预分频=1时,倍频器被旁路,TIMxCLK = APBxCLK。例如SYSCLK=72MHz,APB1=72MHz,则TIM2CLK=72MHz(无倍频);若APB1=36MHz(预分频=2),则TIM2CLK=72MHz(倍频生效)。
4.2 手动计算全流程:从HSE到TIMxCLK的七步推演
我们以一个典型车载项目为例:使用8MHz外部晶振(HSE),目标TIM2周期10ms,精度±0.1%。手动计算步骤如下:
Step 1:确认HSE起振
HSE=8MHz,无倍频(HSEPRE=0),PLL输入=8MHz。
Step 2:配置PLL倍频
PLLMUL=9(Fvco=72MHz),则PLLCLK=72MHz。
Step 3:设置系统时钟
SW=PLL,SYSCLK=72MHz。
Step 4:配置APB1预分频
PPRE1=2(APB1=SYSCLK/2=36MHz)。注意:此值决定倍频是否启用。
Step 5:确定TIM2时钟源
因PPRE1=2≠1,TIM2CLK=2×APB1=72MHz。
Step 6:计算所需总脉冲数
Tout=10ms,CK_CNT=72MHz → Total Counts = 72M × 0.01 = 720000。
Step 7:拆分PSC与ARR
选择ARR=999(常用值,留余量),则PSC = (720000 / (999+1)) - 1 = 719。
验证:(719+1)×(999+1)/72M = 720000/72M = 0.01s,完美。
若误以为TIM2CLK=APB1=36MHz,则会算出PSC=(720000/(999+1))-1=719(相同),但实际总周期=720000/36M=0.02s,误差100%。这就是为何必须查清TIMxCLK的真实值。
4.3 现场排错:用STM32CubeIDE的时钟树视图定位源头
当定时器不准时,最快定位法是打开CubeIDE的Clock Configuration视图(.ioc文件),展开“Clock Configuration”标签页。这里以图形化方式展示整个时钟树:
- 左侧“HSE/HSI”节点显示输入源频率;
- 中间“PLL”节点显示倍频系数和输出频率;
- 右侧“AHB/APBx”节点显示各总线预分频值;
- 最关键的是每个定时器图标旁的“CLK”标注:TIM2旁明确写着“72 MHz”,TIM3旁写着“36 MHz”(因TIM3挂APB1,但PPRE1=1时无倍频)。
我曾帮一位同事解决“TIM3输出PWM频率总是目标值的2倍”问题。他坚持说“TIM3在APB1上,APB1=36MHz,所以TIM3CLK=36MHz”。我在CubeIDE时钟树里点开TIM3图标,发现其CLK标注为“72 MHz”,再查PPRE1配置——果然是2。他忽略了倍频规则,把TIM3当成普通APB外设处理。
提示:CubeMX生成的
SystemClock_Config()函数里,RCC_ClkInitStruct.AHBCLKDivider和RCC_ClkInitStruct.APB1CLKDivider的赋值直接决定倍频开关。修改这些值后,务必重新生成代码并检查时钟树视图,不要凭记忆推断。
5. 综合实战:用示波器+逻辑分析仪交叉验证定时精度
5.1 构建黄金标准测试环境:硬件连接与触发设置
要真正验证定时器精度,不能只靠串口打印或LED肉眼观察。我搭建的标准测试环境包括:
- 信号源:Keysight DSOX1204G示波器(1GHz带宽,1GSa/s采样率);
- 参考时钟:GPS授时模块输出1PPS(1Hz脉冲,精度±100ns);
- 被测信号:TIM2_CH1输出方波,经10:1探头接入示波器CH1;
- 同步触发:GPS 1PPS信号接入示波器EXT TRIG端口,设置触发模式为“External”,触发边沿为“Rising”。
这样,示波器每一屏都以绝对时间零点(GPS秒脉冲)为基准,可测量任意信号相对于UTC时间的偏移。例如,若TIM2配置为1000Hz方波,理论上每个上升沿应严格落在t=0s, 0.001s, 0.002s...处。用光标测量第1000个上升沿的时间戳,与理论值0.999s对比,即可得累积误差。
5.2 三次典型错误复现与修正过程
错误案例1:PSC计算遗漏+1
- 配置:CK_CNT=72MHz,目标1ms,设PSC=7199,ARR=99;
- 理论周期:(7199+1)×(99+1)/72M = 7200000/72M = 0.1s?等等,7200000/72M=0.1s?不对!7200000/72000000=0.1s,但我们需要1ms=0.001s,所以总脉冲应为72000。此处PSC=7199对应分频7200,ARR=99对应计数100,总脉冲=7200×100=720000,720000/72M=0.01s=10ms。哦,原设定就是10ms,不是1ms。
- 实测周期:10.23ms(误差+2.3%);
- 根因:误以为CK_CNT=APB1=36MHz,实际TIM2CLK=72MHz,总脉冲多算一倍;
- 修正:PSC=3599(分频3600),ARR=99,总脉冲=3600×100=360000,360000/72M=0.005s=5ms?不对,目标是10ms,所以总脉冲需720000,PSC=3599→分频3600,ARR=199→计数200,3600×200=720000,720000/72M=0.01s。
- 修正后实测:10.002ms(误差+0.02%)。
错误案例2:ARR未启用影子寄存器
- 配置:动态修改ARR从1000→500,用于呼吸灯渐变;
- 现象:LED亮度跳变,有明显闪烁;
- 示波器抓取:在ARR修改时刻,PWM周期从1001个CK_CNT突变为501个CK_CNT,但CNT当前值为800,导致下一个UEV在CNT=500时发生(800→801→...→1000→0→1→...→500),延迟了300个CK_CNT;
- 修正:开启ARPE,用UG触发同步;
- 修正后:周期瞬时从1001→501,无延迟,呼吸平滑。
错误案例3:时钟源混淆APB1与APB2
- 配置:TIM1挂APB2,TIM2挂APB1,均设PPRE=2;
- 问题:TIM1输出正常,TIM2慢一倍;
- 根因:TIM1在APB2上,PPRE2=2时TIM1CLK=2×APB2;TIM2在APB1上,PPRE1=2时TIM2CLK=2×APB1;但APB2=72MHz(PPRE2=1),APB1=36MHz(PPRE1=2),故TIM1CLK=72MHz,TIM2CLK=72MHz,本应相同。实际差异来自CubeMX配置:APB1预分频被误设为4(APB1=18MHz),TIM2CLK=36MHz。
- 修正:统一APB1/2预分频为2,TIM2CLK=72MHz。
5.3 终极校准技巧:用ADC+DMA实现亚微秒级在线校准
对于要求±0.01%精度的工业应用,示波器校准仍不够。我采用以下闭环校准法:
- 用TIM2_CH1输出方波,同时用TIM2_CH2输出互补波形(死区控制);
- 将CH1信号接入ADC1_IN0,CH2信号接入ADC1_IN1;
- 配置ADC为连续扫描模式,DMA循环传输2个通道数据;
- 在DMA传输完成中断中,计算CH1与CH2的采样点差值Δn;
- 因ADC采样周期固定(如1MHz),Δn×1μs即为两通道实际相位差;
- 若目标死区为1μs,但实测Δn=102,则说明TIM2时钟偏快2%,动态调整PSC值补偿。
此法将定时器精度校准融入系统运行中,无需外部仪器,且精度达ADC采样周期级别(通常<100ns)。
6. 我的定时器配置检查清单(附带CubeMX避坑指南)
经过上百个项目锤炼,我总结出一份可直接打印贴在工位上的检查清单。每次配置定时器前,逐项核对,100%避免前三类错误:
| 检查项 | 关键问题 | 正确答案 | CubeMX操作位置 |
|---|---|---|---|
| 时钟源确认 | TIMx挂载在哪个总线?APBx预分频是多少?TIMxCLK是否启用倍频? | 查手册确认TIMx所属总线,计算TIMxCLK=APBxCLK×(1 if PPREx=1 else 2) | Clock Configuration → AHB/APBx Divider |
| PSC计算 | PSC寄存器值 = ? 是否已减1? | PSC = (Total_Count / (ARR+1)) - 1 | Parameter Settings → Prescaler |
| ARR设置 | ARR是否启用影子寄存器?动态修改是否用UG同步? | ARPE=1,修改后调用HAL_TIM_GenerateEvent() | Parameter Settings → Counter Settings → Auto Reload Preload |
| 计数模式 | 当前是向上/向下/中心对齐?ARR含义是否变化? | 向上计数:周期=ARR+1;中心对齐:周期=2×ARR | Parameter Settings → Counter Settings → Counter Mode |
| 中断/DMA使能 | 更新中断是否开启?UEV是否映射到正确中断线? | __HAL_TIM_ENABLE_IT(&htimx, TIM_IT_UPDATE) | NVIC Settings → TIMx Global Interrupt |
CubeMX特有的三个坑:
坑1:自动生成的HAL_TIM_Base_Start()不启用更新中断
CubeMX勾选“Update interrupt”后,生成的MX_TIMx_Init()里只调用HAL_TIM_Base_Start(),但此函数不使能中断。必须手动添加HAL_TIM_Base_Start_IT(),或在main()中调用。坑2:时钟树修改后未重新生成代码
调整APB分频后,CubeMX右下角提示“Configuration is not up to date”,但很多人直接编译。务必点击“Generate Code”按钮,否则SystemClock_Config()函数不会更新。坑3:高级定时器(TIM1/TIM8)的BDTR寄存器被忽略
这些定时器有断路功能,BDTR寄存器的MOE位(Main Output Enable)必须置1,否则OCx输出始终为0。CubeMX在“Advanced Settings”中默认不勾选“Main Output Enable”,需手动开启。
最后分享一个血泪教训:某次量产固件中,TIM2用于CAN总线位定时,因PSC计算错误导致波特率偏差0.5%,在高温环境下误码率飙升。返工时发现,错误根源竟是CubeMX版本升级后,时钟树视图默认显示“APB1=36MHz”,但实际代码里PPRE1被设为4(APB1=18MHz),而TIM2CLK=36MHz(倍频生效)。永远相信寄存器读值,而不是IDE界面显示——用调试器读RCC->CFGR的PPRE1字段,读RCC->CFGR的PPRE1字段,读RCC->CFGR的PPRE1字段(重要的事说三遍),这才是真相。
我在实际项目中发现,最可靠的验证方式不是反复烧录,而是在初始化完成后,立即用调试器读取TIMx->PSC、TIMx->ARR、TIMx->CNT,并用公式反算当前周期,与预期值比对。这个动作耗时不到10秒,却能拦截90%的配置错误。毕竟,硬件从不撒谎,它只忠实地执行你写进寄存器的每一个比特。