STM32 PWM输入捕获从模式复位原理与HAL库实战:测量周期与占空比
不少开发板的例程里都能看到1-23 PWM输入捕获-从模式测量脉冲周期-占空比值这一节但很多朋友照着代码抄了一遍换了个信号源就发现测出来的周期和占空比全是乱的。有人怀疑是硬件坏了有人怀疑是库函数版本不对实际上大多数问题都出在没搞懂PWM输入模式这个从模式复位到底在硬件层面做了什么。这篇就把原理、配置、实测坑位一次讲透以STM32F1系列72MHz主频为例代码用HAL库实现只要你手里有一块带定时器的板子和一个可调PWM信号源就能完整复现并验证。1. PWM输入模式和普通捕获到底差在哪1.1 普通双通道捕获为什么容易翻车先说说大家最习惯用的普通输入捕获方案。思路很简单定时器开两个通道CH1捕获上升沿CH2捕获下降沿然后在中断里记录CNT的值。上升沿记下t0下降沿记下t1那么高电平时间就是t1 - t0再等下一个上升沿记下t2周期就是t2 - t0。听起来没有任何问题实际写起来也不复杂但只要你真正用来测信号痛点会一个接一个冒出来。第一个痛点是数据错位。中断是软件响应的上升沿和下降沿事件到达CPU的时间不确定一旦某个边沿对应的中断没有及时响应甚至被更高优先级的中断打断那t0、t1、t2之间的对应关系就乱了。你算出来的高电平时间可能是上一次周期的周期可能是上上次的占空比自然完全失真。而且占空比越接近0%或100%两个边沿离得越近中断响应顺序错乱的概率越高。第二个痛点是CPU负担。普通方案里每个边沿都要进一次中断1kHz的PWM信号每秒要进2000次中断100kHz的PWM信号每秒就是20万次中断。中断里还得做减法、记录变量看起来没多少代码但高频场景下CPU大部分时间都在响应中断主循环基本被饿死。第三个痛点是低频信号测不准。如果你的PWM信号只有几十赫兹两个边沿间隔几十毫秒定时器还没等到下降沿就可能溢出了你还得额外处理溢出中断把溢出的次数也计入周期代码复杂度和出错概率一起上升。从模式测量脉冲周期-占空比值这套方案就是为了同时解决这三个痛点出现的。1.2 从模式复位带来的硬件对齐优势PWM输入模式的核心是让硬件而不是软件来完成周期对齐这件事。它利用了定时器的从模式功能把定时器配置成Reset Mode也就是复位从模式。外部PWM信号的上升沿经过输入滤波和边沿检测后会直接作为触发源硬件自动把计数器CNT清零同时把当前CNT的值锁存到捕获寄存器CCR1里。换句话说每个PWM周期开始的那一瞬间CCR1里存的就是上一个周期结束到这个周期开始的总计数也就是周期值。与此同时通道2的输入信号在内部被反相处理PWM信号的下降沿会触发CCR2的捕获锁存的是从周期开始到下降沿之间的计数值也就是高电平持续时间。这样CCR1和CCR2天然属于同一个PWM周期不存在软件错位的问题。CPU这边只需要在一个边沿中断里把两个寄存器的值读走就行不需要在中断里做减法也不需要维护多个状态变量。对比一下普通方案1kHz信号原来每秒进2000次中断现在只需要1000次100kHz信号原来每秒进20万次中断现在只需要10万次而且每次中断处理的工作量小得多。我整理了一个对比表格方便你决定什么时候用PWM输入模式对比项普通双通道捕获PWM输入模式从模式复位数据对齐软件维护容易错位硬件锁存天然对齐同一周期中断次数每个边沿都触发每个周期触发一次主通道捕获中断内运算需要做减法、存状态直接读取两个捕获值低频溢出需要额外处理溢出中断仍需处理但逻辑简单些配置复杂度直观但易踩坑需理解从模式配置项多适用场景单次测量、低频变化慢连续测量、高频信号、低CPU占用2. 从模式复位机制拆解周期和占空比是怎么被同时锁存的2.1 主模式、从模式到底是什么关系很多初次接触的人看到从模式三个字会困惑定时器不是自己干活的吗怎么还有主从关系这里的主从不是说两个定时器协作而是指一个定时器内部的两种工作逻辑。主模式负责产生一个输出信号TRGO这个信号可以输出给其他外设使用比如触发ADC采样、触发另一个定时器启动。从模式则是反过来的逻辑定时器作为一个从设备接收外部触发输入信号的控制根据触发信号执行复位、门控、触发三种动作之一。这两种模式可以同时存在互不干扰。在PWM输入捕获这个场景里我们真正用到的是从模式的Reset复位功能。触发源选择TI1FP1也就是定时器通道1经过输入滤波和极性选择后的信号。PWM信号的上升沿到达时硬件立即执行两个动作把CNT的值写入CCR1然后把计数器清零。这两个动作是硬件同步完成的不需要软件介入所以时间上非常精确。有人会问既然计数器的值被锁存到CCR1之后就清零了那CCR1存的到底是这一周期的计数值还是上一周期的计数值严格来说上升沿到来时CNT还在跑此时CNT的值是从上一个上升沿到当前这个上升沿之间的计数值所以这就是一个完整的周期。硬件先把值锁存到CCR1再把CNT清零CCR1自然存的就是周期值。这就是标题里从模式测量脉冲周期-占空比值这句话的硬件基础。2.2 TI1FP1、TI1FP2的信号路径与边沿关系讲到这里有必要把定时器输入端的信号路径捋一遍因为很多人就是在这一步配错的。定时器通道1的外部引脚信号进来后会经过三个环节。首先是输入滤波器用来滤除高频毛刺。然后是边沿检测器可以配置为上升沿有效、下降沿有效或者双边沿有效。最后经过极性选择得到真正用来捕获和触发内部信号TI1FP1。注意从模式复位用的触发源是TI1FP1而通道1捕获用的信号也是TI1FP1两者是同一个信号。这就保证了复位动作和CH1捕获动作严格同步都在PWM上升沿发生。通道2这边情况稍特殊。通道2的外部引脚没有接额外的信号在PWM输入模式下我们利用的是定时器内部一个巧妙的设计TI1FP2信号其实是TI1FP1经过反相后的结果。也就是说通道2捕获到的边沿正好是PWM信号的反向边沿。PWM上升沿触发TI1FP1TI1FP2就是下降沿信号PWM下降沿触发TI1FP1到TI1FP2的变化CCR2捕获的就是高电平结束的时刻。这就是为什么在CubeMX配置中通道1要选Direct模式通道2要选Indirect模式通道1的极性是上升沿通道2的极性是下降沿。所谓Indirect就是间接映射IC2捕获的并不是TI2引脚本身而是内部反相后的TI1信号。一次完整的PWM周期内硬件时序是这样的PWM上升沿到达TI1FP1产生触发事件CNT的值被锁存到CCR1随后CNT被清零。PWM信号维持高电平CNT从0开始向上计数。PWM下降沿到达TI1FP2产生捕获事件当前CNT的值被锁存到CCR2。PWM信号维持低电平CNT继续计数直到下一个上升沿再次触发复位。等下一个上升沿到来时CCR1和CCR2里分别保存着周期值和高电平时间值软件在中断里把两个寄存器读走即可。整个过程硬件自动完成数据天然属于同一个周期。2.3 为什么CCR2的值是占空比而不是别的什么只要理解了上面的时序就不难得出一个结论CCR2的值等于从周期开始到下降沿之间的计数值也就是高电平持续时间。CCR1是周期计数值占空比就是CCR2除以CCR1。但这里有个容易混淆的点CCR2的值不是低电平时间。因为计数器是在上升沿复位的之后先经历高电平再经历低电平CCR2捕获发生在下降沿那一下所以它只能代表高电平持续的计数值。如果高电平时间占50%CCR1是1000CCR2就是500如果占空比是75%CCR2就是750。占空比接近100%时下降沿几乎紧挨着下一个上升沿CCR2会非常接近CCR1占空比接近0%时下降沿几乎紧挨着上升沿CCR2会非常小甚至可能因为信号本身的抖动导致CCR2为0或者捕获异常。这个问题在后面踩坑部分我会专门展开。3. 基于HAL库的PWM输入捕获配置3.1 CubeMX参数设置通道映射、从模式触发与极性选择用CubeMX配置PWM输入模式说起来只有几步但每一步都有隐藏细节。以TIM2为例打开定时器配置页面先看Slave Mode部分从模式选择Reset Mode触发源选择TI1FP1。这一步的作用就是让定时器在PWM上升沿时自动复位计数器。然后是通道配置。Channel1选择Input Capture direct mode也就是直接捕获TI1的信号。Channel2选择Input Capture indirect mode注意不是direct。很多人在这一步直接选了direct导致CH2捕获的是CH2引脚本身的信号而CH2引脚上根本没接信号结果CCR2永远读不到有效数据。极性方面Channel1配置为Rising EdgeChannel2配置为Falling Edge。这个配置和从模式触发源是配套的三者缺一不可。如果Channel1的极性配成了下降沿从模式触发也会跟着变成下降沿复位那捕获的周期值就变成了低电平时间加上高电平时间混乱的序列数据完全不可用。预分频和自动重载值需要一起考虑。以72MHz定时器时钟为例预分频器设为71则计数频率为1MHzcounter每1微秒加1。自动重载值设为65535是16位定时器的上限。如果你用的是TIM2或者TIM5这种32位定时器可以把自动重载设成更大的值比如0xFFFFFFFF这样低频信号也能直接测量。CubeMX里对应的时钟树也要检查确认定时器时钟确实是72MHz。很多板子默认APB1总线时钟是36MHz但定时器时钟会倍频到72MHz这个倍频逻辑经常被忽略导致实际计数频率和预期不符测出来的频率整体偏大。3.2 GPIO配置的常见误区输入捕获也是输入这一部分我要单独拿出来说因为真的有很多人在这里翻车。输入捕获嘛很多人潜意识里觉得捕获是个主动的动作就习惯性地把GPIO配置成了复用推挽输出结果信号根本进不来CCR1和CCR2永远读不到有效数据或者只能读到初始值。实际上输入捕获的GPIO要配置为输入模式。在CubeMX中把TIM2_CH1和TIM2_CH2对应的引脚选中后GPIO模式会默认选择Alternate Function但输出方式要确认是Input。如果信号源是推挽输出的PWMGPIO可以配置为浮空输入如果信号源是开漏输出则必须配置上拉输入否则高电平无法被正确识别。我用过的一个开发板例程里例程代码把GPIO配置成了GPIO_MODE_AF_PP也就是复用推挽输出。这个配置在例程自带的信号源场景下居然能工作原因是该信号源内部还有别的通路但当你外接一个标准PWM信号时就测不出来了。这就是典型的例程能用但换个场景就废的情况。3.3 完整代码实现初始化、从模式配置与中断回调CubeMX生成初始化代码后还需要手动补上从模式配置和捕获通道配置。下面是一份完整的TIM2 PWM输入模式初始化代码以STM32F103系列72MHz为例。static TIM_HandleTypeDef s_Tim2Handle; void MX_TIM2_PWMInput_Init(void) { TIM_SlaveConfigTypeDef sSlaveConfig {0}; TIM_MasterConfigTypeDef sMasterConfig {0}; TIM_IC_InitTypeDef sICConfig1 {0}; TIM_IC_InitTypeDef sICConfig2 {0}; __HAL_RCC_TIM2_CLK_ENABLE(); s_Tim2Handle.Instance TIM2; s_Tim2Handle.Init.Prescaler 71; // 72MHz / (711) 1MHz每tick 1us s_Tim2Handle.Init.CounterMode TIM_COUNTERMODE_UP; s_Tim2Handle.Init.Period 0xFFFFFFFF; // TIM2是32位低频也能测 s_Tim2Handle.Init.ClockDivision TIM_CLOCKDIVISION_DIV1; s_Tim2Handle.Init.AutoReloadPreload TIM_AUTORELOAD_PRELOAD_DISABLE; HAL_TIM_IC_Init(s_Tim2Handle); // 从模式复位触发源TI1FP1 sSlaveConfig.SlaveMode TIM_SLAVEMODE_RESET; sSlaveConfig.InputTrigger TIM_TS_TI1FP1; sSlaveConfig.TriggerPolarity TIM_INPUTCHANNELPOLARITY_RISING; sSlaveConfig.TriggerFilter 0; HAL_TIM_SlaveConfigSynchro(s_Tim2Handle, sSlaveConfig); // 主模式不需要额外输出按默认即可 sMasterConfig.MasterOutputTrigger TIM_TRGO_RESET; sMasterConfig.MasterSlaveMode TIM_MASTERSLAVEMODE_DISABLE; HAL_TIMEx_MasterConfigSynchronization(s_Tim2Handle, sMasterConfig); // CH1 直接捕获上升沿用于锁存周期 sICConfig1.ICPolarity TIM_INPUTCHANNELPOLARITY_RISING; sICConfig1.ICSelection TIM_ICSELECTION_DIRECTTI; sICConfig1.ICPrescaler TIM_ICPSC_DIV1; sICConfig1.ICFilter 0; HAL_TIM_IC_ConfigChannel(s_Tim2Handle, sICConfig1, TIM_CHANNEL_1); // CH2 间接捕获下降沿用于锁存高电平时间 sICConfig2.ICPolarity TIM_INPUTCHANNELPOLARITY_FALLING; sICConfig2.ICSelection TIM_ICSELECTION_INDIRECTTI; sICConfig2.ICPrescaler TIM_ICPSC_DIV1; sICConfig2.ICFilter 0; HAL_TIM_IC_ConfigChannel(s_Tim2Handle, sICConfig2, TIM_CHANNEL_2); HAL_TIM_IC_Start_IT(s_Tim2Handle, TIM_CHANNEL_1); HAL_TIM_IC_Start_IT(s_Tim2Handle, TIM_CHANNEL_2); }这段代码有几个关键点要强调。从模式配置里TriggerPolarity要配成上升沿和CH1的捕获极性保持一致。如果你在这里配成了下降沿从模式会在下降沿复位计数器根本测不出正确的周期。TriggerFilter是触发输入滤波器设为0表示不加滤波。如果被测信号比较干净保持默认即可如果信号有毛刺可以适当调大后面我会专门讲滤波的影响。两个通道的ICSelection区别很大CH1是DIRECTTI直接捕获TI1CH2是INDIRECTTI间接捕获TI1FP2也就是反相后的TI1信号。这个区别是整个PWM输入模式能否正常工作的核心千万不能搞混。中断里读取数据的回调函数也很关键volatile uint32_t g_uiPeriod 0; volatile uint32_t g_uiDutyValue 0; volatile uint8_t g_bDataReady 0; void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) { if (htim-Instance TIM2) { if (htim-Channel HAL_TIM_ACTIVE_CHANNEL_1) { // 必须在CH1中断里同时读取两个寄存器的值 g_uiPeriod HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1); g_uiDutyValue HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_2); g_bDataReady 1; } } }注意这里的注释为什么要在CH1中断里同时读两个值因为CH1捕获事件发生在PWM上升沿此时CCR1刚被写入周期值CCR2里保存的是当前周期的高电平时间两个值都还处于有效状态。如果等到CH2中断再读理论上也能读到数据但CH2中断触发时间在下落沿此时CCR1存的是上一个周期的值而CCR2才刚被更新软件上需要额外判断数据对应关系很容易出错。直接在CH1中断里一次读走是最简单的方案。在主循环里计算频率和占空比while (1) { uint32_t uiPeriod; uint32_t uiDutyValue; float fFreq; float fDutyPercent; if (g_bDataReady) { g_bDataReady 0; uiPeriod g_uiPeriod; uiDutyValue g_uiDutyValue; if (uiPeriod 0) { fFreq 1000000.0f / uiPeriod; fDutyPercent (float)uiDutyValue / uiPeriod * 100.0f; } else { fFreq 0.0f; fDutyPercent 0.0f; } // 这里输出或显示结果 } }主循环里使用局部变量复制volatile变量是为了防止在读取过程中数据被中断更新导致读到半个周期的值和半个周期的旧值混在一起。虽然对于32位赋值而言大部分架构是原子的但养成这种习惯能避免后续踩更隐蔽的坑。4. 数据怎么算、怎么滤波、怎么防丢信号4.1 频率与占空比的计算公式在1MHz计数频率下CCR1的值就是PWM周期的微秒数。比如被测信号是1kHzCCR1读数就是1000信号是10kHzCCR1读数就是100。频率就是1000000除以CCR1单位是Hz。占空比的计算更直接CCR2除以CCR1乘以100。如果CCR2是300CCR1是1000占空比就是30%。不过这里必须提醒一件事CCR1可能因为信号频率太低而溢出。16位定时器计数到65535就溢出了对于1MHz计数频率可测的最大周期是65.535毫秒对应频率约15.26Hz。低于这个频率CNT还没等到上升沿就溢出归零了CCR1捕获到的就不是真实周期。TIM2和TIM5是32位定时器最大周期可以到4294秒常规PWM场景完全够用。所以如果你需要测低频信号优先选用32位定时器。预分频器的选择也要根据信号范围提前计算。假设被测信号最高频率是100kHz那么周期最小值是10us。为了至少能够分辨出10us内的上升沿和下降沿计数频率至少要达到信号频率的数倍以上。1MHz计数频率分辨1us对100kHz信号有10个计数点的余量精度尚可。如果信号频率更高比如1MHz那就必须把预分频调小甚至设为0让计数频率达到72MHz这样每个周期至少有72个计数点才能保证占空比的精度。4.2 数据滤波策略中值滤波和去极值平均PWM输入模式虽然硬件对齐做得很好但实测数据不会永远平滑。信号源的抖动、外部干扰、电源噪声都会让连续几次捕获的CCR1和CCR2存在小范围波动。如果你直接把这些数据用于控制比如根据占空比调整电机转速会发现转速有明显抖动。常见的做法是软件滤波。我最常用的是一级中值滤波加滑动平均具体思路是连续采集5组数据去掉最大值和最小值对剩下的3组求平均。这个组合既能滤掉偶发的毛刺又不会让响应变得太迟钝。#define FILTER_SAMPLE_NUM 5 uint32_t g_auiPeriodBuffer[FILTER_SAMPLE_NUM]; uint32_t g_auiDutyBuffer[FILTER_SAMPLE_NUM]; uint8_t g_ucBufferIndex 0; void Filter_AddSample(uint32_t uiPeriod, uint32_t uiDutyValue) { g_auiPeriodBuffer[g_ucBufferIndex] uiPeriod; g_auiDutyBuffer[g_ucBufferIndex] uiDutyValue; g_ucBufferIndex; if (g_ucBufferIndex FILTER_SAMPLE_NUM) { g_ucBufferIndex 0; } } uint32_t Filter_GetAverage(uint32_t *pBuffer) { uint32_t uiMin 0xFFFFFFFF; uint32_t uiMax 0; uint32_t uiSum 0; uint8_t i; for (i 0; i FILTER_SAMPLE_NUM; i) { if (pBuffer[i] uiMin) uiMin pBuffer[i]; if (pBuffer[i] uiMax) uiMax pBuffer[i]; uiSum pBuffer[i]; } uiSum - uiMin; uiSum - uiMax; return uiSum / (FILTER_SAMPLE_NUM - 2); }这个滤波函数很简单但有一个前提信号本身的频率变化不能太快。如果你测的是一个正在快速扫频的信号比如从1kHz扫到10kHz中值滤波会把扫频过程中的中间值当成当前值输出造成滞后。这种场景下应该减少采样点数或者改用动态窗口。4.3 信号丢失检测独立看门狗计时器PWM输入模式有一个隐患如果信号突然断开从模式不再触发复位CNT继续自由运行CCR1和CCR2停留在旧值上程序会一直用最后一次的正确数据输出。这在很多应用场景是不可接受的。解决办法是利用一个独立的软件定时器或者硬件定时器做超时看门狗。这里不能用当前定时器的CNT来判断因为从模式复位会不断把CNT清零CNT的值永远不是真实流逝时间。我通常用SysTick作为基础时钟在每次CH1捕获中断里顺便把超时时间戳更新一下然后在主循环里检查当前时间和最后一次捕获时间戳的差值。volatile uint32_t g_uiLastCaptureTick 0; void SysTick_Handler(void) { g_uiTickCount; } void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) { if (htim-Instance TIM2 htim-Channel HAL_TIM_ACTIVE_CHANNEL_1) { g_uiLastCaptureTick g_uiTickCount; // 读取CCR1和CCR2... } } // 在主循环中检查信号是否丢失 if (g_uiTickCount - g_uiLastCaptureTick 500) // 500ms没有捕获事件 { // 信号丢失处理异常逻辑 }这里的判断条件是500ms内没有捕获事件。如果被测信号本身频率很低比如周期是200ms那500ms超时时间设置得还不够合理需要根据最低信号频率动态调整。一个更稳妥的做法是从CCR1当前值估算出信号周期把超时时间设成当前周期的5到10倍。5. 实测踩坑记录与排查思路5.1 踩坑一GPIO配成复用推挽输出读数始终为零这是我见过最多的问题症状非常明确程序下载进去后CCR1和CCR2始终读不到有效数据偶尔能读到一个固定值但不管怎么调整被测信号频率数据都不变。排查链路是这样的先用逻辑分析仪或者示波器确认被测信号本身是正常的排除信号源问题。接着检查GPIO配置发现MX_GPIO_Init里把TIM2_CH1对应的引脚配置成了GPIO_MODE_AF_PP。在HAL库中这种模式代表复用推挽输出引脚是作为输出来工作的。输入捕获的引脚实际上是输入应该配置成GPIO_MODE_AF_IN或者GPIO_MODE_INPUT。修复方法就是改GPIO模式。值得多说一句的是为什么例程里配成AF_PP也能跑原因在于某些开发板的例程使用了内部回环测试功能或者信号源驱动能力很强信号通过引脚内部的保护二极管等方式耦合了进来。这种情况在个别板子上能工作但极不稳定换一块板子就完蛋。我建议所有做输入捕获的新手第一件事就检查GPIO模式。5.2 踩坑二CH2极性或映射配错周期和占空比互换第二个常见问题更隐蔽症状是读数也有频率也对但占空比不对。有一种典型表现是占空比显示为100%减去实际值比如实际占空比30%读出来70%。这种情况基本可以确定CH2的极性或者映射配置和CH1反了。如果通道2的ICSelection配置成了DIRECTTI而通道1配置成了INDIRECTTI那么周期值和高电平时间值就会交换计算结果彻底错误。还有一种情况是CH2的ICSelection配对了但ICPolarity配成了上升沿本来该在下降沿捕获结果也在上升沿捕获那么CCR2的值会接近CCR1占空比读出来接近100%。排查方法很简单用一个已知占空比的信号源设置成50%输出读一下CCR1和CCR2。如果两者接近相等说明极性正确如果CCR2比CCR1大或者CCR2非常接近CCR1多半是极性或者映射关系配错了。再设置成25%占空比如果CCR2明显小于CCR1且比例关系正确说明配置恢复正常。5.3 踩坑三16位定时器测低频信号频率显示乱跳我一开始用TIM3测一个10Hz的PWM信号结果频率值在15Hz左右乱跳毫无规律。当时第一反应是滤波没做好加了重滤波后发现数据更乱了这才意识到不是噪声问题。后来查了数据手册TIM3是16位定时器最大计数65535。在1MHz计数频率下10Hz信号的周期是100000usCNT还没跑到100000就已经溢出了CNT从零重新开始。上升沿到来时CCR1捕获的值可能是65000减去某个中间值也可能是几百完全取决于CNT溢出了多少次以及溢出发生后过了多久。这个数据当然没有任何意义。解决方案要么换用TIM2或者TIM5这种32位定时器要么在16位定时器上配合更新中断做计数扩展。计数扩展的思路是在更新中断里把溢出次数记录下来真实周期等于溢出次数乘以65535再加上CCR1的值。但这样还要额外处理一个问题从模式复位会被硬件的边沿触发打断软件需要判断溢出中断发生在哪个周期内逻辑复杂度上升不少。我的建议是测低频信号优先选用32位定时器省心得多。5.4 踩坑四占空比接近0%或100%时数值跳变用PWM输入模式测接近极限的占空比也会遇到特殊问题。信号占空比接近0%意味着下降沿几乎紧接着上升沿CCR2可能只捕获到一个很小的值甚至在某些时钟相位下捕获到0。由于信号本身存在抖动每次捕获的CCR2可能时有时无占空比就在0%和几个百分点之间跳动。占空比接近100%也有类似问题下降沿几乎挨着下一个上升沿此时CCR2接近CCR1但两个边沿之间的时间太短如果计数频率不够高CCR2和CCR1的差值就只有几个tick占空比在99%和100%之间跳动绝对误差虽然小但百分比误差很大。处理办法要分两个方向。一是从硬件上提高分辨率把预分频调小让计数频率尽可能高把边沿之间的微小时间差用更多tick来量化。二是从软件上增加滞回区域当CCR2小于某个阈值时直接判定占空比为0%当CCR2大于CCR1减去某个阈值时直接判定为100%。这样虽然牺牲了一点点精度但避免了数据跳变带来的控制不稳。6. 应用扩展多路信号测量与触发ADC联动6.1 用一个定时器测一个信号还是多个定时器并行PWM输入模式占用了同一个定时器的两个通道所以一个定时器只能测一路完整信号。如果你的系统里有多路PWM需要同时测量比如两个电机编码器的PWM输出或者两路遥控接收信号就需要用到多个定时器。STM32F1系列里TIM1、TIM2、TIM3、TIM4、TIM5都支持PWM输入模式理论上可以同时测5路信号。每路信号的代码逻辑完全一样只需要将Handle结构体换成对应的定时器中断回调函数里根据htim-Instance区分即可。void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) { if (htim-Instance TIM2) { g_uiPeriodTim2 HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1); g_uiDutyTim2 HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_2); } else if (htim-Instance TIM3) { g_uiPeriodTim3 HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1); g_uiDutyTim3 HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_2); } }多路并行时要注意中断优先级的分配。如果两路信号频率都很高中断频繁触发可能导致其中一个回调持续被另一个打断。常规做法是给快速信号对应的定时器分配更高优先级或者改用DMA方式读取捕获寄存器进一步降低CPU占用。6.2 主模式TRGO触发ADC把PWM测量和采样同步起来前面提到从模式是核心但不代表主模式没有用武之地。在电机控制、电源管理等需要边测PWM边采样的场景里主模式TRGO可以把PWM周期和ADC采样精确同步起来。思路是这样的定时器配置成PWM输入模式从模式负责复位计数器和捕获主模式配置为Update事件触发TRGO。TRGO连接到ADC的触发源这样每个PWM周期开始时硬件自动触发一次ADC采样。软件在ADC完成中断里读取采样值同时读取CCR1和CCR2就得到了同一次PWM周期内的占空比信息和模拟量信息。这种同步是纯硬件完成的比软件估算相位准确得多。很多人把这个功能做成触发器联动本质都是一样的利用PWM输入模式下的定时器更新事件作为其他外设的同步基准。需要注意的一点是从模式复位会让CNT频繁清零定时器更新事件也因此每个周期都会产生一次所以TRGO的触发频率就等于PWM信号的频率。如果PWM频率过高导致ADC触发太频繁可以在ADC端配置触发信号的分频或等待机制来限制采样率。6.3 把PWM输入模式做成一个通用测量组件当你把单路测量跑通以后一个很自然的想法是能不能把这段逻辑抽象成一个通用组件以后在其他项目里直接调用我在实际工程里通常是这样封装的定义一个结构体保存定时器句柄、预分频值、最新周期、最新占空比、数据有效标志、信号丢失标志然后提供三个接口函数分别是初始化、读取最新数据、配置滤波参数。这样上层业务完全不关心底层是哪个定时器、定时器时钟是多少只需要拿结果使用即可。这种封装的另一个好处是方便做单元测试。把一个信号发生器接到不同定时器引脚上切换不同频率和占空比只要接口输出的数据准确就说明组件工作正常。我建议你在自己的项目里也这样尝试把PWM输入捕获从一个能跑的例程升级成一个能复用的模块。最后再分享一个小技巧如果你手里没有可调PWM信号源直接用另一个定时器输出PWM把两个定时器的引脚短接起来就能实现自己测自己的闭环验证。先用这种方式确认配置正确再去测外部信号可以少走很多弯路。

相关新闻

带式输送机托辊声学故障诊断:从信号特征到机器学习实战

带式输送机托辊声学故障诊断:从信号特征到机器学习实战

带式输送机托辊声学故障诊断这件事,我在现场投入过不少精力。这套方法的核心思路并不复杂:托辊在旋转过程中,轴承滚道、保持架、辊皮一旦出现损伤,会产生区别于正常运行的特征声学信号,用麦克风采集声音,再…

2026/10/19 5:26:20 阅读更多 →
YOLOv8电梯电瓶车实时检测系统:3分钟启动报警GUI

YOLOv8电梯电瓶车实时检测系统:3分钟启动报警GUI

简介:本资源是一套基于YOLOv8的电梯场景电瓶车闯入智能识别与报警系统,面向计算机、人工智能、自动化等专业在校学生及初学者,解决公共场所安全监管中对违规载电动车入梯行为的实时检测与预警需求,特别适合作为毕业设计、课程设计…

2026/10/19 5:25:20 阅读更多 →
AI工作流优化实战:让智能体不止于单点提效

AI工作流优化实战:让智能体不止于单点提效

这两年我帮好几个团队做过AI落地,发现一个特别普遍的现象:大家一提到AI提效,第一反应就是“上一个智能体”。买工具、调接口、套模板,把某个环节换成AI,然后期待奇迹发生。结果呢?局部确实快了,…

2026/10/19 5:25:20 阅读更多 →

最新新闻

BERT微调实战:IMDB影评情感二分类源码全流程解析与避坑指南

BERT微调实战:IMDB影评情感二分类源码全流程解析与避坑指南

简介:基于BERT模型的情感分析项目,是一套面向自然语言处理初学者与算法工程师的Python源码包,聚焦IMDB影评正面/负面情感二分类,帮助理解BERT预训练模型微调、特征抽取及分类器设计等关键环节。资源共5个文件,其中4个P…

2026/10/19 6:08:40 阅读更多 →
用Matlab完成测风塔风能资源评估:从数据清洗到发电量估算

用Matlab完成测风塔风能资源评估:从数据清洗到发电量估算

气象塔(风电行业里叫测风塔)出来的历史风力数据,看起来就是一堆时间和风速的表格,但真正能把它变成风能资源评估结论的人,并不算多。我刚接触这个领域的时候也犯过“拿数据就求平均”的错,结果被前辈按在工…

2026/10/19 6:08:40 阅读更多 →
单矢量控制下的占空比计算:有效抑制电流纹波与噪声

单矢量控制下的占空比计算:有效抑制电流纹波与噪声

做数字电机控制的人应该都经历过这种尴尬:算法层面跑得好好的,DTC或者有限集模型预测控制(FCS-MPC)每个控制周期都选了那个“最优电压矢量”,上示波器一看,电流纹波比SVPWM大一块,高频噪声还特别…

2026/10/19 6:08:40 阅读更多 →
AUV滑模控制器设计与Simulink仿真:从动力学建模到参数整定全解析

AUV滑模控制器设计与Simulink仿真:从动力学建模到参数整定全解析

1. AUV运动控制的核心难点与滑模控制的切入点自主水下机器人(AUV)的控制一直是个让人又爱又恨的问题。先说结论:在水下环境里,模型不确定性大、海流干扰强、参数摄动严重,传统的PID想同时搞定“深度保持”“航向稳定”…

2026/10/19 6:08:40 阅读更多 →
macOS终端重构:iTerm2+zsh打造可编程工作台

macOS终端重构:iTerm2+zsh打造可编程工作台

简介:本资源是一套面向 macOS 用户的终端环境深度优化配置方案,专为希望提升开发效率、定制化体验的程序员与系统爱好者设计,解决原生 Terminal 功能单一、主题简陋、插件缺失等痛点。压缩包包含 4489 个文件,主体为 1050 个 zsh …

2026/10/19 6:08:40 阅读更多 →
论文查重与AI率检测全攻略:原理、工具与降重技巧

论文查重与AI率检测全攻略:原理、工具与降重技巧

每年到了毕业论文提交季,最让人焦虑的往往不是论文本身有多难写,而是“查重”那一关。更麻烦的是,近两年几乎所有高校都多了一条新指标:AI生成内容检测。也就是说,论文不仅要控制重复率,还要控制“AI率”。…

2026/10/19 6:07:40 阅读更多 →

日新闻

频率跟踪型PWM电源设计:臭氧发生器谐振点漂移与软开关实现

频率跟踪型PWM电源设计:臭氧发生器谐振点漂移与软开关实现

简介:本资源为电源技术方向的学术研究文档,聚焦介质阻挡放电型臭氧发生器电源的频率跟踪型PWM控制策略,面向电力电子、高压电源及臭氧应用领域的科研人员与工程技术人员。针对负载呈容性、等效电容随外加电压升高而增大、谐振频率随之漂移的难…

2026/10/19 0:00:47 阅读更多 →
Vibe Coding 遇上 TaoToken:把开发者思维圣殿的灵感流接入统一 Key 通道

Vibe Coding 遇上 TaoToken:把开发者思维圣殿的灵感流接入统一 Key 通道

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/19 0:00:47 阅读更多 →
HundredCV-Chat话题建模实战教程:利用4,871个内置话题标签构建主题分析系统

HundredCV-Chat话题建模实战教程:利用4,871个内置话题标签构建主题分析系统

【免费下载链接】HundredCV-Chat 项目地址: https://ai.gitcode.com/hf_mirrors/Jax-dan/HundredCV-Chat 点击查看 免费下载 HundredCV-Chat 是一个由 100 位青年简历数据开发而来的中文多轮对话数据集,共包含 24,750 组日常闲聊对话和 4,871 个话题标签…

2026/10/19 0:00:47 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型(GMM)的语音识别系统,很多人第一反应就是一堆数学公式,觉得门槛很高。但如果你真的在MATLAB里把它落地跑通,会发现核心其实就是三件事:把声音变成特征向量,用GMM给每个类别…

2026/10/18 0:00:55 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人,但落到具体操作上,无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程:对图像做反白、改调色板、彩色转灰度,最后把像素数据导出成txt文件。单看每一…

2026/10/18 0:00:55 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词,你会发现一个很有意思的现象:每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型,有人指的是运动控制系统,还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/19 1:30:52 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/19 2:54:50 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/17 17:22:31 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/18 12:16:16 阅读更多 →