1. PWM信号测量实战从原理到代码落地上次我们聊完了输入捕获的基础原理这次直接上手PWM信号测量。先别急着写代码咱们得把测量逻辑理清楚。PWM信号有两大核心参数频率和占空比。频率好理解就是信号一秒钟震荡多少次占空比则是高电平时间占整个周期的百分比。测量PWM时有个经典问题单通道测量就像独臂侠干活既要在上升沿记录时间又要在下降沿抓取数据手忙脚乱还容易出错。我在做无人机电调调试时就吃过这个亏——单通道测量占空比误差能达到5%以上。后来改用双通道协同方案精度直接提升到0.1%级别。具体怎么操作TIM3的CH1和CH2就是黄金搭档。CH1配置为上升沿触发CH2配置为下降沿触发。当信号上升沿到来时CH1的CCR1寄存器会锁存当前计数器值同时触发中断下降沿时CH2的CCR2寄存器同样操作。这两个时间差就是高电平持续时间而相邻两次上升沿的时间差就是整个周期。// 关键配置代码片段 TIM_ICInitTypeDef icConfig; icConfig.TIM_Channel TIM_Channel_1; icConfig.TIM_ICPolarity TIM_ICPolarity_Rising; // 通道1捕获上升沿 icConfig.TIM_ICSelection TIM_ICSelection_DirectTI; TIM_ICInit(TIM3, icConfig); icConfig.TIM_Channel TIM_Channel_2; icConfig.TIM_ICPolarity TIM_ICPolarity_Falling; // 通道2捕获下降沿 TIM_ICInit(TIM3, icConfig);实际调试时要注意三个坑点首先是滤波参数工业现场常有毛刺信号TIM_ICFilter参数建议设为0x4-0x7其次是时钟分频72MHz主频下PSC建议设为71即72分频这样每个计数周期正好1μs最后是中断优先级测量高频信号时要确保捕获中断能抢占其他任务。2. 双通道协同的进阶玩法你以为双通道只能测PWM那可就小看STM32定时器了。通过主从定时器联级可以实现更复杂的测量场景。比如用TIM3作为主定时器TIM4作为从定时器当TIM3捕获到上升沿时触发TIM4开始计数下降沿时停止TIM4。这种方案特别适合测量稀疏的脉冲信号。我在激光测距项目里就用过这招。被测信号可能几分钟才有一个脉冲但要求测量精度到100ns级别。单独用输入捕获会溢出而主从模式下的TIM4可以设置成32位计数器通过级联两个16位定时器完美解决这个问题。配置代码的关键在于触发源选择TIM_SelectInputTrigger(TIM3, TIM_TS_TI1FP1); // 使用TI1作为触发源 TIM_SelectSlaveMode(TIM4, TIM_SlaveMode_Gated); // TIM4工作在门控模式 TIM_SelectMasterSlaveMode(TIM3, TIM_MasterSlaveMode_Enable);硬件连接也有讲究信号源接在TIM3的CH1上TIM3的CH1输出连接到TIM4的ETR引脚。实测时发现个有趣现象——如果两个定时器时钟不同步测量结果会有系统性误差。后来在初始化代码里加入了同步机制TIM3-CR1 | TIM_CR1_CEN; while((TIM3-SR TIM_SR_UIF)0); // 等待TIM3第一次溢出 TIM4-CNT 0; TIM4-CR1 | TIM_CR1_CEN;3. 高精度测量的五大秘籍想要把测量误差控制在0.1%以内光靠标准库还不够得祭出寄存器级操作。这里分享五个实战技巧第一招动态调整预分频器。对于变化范围大的信号可以在中断里根据当前频率动态修改PSC值。比如检测到频率低于1kHz时把PSC从71改为719即720分频这样每个计数代表10μs提高低频段分辨率。第二招捕获中断优化。标准库的TIM_GetITStatus()有查询开销直接读SR寄存器能节省0.5μs响应时间if(TIM3-SR TIM_IT_CC1) { TIM3-SR ~TIM_IT_CC1; // 手动清除标志位 // 处理捕获数据 }第三招时钟校准。STM32内部RC振荡器可能有1%误差用外部信号源校准很重要。我的做法是用GPS的1PPS信号作为基准在初始化时自动校准uint32_t calib_count 0; while(测量误差 0.1%){ RCC-CFGR | RCC_CFGR_SW_HSE; // 切换到外部时钟 calib_count 测量结果; RCC-CFGR | RCC_CFGR_SW_HSI; // 切回内部时钟 RCC-CR | RCC_CR_HSITRIM | ((calib_count 0x1F) 3); }第四招DMA搬运捕获数据。高频信号测量时用DMA把CCR寄存器值搬运到内存缓冲区避免中断延迟影响。配置方法DMA_InitStructure.DMA_PeripheralBaseAddr (uint32_t)TIM3-CCR1; DMA_InitStructure.DMA_MemoryBaseAddr (uint32_t)capture_buffer; DMA_InitStructure.DMA_DIR DMA_DIR_PeripheralSRC; DMA_InitStructure.DMA_BufferSize 256; DMA_Init(DMA1_Channel5, DMA_InitStructure); TIM_DMACmd(TIM3, TIM_DMA_CC1, ENABLE);第五招温度补偿。芯片温度每升高10℃时钟漂移约0.1%。内置温度传感器查表补偿能解决这个问题ADC_RegularChannelConfig(ADC1, ADC_Channel_16, 1, ADC_SampleTime_239Cycles5); float temp (1.43 - ADC_GetConversionValue(ADC1)*3.3/4096) / 0.0043 25; uint32_t clock_error temp_coeff_table[(uint8_t)temp]; TIM3-PSC 71 clock_error;4. 异常处理与抗干扰设计实际工程中信号丢失、噪声干扰都是家常便饭。分享几个踩坑换来的经验案例1信号抖动问题。在电机控制项目中PWM信号在边沿处会出现2-3次抖动。解决方案是在输入滤波器TIM_ICInitStructure.TIM_ICFilter里设置合适的采样窗口。我的经验值是对于100kHz信号设置采样窗口为8个时钟周期即0.1μsTIM_ICInitStructure.TIM_ICFilter 0x7; // 8个时钟周期滤波案例2输入信号过载。当输入电压超过VDD时可能倒灌电流损坏IO口。在PCB设计时要在信号输入端串联100Ω电阻并联3.3V稳压管。软件上要添加过压检测if(GPIOA-IDR GPIO_Pin_6){ if(检测到持续高电平时间异常){ TIM_ICCmd(TIM3, TIM_Channel_1, DISABLE); // 触发保护机制 } }案例3电磁干扰。工业现场遇到的50Hz工频干扰会导致测量值周期性波动。硬件上加磁环软件上采用数字滤波算法。我常用的移动平均滤波实现#define FILTER_DEPTH 8 uint32_t filter_buf[FILTER_DEPTH]; uint32_t moving_average(uint32_t new_val) { static uint8_t index 0; filter_buf[index] new_val; if(index FILTER_DEPTH) index 0; uint64_t sum 0; for(uint8_t i0; iFILTER_DEPTH; i){ sum filter_buf[i]; } return (uint32_t)(sum/FILTER_DEPTH); }最后提醒大家测量超低频信号1Hz时要特别注意定时器溢出问题。我的解决方案是结合定时器溢出中断进行计数void TIM3_IRQHandler(void) { if(TIM3-SR TIM_IT_Update) { overflow_count; TIM3-SR ~TIM_IT_Update; } // 处理捕获中断... }测量结果计算时要把溢出次数考虑进去uint64_t real_count (uint64_t)overflow_count * 65536 TIM_GetCapture1(TIM3);