尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

STM32定时器时间基准原理与精度陷阱解析

STM32定时器时间基准原理与精度陷阱解析 1. 从“滴答”声开始为什么你写的 delay(1000) 实际上并不等于 1 秒你写过多少次HAL_Delay(1000)又或者用SysTick_Config(SystemCoreClock / 1000)配置过多少次滴答定时器但有没有哪一次你盯着示波器上的 LED 闪烁波形发现它明明设了 1 秒亮、1 秒灭结果实测却是 1.023 秒再或者在低功耗 STOP 模式下唤醒后发现 RTC 时间跳了 50ms而你用的 LPTIM 定时器却没触发中断——不是代码错了是时间本身“松动”了。这不是玄学是 STM32 时间基准的底层真相定时器不数“秒”它只数“时钟边沿”而“秒”这个单位是靠你手动告诉芯片“每秒有多少个边沿”才建立起来的。所有定时器——无论是 SysTick、TIM2~TIM17、LPTIM 还是 RTC 的预分频器——本质上都是同一个东西一个可配置的计数器它只认一件事上游时钟源的上升沿或下降沿到来一次它就加一或减一。它根本不认识“毫秒”“微秒”“分钟”这些全是软件强加给它的语义标签。这就引出了最根本的问题那个“上游时钟源”到底是谁它稳定吗它被谁控制它在不同电源模式下会变吗很多人以为只要SystemCoreClock是 72MHz所有定时器就天然拥有 72MHz 的精度这是最大的认知陷阱。SystemCoreClock只是一个全局变量它记录的是你认为的系统主频但它本身不产生任何时钟信号——它只是对硬件时钟树配置结果的一个快照。真正驱动 TIM2 计数器的可能是 APB1 总线时钟驱动高级定时器 TIM1 的可能是 APB2 总线时钟而驱动 LPTIM 的则可能是独立的 LSE 或 LSI。它们彼此之间没有必然的倍数关系甚至可能来自完全不同的物理振荡器。我第一次踩坑是在做一个超声波测距项目时。用 TIM2 捕获回波时间公式是distance (cnt * 1e9 / tim2_clk_freq) / 2 / 1e6单位 mm其中tim2_clk_freq我直接用了SystemCoreClock / 2因为 APB1 分频为 2。结果在不同批次的板子上测距误差从 ±2cm 跳到 ±8cm。最后用逻辑分析仪抓取 TIM2 的时钟输入引脚才发现有两块板子的晶振焊反了导致 HSE 启动失败MCU 自动 fallback 到内部 HSI8MHz±1%而SystemCoreClock变量仍被错误地初始化为 72MHz——整个时间基准塌方了但你的代码毫无察觉。所以“定时器到底在数什么”的答案必须拆成两层第一层它数的是物理时钟信号的脉冲数量第二层它“代表多少时间”取决于你如何把脉冲数量映射到现实世界的时间单位上。这个映射过程就是“时间基准”的建立过程。而这个过程远比RCC-CFGR寄存器里几个位的设置要复杂得多。它牵涉到晶振选型、时钟树拓扑、电源管理策略、甚至 PCB 布线质量。接下来我们就一层层剥开这层“时间基准”的洋葱皮从最底层的物理振荡器开始一直看到你写在main()里那行HAL_TIM_Base_Start_IT(htim2)的背后究竟发生了什么。2. 物理源头STM32 的四类时钟源谁才是真正的“时间之父”STM32 的时间基准最终都必须追溯到四个物理振荡器。它们不是并列关系而是存在明确的层级与优先级。理解这个层级是避免后续所有时间漂移问题的前提。我把它们按“权威性”和“稳定性”排序而不是按数据手册里的字母顺序2.1 HSEHigh-Speed External高精度、高功耗的“法定标准”HSE 是外部高速晶振典型值为 4–26MHzF1 系列或 4–48MHzF4/F7 系列。它是整个时钟树的“黄金标准”。当你在 CubeMX 里勾选“Use external clock source for system clock”你就是在告诉 MCU“请以这个外部晶体的频率为绝对基准去校准所有其他时钟。”它的核心优势在于精度高±10–50ppm、温度稳定性好-20°C ~ 70°C 内漂移小、长期稳定性优老化率低。一块 8MHz 的 AT-cut 晶体在室温下实测频率偏差通常小于 ±20ppm换算成 1 秒就是 ±20 微秒。这对需要精确测频、PWM 波形生成、USB 通信等场景至关重要。但 HSE 的代价也很明显启动慢1–10ms、功耗高几百微安、需要额外的两个 12–22pF 负载电容、PCB 布线要求严格需短而直远离数字噪声源。我见过太多项目因为晶振旁边放了个大功率 MOSFET 驱动电路导致 HSE 频率跳变 ±5%最终 USB 设备枚举失败。提示HSE 的启动状态必须通过RCC_CR寄存器的HSERDY位确认。很多初学者直接在RCC_CR | RCC_CR_HSEON后就配置 PLL这是危险的。正确的做法是RCC-CR | RCC_CR_HSEON; while (!(RCC-CR RCC_CR_HSERDY)) { /* 等待 */ }2.2 HSIHigh-Speed Internal快速、廉价、但“不太靠谱”的“应急方案”HSI 是片内 RC 振荡器标称频率为 8MHzF1或 16MHzF4/F7。它的最大优点是启动极快10μs、无需外围器件、功耗相对较低约 150μA。这也是为什么 MCU 复位后默认使用 HSI 的原因——它能保证系统在 10μs 内跑起来给你争取时间去初始化 HSE 或 PLL。但它的致命弱点是精度差±1% 典型值即 ±100,000ppm、温度敏感-40°C ~ 85°C 内频率变化可达 ±5%、电压敏感VDD 变化 10% 可导致频率偏移 ±2%。这意味着如果你用 HSI 直接作为 TIM2 的时钟源且未做校准那么在 25°C 下设的 1ms 定时在 0°C 时可能变成 1.05ms在 70°C 时可能变成 0.95ms。对于 LED 闪烁这无关紧要但对于电机 FOC 控制中的 PWM 占空比计算0.5% 的误差就可能导致转矩脉动。有趣的是STM32 提供了 HSI 校准机制RCC_ICSCR寄存器可以通过外部精确时钟如 HSE来动态调整 HSI 的 trimming value。但这需要你主动编写校准代码并非开箱即用。2.3 LSELow-Speed ExternalRTC 的“心跳”低功耗下的时间锚点LSE 是外部低速晶振典型值为 32.768kHz。它专为 RTC 和 LPTIM 设计特点是超低功耗1μA、高精度±20ppm、专用于低功耗场景。它的物理结构音叉晶体决定了它在电池供电下能稳定工作数年。LSE 的关键价值在于当 MCU 进入 STOP 或 STANDBY 模式时HSE/HSI 全部关闭只有 LSE如果启用和 LSI 保持运行。因此RTC 的日历功能、LPTIM 的超长定时如 1 小时唤醒都依赖于 LSE 的稳定输出。如果你的项目需要“睡眠 8 小时后自动开机”LSE 就是你唯一可靠的时间锚点。注意LSE 的启动时间比 HSE 更长通常 2s且其使能寄存器RCC_BDCR的LSEON位写入后必须等待LSERDY置位才能使用。很多低功耗项目失败就是因为没等LSERDY就急着配置 RTC。2.4 LSILow-Speed Internal最后的“保底时钟”精度换速度LSI 是片内低速 RC 振荡器标称频率为 32kHz实际范围 20–60kHz。它是 LSE 的“穷兄弟”启动最快100μs、功耗最低1μA、无需外围器件但精度极差±100%即 16–64kHz。它的主要用途是在 LSE 不可用如未焊接晶振时作为 RTC 和独立看门狗IWDG的备用时钟源。LSI 的精度如此之差以至于你不能用它来做任何需要时间精度的测量。但它足够驱动 IWDG因为看门狗的核心诉求是“防止死锁”而不是“精确计时”。我曾在一个紧急量产项目中因 LSE 晶振缺货被迫用 LSI 驱动 RTC。结果是设备在常温下走时每天快 15 分钟但在高温环境下反而慢 20 分钟——这正是 RC 振荡器的典型表现。这四类时钟源的关系可以用一个比喻来理解HSE 是国家授时中心的原子钟HSI 是你手机自带的石英表LSE 是你床头的电子闹钟电池供电LSI 则是你随手画在纸上的沙漏——它们都能“计时”但你敢用哪个来校准航天器3. 时钟树从物理振荡器到定时器输入的“高速公路网”有了物理源头下一步就是理解 STM32 如何把这些源头的“原始脉冲”分配、分频、倍频最终送到各个定时器的输入端。这个分配网络就是“时钟树”。它不是一根简单的线而是一张复杂的、可编程的“高速公路网”每条路都有自己的限速分频系数、收费站门控开关和方向指示牌时钟源选择。3.1 主干道SYSCLK —— 系统时钟一切的起点SYSCLK是整个 MCU 的“心脏节律”它决定了 CPU、Flash、大部分外设如 GPIO、USART的工作频率。它的来源有三个HSE 直接作为 SYSCLK最简单但频率受限于 HSEHSI 直接作为 SYSCLK启动快但精度差PLL 输出作为 SYSCLK最常用通过倍频获得更高主频PLL锁相环是时钟树的“引擎”。它接收一个输入时钟PLLSRC可以是 HSE 或 HSI然后通过一个可编程的倍频系数PLLNF4/F7 系列或PLLMULF1 系列将其放大。例如F103 使用 HSE8MHz设置PLLMUL9则 PLL 输出为8MHz * 9 72MHz这就是我们熟悉的 72MHz 主频。但这里有个关键细节PLL 的输入时钟必须经过一个预分频器PLLPREF4/F7或PLLMUL的隐含分频F1。F1 系列要求 PLL 输入频率在 1–2MHz 之间所以 8MHz 的 HSE 必须先被PLLMUL的隐含分频通常是 /2降到 4MHz再倍频到 72MHz。这个预分频步骤直接影响了最终SYSCLK的精度——如果 HSE 本身有 ±20ppm 误差那么经过 PLL 倍频后误差也被放大了 9 倍达到 ±180ppm。3.2 支线公路APB1 和 APB2 总线时钟 —— 定时器的“直接雇主”SYSCLK并不直接驱动所有定时器。它先被分频形成两条主要支线APB1 总线时钟PCLK1负责低速外设包括 TIM2–TIM7、TIM12–TIM14、RTC、I2C、USART2/3/4/5、SPI2/3 等。APB2 总线时钟PCLK2负责高速外设包括 TIM1、TIM8、USART1、SPI1、ADC、GPIO 等。分频系数由RCC_CFGR寄存器的PPRE1APB1和PPRE2APB2位域控制。常见配置是PPRE1 0b100即 PCLK1 SYSCLK / 2PPRE2 0b100即 PCLK2 SYSCLK / 2。但注意当PPRE1或PPRE2设置为“不分频”0b000时对应的总线时钟等于SYSCLK但当设置为“2 分频”0b100时它并不是简单地除以 2而是有一个隐藏规则如果分频系数为 1则PCLKx SYSCLK如果分频系数为 2则PCLKx SYSCLK / 2但如果分频系数为 4、8 或 16则PCLKx SYSCLK / (2 * 分频系数)。这个“乘以 2”的规则是为了保证 APB 总线上的定时器预分频器PSC能获得足够的计数范围。这才是 TIM2 的真实时钟来源TIM2_CLK PCLK1 * (1 or 2)。这里的(1 or 2)是一个关键开关在RCC_CFGR中有一个TIMPRE位仅 F4/F7当它为 0 时TIMx_CLK PCLKx当它为 1 时TIMx_CLK PCLKx * 2。F1 系列没有这个位其TIMx_CLK恒等于PCLKx。这意味着同样配置SYSCLK72MHz,PCLK136MHzF1 的 TIM2 时钟是 36MHz而 F4 的 TIM2 时钟可以是 36MHz 或 72MHz——这直接影响了你设置ARR自动重装载值时的计算逻辑。3.3 专用通道LPTIM 和 RTC 的“独立供电线路”高级定时器TIM1/TIM8和低功耗定时器LPTIM走的是另一条路。它们的时钟源是独立的不经过 APB 总线高级定时器时钟源可选PCLK2或HCLK系统时钟通过TIMx_CR1寄存器的CKD位选择。LPTIM时钟源可选PCLK1、LSI、LSE或HSE_DIV32HSE 除以 32。这个选择直接决定了它在 STOP 模式下的行为——只有LSI和LSE能在 STOP 模式下保持运行。RTC 的时钟源更是特殊它只能从LSE、LSI或HSE_DIV128HSE 除以 128中选择并且这个选择一旦设定就不能在 RTC 正在运行时更改。这是因为 RTC 的寄存器如RTC_TR、RTC_DR是异步更新的切换时钟源会导致日历数据错乱。这张时钟树图不是一张静态的说明书而是一张动态的“交通管制图”。你每一次调用__HAL_RCC_TIM2_CLK_ENABLE()都是在打开 TIM2 所在支路的“闸门”你每一次修改RCC_CFGR的PPRE1都是在调整整条 APB1 支线的“限速标志”。理解这张图你就明白了为什么HAL_TIM_Base_Start_IT(htim2)之前必须先调用__HAL_RCC_TIM2_CLK_ENABLE()——否则TIM2 的“高速公路”是封闭的再多的脉冲也到不了它的计数器。4. 定时器内部从时钟输入到中断触发的“精密流水线”现在脉冲已经通过时钟树抵达了 TIM2 的时钟输入引脚。但定时器本身还有一套精密的内部流水线将这些原始脉冲转化为你代码里期待的“1ms 中断”。这条流水线由四个核心环节组成时钟预分频PSC、计数器CNT、自动重装载ARR、事件生成UEV。它们共同构成了一个闭环的“时间转换器”。4.1 PSCPrescaler第一道“减速带”决定最小时间分辨率PSC寄存器的作用是将输入时钟TIMx_CLK进行预分频。它的值是PSC 1即如果PSC 999则实际分频系数为 1000。这意味着每 1000 个TIMx_CLK脉冲计数器CNT才加 1。PSC的核心价值在于它决定了定时器的最小时间分辨率Time Base Resolution。假设TIM2_CLK 36MHzF103 常见配置若PSC 0则CNT每1/36MHz ≈ 27.78ns加 1若PSC 35999则CNT每36000 / 36MHz 1ms加 1。后者就是我们常说的“1ms 定时器”。但PSC不是越大越好。它的最大值是 6553516 位寄存器所以PSC的选择必须与ARR配合以满足你的定时周期需求。例如要实现 10ms 定时PSC和ARR的组合可以是PSC 3599,ARR 99→(3600 * 100) / 36MHz 10msPSC 0,ARR 359999→360000 / 36MHz 10ms前者更优因为ARR值小溢出中断响应更快且CNT计数范围小不易受干扰。后者虽然PSC0但ARR接近 36 万一旦CNT因某种原因如中断延迟错过一次溢出就需要等很久才能再次触发造成严重的时间失步。实操心得我习惯将PSC设为TIMx_CLK / 1000 - 1即目标频率为 1kHz然后用ARR来精确调整最终周期。这样ARR始终在 0–65535 范围内且CNT的计数速率固定为 1kHz逻辑清晰调试方便。4.2 CNTCounter与 ARRAuto-Reload Register核心的“计数-重载”循环CNT是一个 16 位部分高级定时器为 32 位的向上计数器。它从 0 开始每收到一个经PSC分频后的脉冲就加 1。当CNT的值等于ARR时发生“溢出”Update EventCNT被清零或根据ARPE位决定是否立即重载同时UIFUpdate Interrupt Flag置位触发中断。ARR的值直接决定了定时周期TT (PSC 1) * (ARR 1) / TIMx_CLK注意公式中的1是关键PSC和ARR都是“减一寄存器”即它们存储的值是分频系数和重装载值减一。这是 STM32 定时器硬件设计的约定也是新手最容易算错的地方。举个例子TIM2_CLK 36MHz,PSC 3599,ARR 99。则T (3599 1) * (99 1) / 36,000,000 3600 * 100 / 36,000,000 0.01s 10ms。如果忘了1就会算成3599 * 99 / 36e6 ≈ 9.997ms误差虽小但在高精度场合累积起来就很可观。ARR还有一个重要特性它支持影子寄存器Shadow Register。当ARPEAuto-Reload Preload Enable位被置位时你写入ARR的新值并不会立即生效而是等到下一个更新事件溢出时才从影子寄存器拷贝到活动寄存器。这保证了ARR的更新是“原子”的不会在计数中途改变从而避免了因ARR更新导致的计数器异常如计数不到预期值就溢出。4.3 UEVUpdate Event与中断从硬件事件到软件响应的“最后一公里”当CNT溢出时硬件会生成一个 Update EventUEV。这个事件有三重作用清零CNT或重载ARR的影子值置位UIF标志位触发更新中断如果UIE位使能。UIF是一个“挂起”标志它不会自动清除必须由软件在中断服务程序ISR中手动清除__HAL_TIM_CLEAR_FLAG(htim2, TIM_FLAG_UPDATE)或通过读取SR寄存器后写 0 清除。这是 HAL 库中一个经典陷阱如果你在 ISR 中只处理业务逻辑忘了清除UIF那么中断会立刻再次进入形成“中断风暴”CPU 100% 占用系统卡死。更隐蔽的问题是中断响应延迟。从UIF置位到你的 ISR 第一行代码执行中间隔着CPU 完成当前指令、压栈、查向量表、跳转、执行 ISR 入口代码。这个延迟Latency在 Cortex-M3/M4 上通常是 12–24 个系统时钟周期。如果TIMx_CLK是 36MHz一个周期是 27.78ns那么 24 个周期就是约 667ns。对于 1ms 定时这个延迟可以忽略但对于 10μs 级别的 PWM 边沿控制就必须考虑。避坑经验我在做 FOC 电机控制时用 TIM1 的互补通道生成 PWM同时用更新中断做电流采样。最初采样点设在CNT0即更新事件后立即采样结果发现每次采样都晚了约 1.2μs。后来改用CNT的捕获/比较功能在CNT达到某个特定值如ARR/2时触发采样将采样点精确锁定在 PWM 周期的中点彻底消除了这个延迟误差。5. 实战验证用逻辑分析仪和示波器亲手“看见”时间基准理论再完美不如亲眼所见。我强烈建议每一个涉及时间精度的 STM32 项目都必须进行实机验证。下面是我常用的三步验证法工具只需一台入门级逻辑分析仪如 Saleae Logic 8或示波器。5.1 第一步验证时钟源 —— 抓住 HSE/LSE 的“真面目”目标确认 MCU 实际使用的时钟源频率而非SystemCoreClock变量的值。方法利用 STM32 的 MCOMicrocontroller Clock Output功能将选定的时钟源如 HSE、SYSCLK、PLLCLK输出到指定 GPIO 引脚通常是 PA8然后用逻辑分析仪测量其频率。步骤在 CubeMX 中配置SYSCLK为 HSE启用 MCO 功能选择MCO1输出HSE到PA8。编译下载用逻辑分析仪探头接触PA8。观察波形测量周期T计算f 1/T。我曾用此法发现一个“幽灵问题”客户送来的 100 块板子有 3 块在出厂测试时HAL_Delay(1000)总是慢 5%。用 MCO 抓取HSE发现这 3 块板子的 HSE 频率是 7.6MHz而非标称的 8MHz。原因是晶振厂商批次混料把 7.6MHz 的晶体当 8MHz 卖了。SystemCoreClock变量仍是 72MHz但实际SYSCLK是7.6MHz * 9 68.4MHz导致所有基于HAL_Delay的时间都变慢。5.2 第二步验证定时器输出 —— 看清 TIMx_CLK 的“真实脉冲”目标确认 TIM2 的时钟输入频率验证PSC和ARR的计算是否准确。方法配置 TIM2 为 PWM 输出模式如 CH1占空比 50%然后测量 PWM 波形的周期。步骤配置 TIM2 通道 1 为 PWM 模式PSC 3599,ARR 99目标 10ms 周期。将TIM2_CH1通常是 PA0连接到逻辑分析仪。测量 PWM 波形的高电平时间T_high和周期T_period。理论上T_period应为 10ms。如果实测是 10.023ms那么误差23μs就是你的TIM2_CLK频率误差。用公式反推TIM2_CLK_actual (PSC 1) * (ARR 1) / T_period 3600 * 100 / 0.010023 ≈ 35,915,000Hz即TIM2_CLK实际为 35.915MHz比理论值 36MHz 低了 0.236%。这个误差很可能源于 HSE 的实际频率偏差。5.3 第三步验证中断响应 —— 捕捉 ISR 的“真实延迟”目标测量从定时器溢出到 ISR 第一行代码执行的时间评估中断延迟对实时性的影响。方法在 ISR 的第一行翻转一个 GPIO如 PB0用示波器同时观测 TIM2 的更新事件可通过TIM2_ETR引脚或内部信号和 PB0 的翻转。步骤在HAL_TIM_PeriodElapsedCallback()的第一行添加HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_0)。用双通道示波器CH1 接 TIM2 的更新事件需查阅参考手册找到对应的内部信号输出引脚或用TIM2_ETR作为同步信号CH2 接 PB0。测量 CH1 上升沿更新事件到 CH2 上升沿GPIO 翻转之间的时间差。这个时间差就是你的中断响应延迟。在我的 F103 项目中典型值是 1.8μs。如果这个值超过你应用允许的最大延迟如 FOC 控制要求 1μs你就必须优化关闭不必要的中断、降低中断优先级、或将关键代码移到__disable_irq()保护区内。这三步验证不是为了证明你的代码“正确”而是为了建立一个可信赖的时间基准模型。它告诉你你的delay(1000)在这块板子上到底等于多少微秒。这才是工程实践的起点——所有精美的算法都必须建立在这个真实的、可测量的物理基础上。6. 终极避坑指南那些让时间“悄悄溜走”的 7 个隐形陷阱即使你熟读参考手册严格按照流程配置STM32 的时间基准依然可能在你眼皮底下“悄悄溜走”。以下是我在十年嵌入式开发中亲手踩过、也帮客户解决过的 7 个最隐蔽、最致命的陷阱每一个都足以让一个看似完美的定时功能在量产时集体失效。6.1 陷阱一SystemCoreClock的“虚假繁荣”SystemCoreClock是一个全局变量它在SystemCoreClockUpdate()函数中被更新。但这个函数不会被自动调用。很多初学者以为只要RCC初始化完成SystemCoreClock就是准确的。错它只是一个初始值通常是 8MHz除非你显式调用SystemCoreClockUpdate()否则它永远不会反映SYSCLK的真实频率。后果所有基于HAL_Delay()、HAL_GetTick()的函数其内部计算都依赖SystemCoreClock。如果这个值是错的HAL_Delay(1000)就永远不是 1 秒。解决方案在HAL_Init()之后MX_GPIO_Init()之前必须手动调用一次SystemCoreClockUpdate()。CubeMX 生成的代码默认包含这行但如果你手写启动代码极易遗漏。6.2 陷阱二STOP 模式下的“时钟静默”当 MCU 进入 STOP 模式时HSE、HSI、PLL全部关闭。此时PCLK1、PCLK2为 0所有基于 APB 总线的定时器TIM2–TIM7、TIM12–TIM14全部停止计数。但很多开发者误以为HAL_TIM_Base_Start_IT(htim2)在 STOP 前启动了醒来后它还能继续计时——这是不可能的CNT的值在 STOP 期间是冻结的。后果你期望的“睡眠 10 秒后唤醒”实际是“睡眠 10 秒 唤醒后 TIM2 从冻结值开始计时”导致总延时远超预期。解决方案在 STOP 模式下必须使用 LPTIM 或 RTC。LPTIM 可以配置为LSE或LSI时钟源它们在 STOP 模式下保持运行。配置 LPTIM 的ARR使其在ARR溢出时产生中断唤醒 MCU。6.3 陷阱三HAL_GetTick()的“1ms 假象”HAL_GetTick()返回自系统启动以来的毫秒数其底层依赖SysTick定时器。SysTick的时钟源是HCLK系统时钟其LOAD值被设为HCLK / 1000 - 1以实现 1ms 中断。陷阱在于HAL_GetTick()是一个 32 位无符号整数最大值为0xFFFFFFFF约 49.7 天。如果系统连续运行超过 49.7 天HAL_GetTick()会回绕到 0。如果你的代码中有if (now - start_time 5000)这样的判断当now回绕而start_time未回绕时这个条件会永远为假导致功能失效。解决方案永远不要直接相减。使用 HAL 提供的HAL_GetTick() - start_time它内部已处理了回绕问题或者自己实现一个安全的差值计算uint32_t elapsed_ms (now start_time) ? (now - start_time) : (0xFFFFFFFF - start_time now 1);6.4 陷阱四HAL_Delay()的“阻塞式幻觉”HAL_Delay()是一个阻塞函数它通过轮询HAL_GetTick()实现。在HAL_Delay(1000)执行期间MCU 无法响应任何其他中断除非你修改了HAL_TICK_FREQ并重写了HAL_IncTick()。后果如果你在HAL_Delay()期间恰好有高优先级的外部中断如按键、CAN 报文到来它会被延迟处理可能导致按键抖动未消抖、CAN 报文丢失。解决方案永远不要在中断服务程序ISR或实时性要求高的任务中调用HAL_Delay()。应使用非阻塞的定时器如 HAL_TIM_Base_Start_IT
返回列表