尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

STM32定时器输入捕获:从原理到实战,精准测量频率与占空比

STM32定时器输入捕获:从原理到实战,精准测量频率与占空比 1. 项目概述从“数数”到“计时”的跨越搞嵌入式开发尤其是玩STM32的定时器这玩意儿绝对是绕不开的核心外设。很多人最开始接触定时器可能就是用它来做个精准延时替代那个不太准的HAL_Delay()或者生成个PWM波驱动个舵机、电机。这相当于让定时器在“自己数数”数到一定值就干点啥这是输出比较的典型应用。但今天咱们要聊的是定时器的另一项绝活输入捕获。简单说它干的不是“自己数数然后做事”而是“盯着别人看别人什么时候做事并把这个时刻记下来”。这有什么用想象一下你想测量一个方波信号的频率或者占空比或者你想知道一个按键被按下的精确时长又或者你想捕捉一个外部事件的精确发生时刻。这时候输入捕获功能就派上用场了。它就像一个高度专注的计时员眼睛死死盯着一个GPIO引脚我们称之为捕获通道一旦这个引脚上发生了你预设的事件比如上升沿或下降沿它就会立刻“咔嚓”一下把当前定时器计数器的值“捕获”并锁存到一个专用的寄存器里同时还可以给你发个中断通知“嘿你要我盯的那个事儿发生了发生时刻我已经记在小本本捕获/比较寄存器上了你快来处理”我最近在做一个无人机飞控相关的项目需要精确测量遥控器接收机输出的PPM脉冲位置调制信号每个脉冲的宽度就代表了不同的通道指令。如果测量不准飞机姿态控制就会出问题。最开始我用外部中断加普通定时器的方式去测精度和稳定性总是不尽如人意特别是在高频信号下中断响应和软件处理的延迟成了瓶颈。后来切换到使用定时器的输入捕获模式硬件自动记录边沿时刻精度直接提升了一个数量级代码也清爽多了。这个实验就是带你一步步打通STM32输入捕获的任督二脉让你不仅能测频率、占空比更能理解其底层硬件机制以后遇到类似需求能信手拈来。2. 核心原理与硬件架构拆解要玩转输入捕获不能只停留在调用HAL库函数的层面必须得对STM32定时器特别是其输入捕获相关的硬件结构有个清晰的画像。我们以STM32最通用的高级/通用定时器如TIM1, TIM2, TIM3, TIM4等为例来拆解。2.1 定时器核心时基单元这是定时器的心脏决定了我们“计时”的尺子有多精确。主要由三部分组成计数器寄存器 (CNT)一个16位或32位的向上/向下/中央对齐的计数器随着时钟脉冲不断递增或递减。它就是我们要“捕获”的那个时刻值。预分频器 (PSC)对来自APB总线的时钟进行分频得到驱动计数器的实际计数时钟CK_CNT。公式是f_CK_CNT f_TIMx_CLK / (PSC 1)。通过调整PSC我们可以改变计数器的“计数速度”从而改变时间测量的范围和分辨率。自动重装载寄存器 (ARR)决定了计数器的周期。在向上计数模式下当CNT计数到ARR值后下一个时钟就会清零并产生一个更新事件。ARR1就是一个计数周期对应的计数值。注意输入捕获测量的是“边沿之间的计数值差”这个差值乘以计数时钟的周期就是实际的时间。因此PSC和ARR的配置直接决定了你能测量的最大时间长度和最小时间分辨率。2.2 输入捕获通道的“流水线”一个定时器通常有多个独立的捕获/比较通道比如TIM2有4个。每个通道都是一套完整的信号处理流水线输入滤波器 (ICxF)GPIO引脚上的信号可能带有毛刺。滤波器实际上是一个事件计数器只有当连续检测到N次相同的电平采样后才认为是一个有效的边沿。这对于消抖特别是机械按键非常有用。边沿检测器检测经过滤波后的信号是发生了上升沿还是下降沿。这个由我们配置的捕获极性选择位CCxP, CCxNP来决定。捕获寄存器 (CCRx)这是关键当边沿检测器发现目标边沿时它会立刻将当前计数器CNT的值复制到对应的捕获/比较寄存器CCRx中。这个过程是硬件自动完成的速度极快几乎没有延迟。预分频器 (ICxPS)可以对输入信号进行分频实现“每N个边沿捕获一次”。比如设置为“每4个上升沿捕获一次”可以用来测量信号周期而不一定是频率。中断/DMA使能逻辑当捕获事件发生时CCRx被写入如果使能了对应的捕获/比较中断CCxIE或DMA请求就会向内核或DMA控制器发出请求通知软件进行后续处理如读取CCRx值、计算时间差。2.3 测量频率与占空比的经典思路理解了硬件测量思路就清晰了。以测量一个未知的PWM波频率和占空比为例通常有两种方法方法一双沿捕获法单通道将定时器通道配置为上升沿和下降沿都捕获。有些定时器支持直接设置极性为“双边沿”更多时候我们需要在中断里动态切换极性。第一次上升沿到来捕获值T1硬件自动将极性切换为下降沿捕获或软件切换。下降沿到来捕获值T2则高电平时间Th (T2 - T1) * 计数时钟周期。同时硬件/软件切换回上升沿捕获。第二次上升沿到来捕获值T3则整个周期T (T3 - T1) * 计数时钟周期。频率Freq 1 / T占空比Duty Th / T。这种方法只需要一个定时器通道但需要处理两次捕获中断并在中断中切换极性对软件响应速度有一定要求。方法二双通道协作法使用同一个定时器的两个通道如CH1和CH2连接到同一个输入引脚通过GPIO复用功能。通道1配置为上升沿捕获通道2配置为下降沿捕获。上升沿触发时读取CH1的捕获值CCR1。下降沿触发时读取CH2的捕获值CCR2。下一个上升沿触发再次读取CCR1。高电平时间由CCR2 - CCR1(第一次)得出周期由CCR1(第二次) - CCR1(第一次)得出。这种方法硬件自动并行捕获软件只需在各自的中断里读取数据逻辑更清晰资源占用稍多。实操心得对于高频信号比如上百KHz强烈推荐方法二。因为方法一需要在中断里切换极性配置会引入几个时钟周期的软件延迟虽然短但不可忽略。而方法二的两次捕获完全由硬件并行完成时间戳的精度更高。STM32的定时器输入通道支持“异或”功能可以将两个通道的输入逻辑合并更方便地实现此方法。3. 从零构建CubeMX配置与代码实现光说不练假把式我们以STM32F103C8T6蓝色药丸核心板为例使用STM32CubeMX和HAL库实现一个测量外部信号频率和占空比的输入捕获实验。假设我们测量TIM2的通道1PA0引脚上的信号。3.1 CubeMX工程配置详解系统核心与时钟在SYS里选择Debug为Serial Wire如果要用SWD下载调试。在RCC里将HSE高速外部时钟设置为Crystal/Ceramic Resonator使用外部8MHz晶振。进入Clock Configuration标签页将系统时钟源选为PLLCLKHSE输入8MHz经过PLL倍频到72MHz作为系统时钟SYSCLK。APB1总线时钟TIM2挂载于此为72MHz。定时器TIM2配置找到TIM2首先配置时基单元Clock Source选择Internal Clock。Prescaler (PSC - 16 bits value)设置为71。因为APB1时钟是72MHz分频后得到72MHz / (711) 1MHz的计数时钟。这意味着计数器每1微秒加1。Counter Mode选择Up向上计数。Counter Period (AutoReload Register - 16 bits value)设置为65535最大值。因为我们要测量的是两个边沿之间的时间差只要这个时间差不超过计数器溢出周期即可。1MHz时钟下65535对应65.535ms对于测量中低频信号足够。如果需要测更长时间可以增大PSC值但会降低分辨率。auto-reload preload使能。这样对ARR的修改会在下次更新事件生效避免中途修改导致计数异常。然后配置通道1为输入捕获模式点击CH1选择Input Capture direct mode。Polarity Selection选择Rising Edge我们先配置为上升沿捕获后续在代码中切换以实现双边沿。IC Selection选择Direct信号直接连接到TI1输入。IC Filter根据信号质量设置。如果信号干净设为0无滤波。如果信号有毛刺如按键可以设为N表示需要连续N个采样点一致才确认边沿。IC Prescaler选择No division每个边沿都捕获。中断与GPIO配置在NVIC Settings标签页勾选TIM2 global interrupt使能全局中断。PA0引脚会自动配置为复用推挽输出模式无需额外操作。生成代码设置好项目名称、路径、IDE如MDK-ARM V5然后点击GENERATE CODE。3.2 HAL库代码实现与解析CubeMX生成代码后我们在main.c和stm32f1xx_it.c中补充业务逻辑。这里我们采用**方法一单通道动态切换极性**进行实现因为它更节省通道资源理解其流程对掌握中断处理至关重要。// 在main.c用户代码区0添加变量定义 /* Private variables ---------------------------------------------------------*/ uint32_t uwIC1Value1 0, uwIC1Value2 0, uwIC1Value3 0; uint32_t uwDiffCapture 0; uint8_t uwIsFirstCaptured 0; // 是否是第一次捕获标志 float uwFrequency 0.0, uwDutyCycle 0.0; // 在main函数中启动定时器和中断 /* USER CODE BEGIN 2 */ HAL_TIM_IC_Start_IT(htim2, TIM_CHANNEL_1); // 启动输入捕获并开启中断 /* USER CODE END 2 */接下来在stm32f1xx_it.c中找到TIM2全局中断服务函数TIM2_IRQHandler它内部会调用HAL库的中断处理回调函数。我们需要重写输入捕获中断的回调函数。// 在main.c文件末尾/* USER CODE END 4 */之前添加回调函数 /* USER CODE BEGIN 4 */ void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) { if (htim-Instance TIM2 htim-Channel HAL_TIM_ACTIVE_CHANNEL_1) { if (uwIsFirstCaptured 0) // 第一次捕获必然是上升沿 { // 获取上升沿捕获值 uwIC1Value1 HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1); // 切换为下降沿捕获 __HAL_TIM_SET_CAPTUREPOLARITY(htim, TIM_CHANNEL_1, TIM_INPUTCHANNELPOLARITY_FALLING); uwIsFirstCaptured 1; // 设置标志位 } else if (uwIsFirstCaptured 1) // 第二次捕获当前是下降沿 { // 获取下降沿捕获值 uwIC1Value2 HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1); // 计算高电平时间计数值差考虑计数器溢出 if (uwIC1Value2 uwIC1Value1) { uwDiffCapture uwIC1Value2 - uwIC1Value1; } else { // 发生了溢出差值需要加上ARR值 uwDiffCapture (0xFFFF - uwIC1Value1) uwIC1Value2 1; } // 切换为上升沿捕获 __HAL_TIM_SET_CAPTUREPOLARITY(htim, TIM_CHANNEL_1, TIM_INPUTCHANNELPOLARITY_RISING); uwIsFirstCaptured 2; // 设置标志位等待下一个上升沿计算周期 } else if (uwIsFirstCaptured 2) // 第三次捕获新的上升沿周期到来 { // 获取新的上升沿捕获值 uwIC1Value3 HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1); uint32_t uwPeriodCapture 0; // 计算周期计数值差 if (uwIC1Value3 uwIC1Value1) { uwPeriodCapture uwIC1Value3 - uwIC1Value1; } else { uwPeriodCapture (0xFFFF - uwIC1Value1) uwIC1Value3 1; } // 计算实际时间和参数 float T_clock 1.0 / 1000000.0; // 1MHz计数时钟周期1us float high_time uwDiffCapture * T_clock; // 高电平时间单位秒 float period_time uwPeriodCapture * T_clock; // 信号周期单位秒 uwFrequency 1.0 / period_time; // 频率单位Hz uwDutyCycle (high_time / period_time) * 100.0; // 占空比单位% // 为下一次测量做准备 uwIC1Value1 uwIC1Value3; // 将本次上升沿作为下一次测量的起点 uwIsFirstCaptured 1; // 状态置为“已捕获第一个上升沿”等待下降沿 // 注意此时极性已经是上升沿无需切换 } } } /* USER CODE END 4 */代码关键点解析状态机逻辑使用uwIsFirstCaptured作为状态标志012清晰地管理“首次上升沿 - 下降沿 - 第二次上升沿”的完整捕获流程。溢出处理这是输入捕获的核心难点。计数器CNT是16位的会从0计数到ARR这里65535然后溢出归零。如果两次捕获发生在溢出前后直接相减会得到负数实际是很大的正数。我们的处理方法是判断后一次值是否小于前一次如果是则差值等于(最大值 - 前值 后值 1)。这里的1是因为从65535溢出到0计数值也增加了一次。极性动态切换使用__HAL_TIM_SET_CAPTUREPOLARITY宏在中断中动态修改捕获边沿。这是单通道实现双边沿捕获的关键。时间计算我们配置的计数时钟是1MHz1us所以捕获值差直接乘以1e-6就是时间差秒。频率和占空比据此算出。重要注意事项在中断回调函数HAL_TIM_IC_CaptureCallback中不要使用printf、sprintf等耗时长的函数来打印结果。这些函数执行时间可能长达几毫秒会严重阻塞中断导致丢失后续的捕获事件。正确的做法是将计算出的uwFrequency和uwDutyCycle赋值给全局变量然后在主循环中查询并打印。4. 精度提升与高级应用技巧基础的输入捕获能工作但要想在严苛的工程环境中稳定、精确还需要一些进阶技巧。4.1 应对高频信号与溢出策略当信号频率很高或者我们为了高分辨率而设置了很高的计数时钟减小PSC时两个边沿之间的计数值差可能很小但计数器溢出的风险会急剧增加。例如用72MHz直接驱动计数器PSC0计数周期仅约0.9ms16位计数器。测量一个10KHz的信号周期0.1ms看似没问题但如果测量过程中发生其他高优先级中断阻塞可能错过几次捕获等处理时计数器已经溢出多次。解决方案使用定时器溢出中断。使能更新中断溢出中断在CubeMX中配置TIM2时除了输入捕获中断还要勾选Update interrupt (TIM2 update interrupt)。添加溢出计数变量定义一个volatile uint32_t类型的全局变量uwTim2OverflowCount。重写更新中断回调void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if (htim-Instance TIM2) { uwTim2OverflowCount; } }修改时间差计算在计算两个捕获值的时间差时不仅要考虑单次周期内的差值还要把中间发生的溢出次数算进去。// 假设捕获时刻t1和t2对应的溢出次数分别为overflow_cnt1和overflow_cnt2 uint32_t delta_ticks 0; if (overflow_cnt2 overflow_cnt1) { // 同一次溢出周期内 delta_ticks capture2 - capture1; } else { // 跨越了N次溢出 delta_ticks (0xFFFF - capture1) capture2 1; delta_ticks (overflow_cnt2 - overflow_cnt1 - 1) * (ARR 1); // 加上完整的溢出周期 } float time_diff delta_ticks * T_clock;这样理论上我们可以测量任意长的时间只要uwTim2OverflowCount这个32位变量不溢出在1MHz下溢出一次65.5ms需要约13万次溢出才会导致32位变量溢出对应约2.4小时。4.2 使用DMA解放CPU对于需要连续、高速捕获的应用例如高频脉冲序列分析频繁的中断仍然会给CPU带来负担。此时可以将输入捕获与DMA结合。配置思路将定时器的捕获/比较事件如TIMx_CC1事件连接到DMA。配置DMA为循环模式目标地址是一个内存中的数组比如uint32_t capture_buffer[100]。当捕获事件发生时硬件自动将CCR1寄存器的值通过DMA传输到数组的下一个位置完全无需CPU干预。CPU可以定期例如数组半满或全满时产生DMA中断去处理这一批捕获数据进行批量计算。这极大地提高了系统效率并能捕获到更高速的信号。在CubeMX中可以在TIM2的DMA Settings标签页添加DMA请求选择TIM2_CH1模式为Circular数据宽度为Word与CCR1寄存器宽度匹配。在代码中启动DMA捕获HAL_TIM_IC_Start_DMA(htim2, TIM_CHANNEL_1, (uint32_t*)capture_buffer, BUFFER_SIZE);。4.3 输入滤波与消抖的实战配置输入滤波器IC Filter并非简单的软件延时去抖而是数字滤波器。它连续采样输入信号只有当连续N个采样点都一致时才将稳定的电平传递给边沿检测器。采样频率由内部触发输入时钟通常为f_DTS决定f_DTS一般等于f_TIMx_CLK或经过分频。滤波参数N取值范围0-15。N0表示无滤波。N0时滤波器带宽和延迟会相应变化。具体关系在参考手册的“定时器输入框图”部分有详细说明通常N值越大抗噪能力越强但信号边沿的延迟也越大。对于机械按键消抖假设f_DTS 72MHz设置IC Filter 0x6表示N6。那么有效的边沿需要连续6个采样点一致。在72MHz下6个采样点约83ns这对于毫秒级的按键抖动来说过滤效果极好且硬件完成不占用CPU。这比在GPIO外部中断里用软件延时消抖要可靠和高效得多。5. 典型问题排查与调试心得即使按照步骤配置第一次做输入捕获也很可能遇到各种问题。下面是我踩过的一些坑和解决方法。5.1 常见问题速查表现象可能原因排查步骤与解决方案根本进不了捕获中断1. 定时器时钟未使能。2. NVIC中断未使能或优先级配置有冲突。3. GPIO引脚模式配置错误。4. 捕获通道未正确映射到引脚。1. 检查__HAL_RCC_TIMx_CLK_ENABLE()是否调用CubeMX通常已生成。2. 在CubeMX的NVIC配置中确认中断已使能并检查是否有更高优先级中断一直抢占。3. 确认GPIO已配置为复用模式Alternate Function而非简单的输入/输出。4. 查阅数据手册的“复用功能映射”表确认该定时器通道是否真的可以映射到你使用的引脚。能进中断但捕获值不变或乱跳1. 输入信号本身有问题用示波器看。2. 预分频器PSC或滤波器ICF设置过大导致捕获不到快速边沿。3. 计数器时钟源不对或未启动。4. 中断处理时间过长错过了后续捕获。1.首要步骤用示波器检查输入信号的波形、电压幅值是否在STM32识别范围内、边沿是否陡峭。2. 尝试将PSC调小ICF设为0排除配置问题。3. 检查HAL_TIM_Base_Start()或HAL_TIM_IC_Start_IT()是否成功调用。4. 简化中断服务函数移除任何耗时操作如浮点计算、打印。确保中断处理时间远小于信号周期。测量结果偶尔准确偶尔偏差巨大1.计数器溢出未处理最常见。2. 在计算时间差时直接用了uint16_t类型做减法未处理溢出。3. 全局变量在中断和主循环间访问未加保护虽然后果通常是数据错乱而非巨大偏差。1. 实现4.1节所述的溢出计数机制。2. 确保计算差值时使用uint32_t并按照代码示例中的方法处理翻转。3. 对于简单的频率测量如果信号频率较高可以增大ARR值或降低计数频率让一个信号周期内绝不发生溢出简化逻辑。占空比测量不准特别是接近0%或100%时1. 信号边沿有振荡或缓慢爬坡导致边沿检测点不唯一。2. 输入滤波参数设置不当引入了固定延迟。3. 测量方法固有误差如方法一中切换极性指令执行需要时间。1. 优化硬件电路确保信号边沿干净。可在输入端增加施密特触发器整形。2. 校准滤波延迟。如果必须使用滤波在计算时减去一个固定的延迟值需要通过实验测定。3. 对于高精度要求采用方法二双通道硬件延迟对称性更好。或者直接使用定时器的PWM输入模式下一节介绍这是专为高精度测量设计的。5.2 调试利器逻辑分析仪与示波器工欲善其事必先利其器。调试输入捕获光靠看串口打印是远远不够的。逻辑分析仪如Saleae连接被测信号线和可能用于调试的GPIO线比如在中断里翻转一个IO用于指示中断发生时刻。可以同时捕获多路数字信号清晰看到中断响应相对于输入边沿的延迟非常直观地判断是否发生了溢出、中断是否及时响应。是分析数字时序问题的首选。示波器用于观察输入信号的模拟特性。看看边沿是否陡峭上升/下降时间是否有过冲、振铃电平幅值是否稳定在VIL和VIH之间。很多“软件没问题”的故障根源都在硬件信号质量上。一个常用的调试技巧是在输入捕获中断的开始处用一条HAL_GPIO_TogglePin(GPIOx, GPIO_PIN_x)语句来翻转一个空闲的GPIO引脚。然后用逻辑分析仪同时抓取这个调试引脚和输入信号你就能精确地看到从边沿发生到进入中断处理函数中间经过了多长时间。这对于评估中断延迟和优化代码至关重要。5.3 进阶PWM输入模式——专为测量而生如果你仔细阅读STM32参考手册会发现定时器有一个叫做“PWM输入模式”的特殊功能。这其实是输入捕获的一个“豪华套餐”硬件上自动将两个通道通常是TI1和TI2连接起来一个捕获上升沿一个捕获下降沿并且自动将两次捕获的值分别存到CCR1和CCR2中还能自动计算两次捕获的时间差周期和占空比并只产生一个中断。配置方法CubeMX选择定时器如TIM2。将通道1的输入映射到TI1或TI2。在Combined Channels中选择PWM Input on CH1或CH2。CubeMX会自动将通道2配置为互补的捕获边沿并设置好从模式控制器等。优势硬件全自动无需在中断中切换极性测量精度达到硬件极限。单中断一个周期只产生一个更新中断或捕获中断CPU负担更轻。高可靠性避免了软件切换极性可能带来的竞争冒险问题。当你的应用纯粹是为了高精度测量PWM信号的频率和占空比时应优先考虑使用PWM输入模式它更简单、更可靠、更精确。6. 项目总结与扩展思考经过这一番从原理到配置从代码到调试的深入探索你应该已经对STM32的输入捕获功能有了立体的认识。它绝不仅仅是一个简单的“记时间”功能而是一套由时基、滤波、边沿检测、捕获寄存器、中断/DMA协同工作的精密系统。回顾整个项目最关键的两个思维转变是从“软件轮询”到“硬件事件驱动”以及从“瞬间值”到“时间差”。输入捕获教会我们在嵌入式世界里让专业的硬件外设去做它最擅长的事情高精度计时、快速响应边沿而CPU则专注于更上层的逻辑和数据处理这是提升系统性能和可靠性的黄金法则。在实际项目中你可以将这个实验进行扩展结合其他外设将测量到的频率或占空比通过串口发送到上位机显示或者用OLED屏实时显示做一个简易的频率计。实现闭环控制测量电机编码器的脉冲频率来计算转速作为速度闭环的反馈。捕捉非周期信号比如测量一个随机脉冲的宽度用于红外遥控解码或超声波测距。多定时器协作用一个定时器产生已知频率的PWM信号用另一个定时器的输入捕获去测量它验证系统精度。最后分享一个我个人的深刻体会阅读参考手册的相应章节永远是最高效的学习方式。CubeMX和HAL库极大地提高了开发效率但它们也屏蔽了硬件细节。当你遇到棘手问题或想追求极致优化时那份几百页的参考手册里往往藏着最权威的答案。试着去理解那些框图、寄存器位定义和时序图你会发现自己对STM32的掌控力又上了一个新的台阶。
返回列表