STM32定时器输入捕获实现PWM频率与占空比测量

📅 2026/7/30 6:15:38 👁️ 阅读次数 📝 编程学习
STM32定时器输入捕获实现PWM频率与占空比测量

1. 项目缘起:为什么需要同时测量PWM的频率和占空比?

在嵌入式开发,尤其是电机控制、舵机驱动、电源管理或者简单的信号分析场景里,PWM(脉冲宽度调制)信号可以说是无处不在。我们经常需要与它打交道,不仅仅是生成它,更重要的是“读懂”它。比如,你设计了一个风扇调速器,通过旋钮产生一个变化的PWM信号来控制风扇转速,你的主控芯片STM32就需要准确知道这个PWM信号的频率和占空比,才能执行相应的逻辑。又或者,你在调试一个舵机,发现它转动不准确,第一件事可能就是拿示波器或者逻辑分析仪去抓一下控制信号,看看频率对不对,占空比是否在预期范围内。

示波器固然精准直观,但它不可能焊在每个产品里。最终,我们的产品必须能自己“感知”外部的PWM指令。这就是输入捕获功能的用武之地。STM32的定时器外设功能强大,其输入捕获模式天生就是为测量脉冲时间参数而设计的。然而,对于刚接触的朋友,或者是从标准库转向HAL库的开发者,如何稳定、准确地实现这个功能,里面有不少门道。网上资料虽多,但往往只讲标准库或只讲HAL库,代码片段也不完整,调试起来遇到问题更是无从下手。今天,我就结合自己多次在项目中实现该功能的经验,把标准库和HAL库两种方式下的实现逻辑、配置细节、避坑要点,以及如何同时获取频率和占空比,一次给你讲透。无论你用的是STM32F1、F4还是其他系列,这套思路都是通用的。

2. 核心原理拆解:定时器如何“抓住”脉冲的边沿

在深入代码之前,我们必须先搞清楚STM32的定时器是怎么“看见”并测量一个PWM波的。这不是魔法,而是基于其内部精密的硬件电路。理解了这个,后面所有的配置和代码逻辑就都顺理成章了。

一个理想的PWM波,我们可以用三个关键参数描述:频率(或周期T)高电平时间(脉宽W)占空比(D = W / T)。定时器的输入捕获,目标就是测出周期T和脉宽W。

2.1 定时器的基础计数机制

STM32的通用定时器(如TIM2, TIM3, TIM4, TIM5等)本质上是一个向上(或向下)计数的计数器。它有一个时钟源,比如内部时钟(APB总线时钟经过倍频后),这个时钟每来一个脉冲,计数器(CNT)的值就加1(或减1)。计数器通常是一个16位或32位的寄存器,计到最大值后会溢出归零,同时产生一个更新事件。

假设定时器的时钟频率是F_TIM,那么计数一次的时间就是1 / F_TIM。如果我们将计数器配置为连续向上计数模式,它就会周而复始地从0计到自动重载值(ARR),然后清零再开始。这个ARR值决定了定时器计数一个周期的长度。

2.2 输入捕获通道的“抓拍”功能

定时器有若干个输入捕获通道(比如TIM2有CH1-CH4)。每个通道可以映射到一个特定的GPIO引脚上。这个通道的核心功能是“抓拍”:当配置的GPIO引脚上出现指定的电平时钟边沿(比如上升沿)时,通道的硬件会立即将当前计数器(CNT)的值“捕获”并锁存到一个专用的捕获/比较寄存器(CCRx)中,同时可以产生一个中断或者DMA请求,通知CPU:“嘿,我抓到一个边沿,此刻的时间戳是XXX!”

2.3 测量周期与脉宽的“两次抓拍法”

如何利用一次“抓拍”得到两个时间值呢?这就需要我们利用两个边沿,并进行两次捕获。

测量周期(T):我们只需要捕获两个相邻的同极性边沿之间的时间差。最常用的方法是捕获两个连续的上升沿。第一个上升沿到来时,捕获到时间戳t1;下一个上升沿到来时,捕获到时间戳t2。那么,周期T = (t2 - t1) * (1 / F_TIM)。如果计数器在t1t2之间发生了溢出,我们还需要记录溢出的次数N,那么T = (t2 - t1 + N * (ARR+1)) * (1 / F_TIM)。在标准库中,我们通常会在中断里手动处理溢出;而在HAL库中,利用其输入捕获中断回调函数,可以更清晰地获取这些值。

测量高电平脉宽(W):我们需要捕获一个周期内上升沿和紧随其后的下降沿之间的时间差。配置输入捕获通道在上升沿和下降沿都触发捕获(即双边沿捕获)。第一个上升沿到来,捕获t_rise;紧接着的下降沿到来,捕获t_fall。那么,高电平时间W = (t_fall - t_rise) * (1 / F_TIM)。同样需要考虑溢出。

占空比(D):在得到TW后,占空比D = W / T

所以,实现的关键就在于:合理配置定时器的一个或两个通道,使其能够稳定地捕获我们关心的边沿,并在中断服务程序中正确计算时间差,同时处理好计数器的溢出问题。

3. 标准库方案实现:精细控制与直接寄存器操作

标准库(Standard Peripheral Library)的特点是对硬件寄存器进行了封装,但封装层较薄,开发者对流程有很强的控制力,代码效率高,也更容易理解底层机制。我们以STM32F103C8T6的TIM3通道1(PA6引脚)为例,实现双边沿捕获。

3.1 硬件与引脚配置

首先,需要初始化对应的GPIO和定时器时钟,并将GPIO配置为复用功能上拉输入模式,因为输入捕获功能是定时器外设控制的,GPIO需要工作在复用模式。

// 开启时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); // 配置PA6为复用上拉输入 GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; // 上拉输入,确保引脚无输入时为高电平 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); // 将PA6引脚复用连接到TIM3通道1 GPIO_PinRemapConfig(GPIO_PartialRemap_TIM3, ENABLE); // 注意:TIM3_CH1默认是PA6,有些重映射情况需要查数据手册

3.2 定时器基础参数配置

接下来配置定时器本身。我们需要设置预分频器(PSC)和自动重载值(ARR),这决定了定时器的计数频率和周期。为了获得较高的测量精度和较宽的频率测量范围,需要权衡。

假设系统时钟是72MHz,APB1定时器时钟也是72MHz。如果我们想测量一个1kHz(周期1ms)的PWM,并且希望计数器在1ms内不要计满溢出太多(以减少溢出处理复杂度),我们可以这样设计:

  • 目标:1ms内,计数器计数值在1000左右,这样每个计数代表1us。
  • 计算:定时器时钟F_TIM = 72MHz。要实现1us计数,需要分频:PSC = 72 - 1,这样定时器实际计数频率F_CNT = 72MHz / 72 = 1MHz,即1us计数一次。
  • ARR值:设置为最大值0xFFFF(65535),因为我们是连续计数模式,ARR主要决定溢出周期。在1MHz下,溢出周期是65.535ms,足够捕获1ms周期的信号。
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_TimeBaseStructure.TIM_Period = 0xFFFF; // 自动重载值,决定溢出周期 TIM_TimeBaseStructure.TIM_Prescaler = 72 - 1; // 预分频,使计数器时钟为1MHz TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; // 时钟分频,与输入滤波相关,通常设为DIV1 TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; // 向上计数模式 TIM_TimeBaseStructure.TIM_RepetitionCounter = 0; // 高级定时器才有,通用定时器设为0 TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);

3.3 输入捕获通道配置(双边沿触发关键)

这是核心步骤。我们需要将通道1配置为输入捕获模式,并且触发方式设置为“上升沿和下降沿”。在标准库中,这通过配置TIM_ICInitTypeDef结构体实现,但标准库函数TIM_ICInit通常只支持配置一种边沿。为了实现双边沿捕获,我们需要一点技巧:使用两个通道,或者利用定时器的“从模式”触发切换极性。更常见且简单的做法是使用一个通道,但在中断中动态切换捕获边沿

这里我们采用动态切换边沿的方法。初始配置为上升沿捕获。

TIM_ICInitTypeDef TIM_ICInitStructure; TIM_ICInitStructure.TIM_Channel = TIM_Channel_1; TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising; // 初始为上升沿触发 TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI; // 映射到TI1输入 TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1; // 输入捕获预分频,每个事件都捕获 TIM_ICInitStructure.TIM_ICFilter = 0x0; // 输入滤波器,0表示不滤波。对于信号有毛刺可适当增加 TIM_ICInit(TIM3, &TIM_ICInitStructure);

3.4 中断配置与全局变量设计

我们需要开启定时器的捕获/比较1中断(CC1IE)和更新中断(UIE)。CC1IE用于在捕获事件发生时处理时间戳,UIE用于处理计数器溢出,以计算长时间间隔。

// 开启中断 TIM_ITConfig(TIM3, TIM_IT_CC1 | TIM_IT_Update, ENABLE); // 配置NVIC(嵌套向量中断控制器) NVIC_InitTypeDef NVIC_InitStructure; NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); // 启动定时器 TIM_Cmd(TIM3, ENABLE);

我们需要几个全局变量来记录状态和数据:

  • capture_stage: 记录当前捕获阶段(0:等待第一个上升沿;1:已捕获上升沿,等待下降沿;2:已捕获下降沿,等待下一个上升沿以计算周期)。
  • ticks_rise,ticks_fall: 记录上升沿和下降沿捕获到的计数器值。
  • period_ticks,pulse_width_ticks: 计算出的周期和脉宽计数值。
  • overflow_count: 记录从一次捕获到下一次捕获之间,定时器溢出的次数。

3.5 中断服务程序逻辑实现

中断服务程序TIM3_IRQHandler是整个算法的核心。逻辑必须清晰且高效。

volatile uint8_t capture_stage = 0; volatile uint32_t ticks_rise = 0, ticks_fall = 0; volatile uint32_t period_ticks = 0, pulse_width_ticks = 0; volatile uint16_t overflow_count = 0; volatile uint32_t last_capture = 0; void TIM3_IRQHandler(void) { // 处理更新(溢出)中断 if (TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET) { overflow_count++; // 溢出次数加1 TIM_ClearITPendingBit(TIM3, TIM_IT_Update); } // 处理捕获/比较1中断 if (TIM_GetITStatus(TIM3, TIM_IT_CC1) != RESET) { uint32_t current_capture = TIM_GetCapture1(TIM3); // 读取当前捕获值 switch (capture_stage) { case 0: // 等待第一个上升沿 ticks_rise = current_capture; overflow_count = 0; // 切换为下降沿捕获 TIM_OC1PolarityConfig(TIM3, TIM_ICPolarity_Falling); capture_stage = 1; break; case 1: // 已捕获上升沿,等待下降沿 ticks_fall = current_capture; // 计算高电平脉宽,需考虑溢出 pulse_width_ticks = (ticks_fall > ticks_rise) ? (ticks_fall - ticks_rise) : (ticks_fall + 0x10000 - ticks_rise); // 假设16位定时器 pulse_width_ticks += overflow_count * 65536; // 加上溢出带来的计数 overflow_count = 0; // 切换为上升沿捕获,准备测周期 TIM_OC1PolarityConfig(TIM3, TIM_ICPolarity_Rising); capture_stage = 2; break; case 2: // 已捕获下降沿,等待下一个上升沿(即周期结束) period_ticks = (current_capture > ticks_rise) ? (current_capture - ticks_rise) : (current_capture + 0x10000 - ticks_rise); period_ticks += overflow_count * 65536; overflow_count = 0; // 更新上升沿时间戳,为下一个周期做准备 ticks_rise = current_capture; // 切换为下降沿捕获,开始测量下一个脉宽 TIM_OC1PolarityConfig(TIM3, TIM_ICPolarity_Falling); capture_stage = 1; // 注意,这里回到阶段1,而不是0 break; } TIM_ClearITPendingBit(TIM3, TIM_IT_CC1); } }

注意:上述代码中的TIM_OC1PolarityConfig函数用于动态切换捕获极性。0x1000065536的十六进制表示,即ARR+1(对于ARR=0xFFFF)。这段代码实现了在一个完整PWM周期内,先后捕获上升沿、下降沿、下一个上升沿,从而计算出脉宽和周期。capture_stage状态机确保了流程的正确性。

3.6 主循环中计算频率与占空比

在主函数或某个任务中,我们可以定期读取period_tickspulse_width_ticks来计算最终结果。为了防止中断中正在更新数据时读取到不完整的值,可以临时关闭中断进行读取。

float get_pwm_freq_hz(void) { uint32_t period; __disable_irq(); // 关中断,确保读取原子性 period = period_ticks; __enable_irq(); if (period == 0) return 0.0f; // 防止除零 // 定时器计数频率 = 72MHz / (PSC+1) = 1MHz float timer_freq = 72000000.0f / 72.0f; // 1,000,000 Hz return timer_freq / period; // 频率 = 计数频率 / 周期计数值 } float get_pwm_duty_cycle(void) { uint32_t period, pulse; __disable_irq(); period = period_ticks; pulse = pulse_width_ticks; __enable_irq(); if (period == 0) return 0.0f; return (pulse * 100.0f) / period; // 返回百分比占空比 }

4. HAL库方案实现:结构清晰与跨系列兼容

HAL库(Hardware Abstraction Layer)是ST主推的新一代库,代码更结构化,跨STM32系列兼容性更好,但有时效率稍低,且需要理解其回调机制。我们同样实现双边沿捕获,使用HAL库的输入捕获中断模式。

4.1 CubeMX配置与代码生成

使用STM32CubeMX工具可以极大简化初始化过程。

  1. 选择定时器:例如TIM3。
  2. 时钟源:选择内部时钟(Internal Clock)。
  3. 参数配置
    • Prescaler (PSC - 1): 71 (将72MHz分频为1MHz)。
    • Counter Period (ARR): 65535。
    • Counter Mode: Up。
    • 其他保持默认。
  4. 通道配置:选择通道1为“Input Capture direct mode”。
  5. 极性(Polarity):这里有个关键点。在CubeMX中,我们通常先配置为“Rising Edge”(上升沿)。双边沿捕获需要在代码中动态修改,或者利用定时器的“PWM输入模式”(该模式自动使用两个通道测量周期和脉宽,但占用资源多)。我们采用代码动态修改的方式,所以这里先选上升沿。
  6. IC Filter:根据信号质量设置,默认为0。
  7. NVIC设置:使能TIM3全局中断。

生成代码后,我们会在tim.c中看到MX_TIM3_Init函数,里面已经完成了GPIO和定时器的基本初始化。

4.2 HAL库输入捕获的启动与回调机制

HAL库将操作封装成了几个核心函数和回调函数。我们需要在生成代码的基础上进行补充。

首先,在main.c的合适位置(如初始化所有外设后),启动定时器的输入捕获中断。

// 启动TIM3的输入捕获通道1(上升沿) if (HAL_TIM_IC_Start_IT(&htim3, TIM_CHANNEL_1) != HAL_OK) { Error_Handler(); }

HAL库的中断处理流程是:硬件中断发生 → HAL库的通用中断处理函数(如TIM3_IRQHandler,已由CubeMX生成) → HAL库调用相应的回调函数。我们需要重写(__weak修饰)这个回调函数来处理捕获事件。

4.3 实现输入捕获回调函数与状态机

HAL库为输入捕获提供了HAL_TIM_IC_CaptureCallback回调函数。每当捕获事件发生时,它就会被调用。我们需要在这个函数里实现类似标准库的状态机逻辑。

同样,我们需要定义一些全局变量来保存状态和数据。注意,HAL库提供了TIM_HandleTypeDef结构体指针htim和通道号Channel作为参数。

// 在main.c文件顶部定义全局变量 volatile uint8_t ic_capture_stage = 0; volatile uint32_t ic_ticks_rise = 0, ic_ticks_fall = 0; volatile uint32_t ic_period_ticks = 0, ic_pulse_width_ticks = 0; volatile uint32_t ic_overflow_count = 0; // 重写输入捕获回调函数 void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) { // 确保是我们关心的定时器和通道 if (htim->Instance == TIM3 && htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1) { uint32_t current_capture = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1); switch (ic_capture_stage) { case 0: // 第一个上升沿 ic_ticks_rise = current_capture; ic_overflow_count = 0; __HAL_TIM_SET_CAPTUREPOLARITY(htim, TIM_CHANNEL_1, TIM_INPUTCHANNELPOLARITY_FALLING); ic_capture_stage = 1; break; case 1: // 下降沿 ic_ticks_fall = current_capture; // 计算脉宽,考虑溢出 if (ic_ticks_fall >= ic_ticks_rise) { ic_pulse_width_ticks = ic_ticks_fall - ic_ticks_rise; } else { ic_pulse_width_ticks = (0x10000 - ic_ticks_rise) + ic_ticks_fall; } ic_pulse_width_ticks += ic_overflow_count * 65536; ic_overflow_count = 0; __HAL_TIM_SET_CAPTUREPOLARITY(htim, TIM_CHANNEL_1, TIM_INPUTCHANNELPOLARITY_RISING); ic_capture_stage = 2; break; case 2: // 第二个上升沿(周期结束) // 计算周期 if (current_capture >= ic_ticks_rise) { ic_period_ticks = current_capture - ic_ticks_rise; } else { ic_period_ticks = (0x10000 - ic_ticks_rise) + current_capture; } ic_period_ticks += ic_overflow_count * 65536; ic_overflow_count = 0; // 为下一个周期做准备 ic_ticks_rise = current_capture; __HAL_TIM_SET_CAPTUREPOLARITY(htim, TIM_CHANNEL_1, TIM_INPUTCHANNELPOLARITY_FALLING); ic_capture_stage = 1; break; } } }

__HAL_TIM_SET_CAPTUREPOLARITY用于动态切换捕获边沿,这是实现双边沿捕获的关键。

4.4 处理溢出中断与计算函数

HAL库中,更新(溢出)中断有独立的回调函数HAL_TIM_PeriodElapsedCallback。我们需要重写它来记录溢出次数。

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if (htim->Instance == TIM3) { ic_overflow_count++; } }

计算频率和占空比的函数与标准库类似,只是变量名不同。

float get_pwm_freq_hz_hal(void) { uint32_t period; uint32_t primask = __get_PRIMASK(); // 保存中断状态 __disable_irq(); period = ic_period_ticks; __set_PRIMASK(primask); // 恢复中断状态 if (period == 0) return 0.0f; float timer_freq = 72000000.0f / 72.0f; // 1MHz return timer_freq / period; } float get_pwm_duty_cycle_hal(void) { uint32_t period, pulse; uint32_t primask = __get_PRIMASK(); __disable_irq(); period = ic_period_ticks; pulse = ic_pulse_width_ticks; __set_PRIMASK(primask); if (period == 0) return 0.0f; return (pulse * 100.0f) / period; }

5. 实测中的关键细节、常见问题与优化策略

理论代码写完了,但真正让它在板子上跑起来并稳定工作,才是挑战的开始。下面分享几个我踩过坑后总结的关键点。

5.1 信号毛刺与输入滤波器配置

如果你的PWM信号来自电机、开关电源附近,或者导线较长,可能会引入毛刺。这些毛刺会导致定时器误触发捕获中断,使测量结果完全错误。STM32的输入捕获通道有一个数字滤波器(ICF),可以抑制短于一定时间的脉冲。

在标准库中,通过TIM_ICInitStructure.TIM_ICFilter配置;在HAL库/CubeMX中,是ICFilter参数。这个值范围通常是0-15,代表多少个定时器时钟周期内的信号变化会被视为噪声而忽略。例如,定时器时钟1MHz,设置ICFilter=5,则持续时间短于5us的毛刺会被滤除。但要注意,滤波器也会引入微小的延迟,并且会限制你能测量的最高信号频率(因为高频率的合法边沿也可能被滤掉)。通常从0开始,如果发现测量值跳变异常,再逐步增加。

5.2 计数器溢出处理与32位扩展

我们的示例代码假设ARR=65535,使用16位计数器。当测量低频率信号时(比如1Hz),两个上升沿之间计数器会溢出很多次。overflow_count变量是uint16_t,如果溢出超过65535次,它也会溢出,导致计算错误。对于极低频率测量,需要将overflow_count改为uint32_t

更稳健的做法是,利用定时器的溢出更新事件,将overflow_count作为计数器高16位,与捕获值(低16位)共同组成一个虚拟的32位或64位时间戳。在捕获中断中读取overflow_countCCRx,合并计算。这样可以避免在两次捕获之间因中断响应延迟而漏计溢出次数的问题(虽然概率极低)。

5.3 测量范围与精度权衡

测量范围和精度是一对矛盾。

  • 提高精度:提高定时器的计数频率(减小PSC)。例如,用72MHz直接计数,每个计数约13.9ns。但这样计数器溢出很快(ARR=65535时约0.9ms溢出一次),测量低频率信号时溢出处理频繁,且overflow_count容易溢出。
  • 扩大范围:降低计数频率(增大PSC)。例如,PSC=7200-1,计数频率10kHz,每个计数0.1ms。这样溢出周期长达6.55秒,可以测量很低的频率,但精度下降到0.1ms。

策略:根据应用需求选择。如果已知信号频率范围,可以动态调整PSC。或者,使用两个定时器,一个高频精测脉宽,一个低频粗测周期。

5.4 HAL库动态切换极性时的潜在风险

在HAL库回调函数中,我们使用__HAL_TIM_SET_CAPTUREPOLARITY宏来切换边沿。这个宏实际上修改了TIMx_CCER寄存器的CCxP和CCxNP位。这里有一个隐藏的坑:在极少数情况下,当你刚刚捕获一个边沿并立即切换极性时,如果信号非常“干净”,新的边沿(比如下降沿)可能紧接着就来了,而此时硬件可能还没来得及完全响应新的极性设置,导致漏捕或误捕。

一种更安全的做法是,在切换极性后,手动清除一下该通道的捕获标志位并重新使能捕获。不过,在大多数应用场景下,PWM信号频率不会高到这种程度,直接切换是可行的。如果遇到测量不稳定,可以尝试在切换极性后加入一小段延时(几个NOP指令),或者采用PWM输入模式(该模式硬件自动处理两个边沿)。

5.5 没有信号或信号异常时的处理

当输入引脚没有PWM信号时,程序会一直卡在某个捕获阶段等待。这通常不是问题。但如果需要检测“无信号”状态,可以添加一个软件看门狗。在main循环中,如果超过预期最大周期时间(例如,对于50Hz信号,周期最大20ms)仍未完成一个完整的捕获周期(即ic_capture_stage没有从2回到1或从1回到2),则可以判定为信号丢失,将测量值清零或标记为无效。

另外,如果占空比为0%(常低)或100%(常高),我们的算法只能捕获到一种边沿,会一直停留在某个阶段。这时也需要超时判断来处理这种边界情况。

5.6 使用PWM输入模式简化测量

STM32的定时器提供了一个叫“PWM输入模式”的特殊功能。它本质上是将一个输入信号同时映射到两个捕获通道(例如TI1映射到IC1和IC2),并自动将其中一个通道配置为上升沿捕获,另一个为下降沿捕获,且硬件自动将两次捕获的差值分别存入周期和脉宽寄存器。你只需要读取两个寄存器的值,并处理一次溢出即可。这大大简化了软件逻辑,且测量更精确(硬件自动处理,不受中断延迟影响)。

但它的缺点是占用两个捕获通道。在资源紧张的项目中可能不适用。在CubeMX中,可以直接选择“PWM Input Mode”,它会自动配置好两个通道。在标准库中,需要配置TIM_ICInitStructure.TIM_ICSelectionTIM_ICSelection_IndirectTI等参数,并设置从模式控制器。具体配置比普通输入捕获稍复杂,但一旦配好,软件侧会轻松很多。

我个人在资源允许的情况下,会优先考虑PWM输入模式,它的稳定性和代码简洁性是巨大的优势。