PPM解码与随机PWM生成:STM32实现EMI降噪的完整方案
简介资源包为基于MATLAB的PWM整流器建模仿真脚本面向电力电子、通信及信号处理方向的初学者与进阶者聚焦随机PWM与脉冲位置调制PPM在随机调制信号下的联合仿真并引入独立分量分析ICA用于仿真信号的分离与图像处理场景验证。包体仅含1个m文件共5KB代码精炼且注释完整便于直接阅读与修改适合理解PWM整流器控制策略、随机PWM降低电磁干扰的原理以及PPM信息编码方式整体简洁适合科研入门与课程设计复用。脚本内置随机调制信号生成与ICA解混模块可辅助读者对照理论推导快速完成实验验证。已有210人学习下载。通过运行该脚本可观察PPM脉冲位置变化规律并为后续开展电力电子系统优化或图像去噪研究提供可复用的MATLAB基础代码。1. PPM 输入配上随机 PWM 输出是航模和机器人里最常见的信号链改造一个接收机 PPM 信号进来解出通道值再转成 PWM 去驱动电机或灯这条链路在航模、车模、机器人地盘项目里很常见。但直接映射成固定频率 PWM 的项目整机调试时大多吃亏频谱能量集中在载频和谐波上EMI 测试尖峰显眼电机低速还有尖锐啸叫。把 PWM 周期做随机化扰动后频谱能量摊开到一段连续带宽里尖峰降下来听感也柔和很多。meijie_v46.zip 这个名字同时出现 PPM 和随机 PWM要解决的正是输入解析和输出降噪两件事。下面按先解 PPM、再生成随机 PWM、最后接起来验证的顺序展开参数、代码和示波器判据都写清楚适合自己写电调或 LED 驱动固件的嵌入式工程师。2. 先解 PPM帧结构、上升沿间隔与脉冲提取PPM 全称 Pulse Position Modulation航模接收机把多个通道按时间顺序塞进一个 20ms 帧里从同一个引脚输出。PPM 信号是后续随机 PWM 输入来源解不出来后面全是空谈所以先把帧结构和解码代码定死。2.1 别把 PPM 当多路 PWM 读帧、同步间隙与两种编码约定PWM 的一路信号只表达一个通道的占空比PPM 则是单线时分复用。绝大多数航模 PPM 输出约定脉冲本身宽度固定约 300us400us通道值体现在相邻上升沿的间隔上间隔 1.0ms2.0ms 对应行程 0%100%帧与帧之间用一个更长的低电平时隙隔开称为同步间隙通常 2ms 以上。也存在另一类实现高电平脉宽 1ms2ms 可变、通道间低电平固定。两类约定用上升沿间隔测量都能覆盖只是同步阈值不同拿到接收机先抓波形确认是哪一种。参数典型值说明帧周期20ms50Hz部分接收机 22.5ms脉冲宽度300us500us固定另一类约定为 1ms2ms 可变通道上升沿间隔1.0ms2.0ms对应行程两端同步间隙≥ 2ms常见 2.5ms4ms上升沿间隔超过该值表示帧结束帧内通道数412由发射机通道设置决定提示解码阈值不要卡在 2ms 临界值。固定脉宽型 PPM 的同步阈值取 2.5ms脉宽可变型的通道间隔上限到 2.5ms同步阈值要提到 2.7ms3ms留出裕量。2.2 定时器输入捕获解 PPM 的最小代码1us 分辨率常见做法是拿一个定时器做上升沿捕获相邻两次捕获值相减得到间隔同时用定时器的预分频把计数频率压到 1MHz让计数差值直接等于微秒数。这样中断里只做减法和比较开销极小。/* TIM2 预分频 7172MHz / 72 1MHz1 tick 1us */ #define PPM_SYNC_TICKS 2500u /* 上升沿间隔大于 2.5ms 判定为同步 */ #define PPM_MIN_TICKS 700u /* 小于 0.7ms 视为毛刺丢弃 */ #define PPM_MAX_US 2000u /* 大于 2ms 且小于同步阈值的非法值 */ #define PPM_MAX_CH 8 volatile uint16_t ppm_raw[PPM_MAX_CH]; volatile uint8_t ppm_ch_cnt; volatile uint8_t ppm_frame_ready; static uint16_t last_rise; void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) { if (htim-Instance ! TIM2) return; if (htim-Channel ! HAL_TIM_ACTIVE_CHANNEL_1) return; uint16_t now HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1); uint16_t interval now - last_rise; /* 无符号减法自动处理回绕 */ last_rise now; if (interval PPM_SYNC_TICKS) { /* 同步间隙一帧结束置标志交给主循环 */ ppm_frame_ready 1; ppm_ch_cnt 0; } else if (interval PPM_MIN_TICKS interval PPM_MAX_US ppm_ch_cnt PPM_MAX_CH) { ppm_raw[ppm_ch_cnt] interval; } }这段逻辑的关键在now - last_rise。16 位计数器回绕时无符号减法仍能得到正确差值前提是两次捕获间隔小于 65535usPPM 帧周期 20ms 远小于这个值安全。注意帧开始的第一个上升沿其实是被同步判定消费掉的上一次边沿到该边沿的间隔等于上一帧最后一个通道加同步低电平必然超过阈值所以每一帧的第一个通道从下一次捕获开始计数不会丢通道。定时器和引脚配置如下htim2.Init.Prescaler 71; /* 计数频率 1MHz */ htim2.Init.Period 0xFFFF; /* 自由运行不自动重装 */ HAL_TIM_IC_Init(htim2); TIM_IC_InitTypeDef sICConfig {0}; sICConfig.ICPolarity TIM_INPUTCHANNELPOLARITY_RISING; sICConfig.ICSelection TIM_ICSELECTION_DIRECTTI; sICConfig.ICPrescaler TIM_ICPSC_DIV1; sICConfig.ICFilter 0x0F; /* 数字滤波去掉 1us 以下毛刺 */ HAL_TIM_IC_ConfigChannel(htim2, sICConfig, TIM_CHANNEL_1); HAL_TIM_IC_Start_IT(htim2, TIM_CHANNEL_1);ICFilter 开在 0x0F 附近能滤掉接收机输出上的窄毛刺代价是引入 1us 级捕获延迟对 2ms 级测量误差可忽略。PPM 输入引脚必须配置为复用功能推挽或带上拉输入浮空输入在信号线悬空时会捕获到随机边沿通道数会乱跳。2.3 解码的三个坑极性、帧超时与通道数漂移极性反了是最容易踩的坑。部分接收机输出负逻辑 PPM高电平是间隔、低电平是脉宽。示波器上看波形是好的但解码出来全是同步或全是毛刺。处理办法不是改代码逻辑而是把捕获边沿从上升沿改成下降沿其余不动。帧超时要用主循环兜底。接收机关机或信号线断开时中断不再触发ppm_frame_ready永远不置位上一帧数据会被 PWM 一直使用这在电机驱动场景里是安全事故。主循环里比对HAL_GetTick()与上次完整帧时间戳超过 100ms 无新帧就进故障保护。通道数漂移来自发射机设置与固件假设不一致。发射机设 6 通道但开了混控帧里可能实际塞了 8 个脉冲。稳妥做法是解码同时记录ppm_ch_cnt与期望通道数比对不一致就丢弃整帧连续两帧一致才更新输出。3. 随机 PWM 降 EMI 的原理与 LFSR 实现PPM 解出来后输出端选择随机 PWM 而不是固定 PWM核心动机是频谱整形。这一章讲清楚随机化到底随机了谁以及怎么在 MCU 上用最少的资源生成可靠的随机数。3.1 固定频率 PWM 的频谱尖峰为什么难搞固定频率 PWM 波形是周期信号频谱是离散谱线载频 f0 处一条大谱线2f0、3f0 等整数倍处若干衰减谐波。做传导发射测试时限值曲线是随频率上升而下降的包络离散谱线最容易顶到包络线造成超标。对付单根谱线传统手段是加滤波电感、磁珠、改布局成本都不低。随机 PWM 让每个周期的开关频率在一个范围内随机变化波形失去严格周期性频谱变成连续谱原来集中在 f0 的能量被摊到一整段频率里。实测中峰值能量通常能降 10dB 以上代价是开关频率时刻在变输出纹波瞬时值跟着变电机负载会有轻微转矩脉动但对多数应用来说 EMI 收益更划算。3.2 随机频率、随机相位与随机占空比三种随机化怎么选叫随机 PWM的常见实现有三种工程上常被混为一谈随机频率 PWM每个周期随机改变定时器 ARR开关频率在基准值附近抖动。实现最简单一个 LFSR 加一次重载即可直接摊开频谱是电机应用的首选。随机相位 PWM频率不变每个周期在 0T 内随机平移脉冲起始位置。也能摊开频谱但需要定时器支持比较通道重映射多数 MCU 实现繁琐收益不比随机频率大。随机占空比 PWM这个名字本身有歧义。占空比是输出平均值直接随机改输出会引入控制噪声实际指在平均占空比不变的前提下随机改脉冲位置本质是相位随机化。随机化方式随机对象实现难度适用场景随机频率ARR 周期低电机驱动、电源 EMI随机相位脉冲位置中LED 调光随机占空比等价相位随机中很少单独使用工程上先做随机频率。占空比计算公式要始终保持闭环duty CCR / (ARR 1)改 ARR 的同时必须按新周期重算 CCR否则占空比会跟着频率抖负载平均电压不稳。3.3 用 LFSR 生成硬件友好型随机数随机源有硬件 RNG、伪随机序列和查表三种。多数项目用伪随机够用但种子要固定、周期要够长、不能输出规律明显的序列。线性反馈移位寄存器LFSR最合适几个异或门就能实现几乎不占内核时间周期可达 65535。/* 16bit Fibonacci LFSR本原多项式 x^16 x^14 x^13 x^11 1 */ static uint16_t lfsr_state 0xACE1u; /* 种子不能为 0 */ uint16_t lfsr_next(void) { uint8_t bit; /* 反馈位来自第 15、13、12、10 位0 起始的异或 */ bit (uint8_t)(((lfsr_state 15) ^ (lfsr_state 13) ^ (lfsr_state 12) ^ (lfsr_state 10)) 1u); lfsr_state (uint16_t)((lfsr_state 1) | bit); return lfsr_state; } /* 把 LFSR 输出无偏映射到 -PCT ~ PCT 的扰动百分比 */ int32_t get_dither_percent(uint8_t pct) { uint32_t r lfsr_next(); return (int32_t)((r * (uint32_t)(pct * 2)) / 65536u) - (int32_t)pct; }反馈位的选取决定序列周期。x^16 x^14 x^13 x^11 1 是 16 位 LFSR 常见本原多项式保证 65535 周期不锁死短循环。种子为 0 时整个序列永远停在 0初始化时务必给非零值。取扰动量时不要用lfsr_next() % N取模低字节随机性差扰动范围小时会连续同向扰动频谱上残留可辨识谱线。先乘后除的无偏映射能避免这个问题。扰动百分比一般取 515取 5 频谱摊开效果有限取 15 以上电机转矩脉动开始明显LED 场景则可能出现可觉察亮度波动。4. 从 PPM 通道值到随机 PWM 输出完整例程两端都齐了这一章接成完整链路。选型用 72MHz 的 STM32F103TIM2 做 PPM 上升沿捕获TIM3 做随机 PWM 输出主循环负责通道映射与故障保护。4.1 系统结构与参数设计信号流向接收机 PPM 输出接 TIM2_CH1捕获中断把通道间隔存进 ppm_raw主循环检测到 ppm_frame_ready 后拷贝通道值按行程映射出目标占空比占空比换算成 TIM3 的 CCRTIM3 的更新中断里每个周期调用 lfsr_next() 重算 ARR 和 CCR实现随机频率输出。参数设计定三件事PPM 用哪个通道、通道行程范围、PWM 基准频率与扰动百分比。以第二通道油门、脉宽 1000us2000us、输出 20kHz 随机 PWM为例参数值依据PPM 输入通道CH2油门航模惯例CH1 舵机、CH2 油门脉宽范围1000us2000us接收机默认行程PWM 基准频率20kHz高于人耳听阈电机无啸叫随机扰动±8%频谱摊开与转矩脉动的折中故障保护延时100ms大于 20ms 帧周期的 5 倍提示基准频率要落到 PWM 频率对电机的影响上。低于 15kHz 会出现可听噪音和更大电流纹波高于 40kHz 开关损耗明显上升。20kHz 是多数电调起点±8% 扰动后实际频率 18.4kHz21.6kHz最低点仍高于听阈。4.2 周期随机化与占空比更新的协同核心难点在 ARR 和 CCR 的更新时序。两者是乘除关系先改 CCR 再改 ARR新周期内 CCR 没跟上瞬时占空比跳变。正确顺序先更新 ARR再用新 ARR 反算 CCR。#define TIM_CLK_HZ 72000000u #define PWM_BASE_FREQ_HZ 20000u #define PWM_BASE_ARR (TIM_CLK_HZ / PWM_BASE_FREQ_HZ - 1u) /* 3599 */ #define PWM_MIN_ARR (TIM_CLK_HZ / 18000u - 1u) /* 频率下限 18kHz */ static volatile uint32_t duty_x10 0; /* 0~1000对应 0.0%~100.0% */ /* TIM3 更新中断每周期随机化频率并重算比较值 */ void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if (htim-Instance ! TIM3) return; int32_t dither get_dither_percent(8); uint32_t arr; /* ARR 越大频率越低这里直接钳位到最低频率对应的 ARR */ arr PWM_BASE_ARR (uint32_t)((uint32_t)PWM_BASE_ARR * (uint32_t)dither / 100); if (arr PWM_MIN_ARR) arr PWM_MIN_ARR; /* 先改 ARR再按新周期重算 CCR占空比保持稳定 */ __HAL_TIM_SET_AUTORELOAD(htim, arr); __HAL_TIM_SET_COMPARE(htim, TIM_CHANNEL_1, (uint32_t)((arr 1u) * duty_x10 / 1000u)); }duty_x10单位是千分之一精度 0.1%对电机调速和 LED 呼吸灯都够用。注意我们正处在更新中断里此刻写入 ARR 和 CCR生效于下一周期——这正是想要的当前周期已经按旧周期走完从下个周期开始用新频率。ARR 和 CCR 都有影子寄存器必须开启预装载否则 ARR 修改要拖到下下周期才生效随机化节奏慢半拍。TIM3 初始化时置AutoReloadPreload TIM_AUTORELOAD_PRELOAD_ENABLE启动时用HAL_TIM_PWM_Start()配合HAL_TIM_Base_Start_IT()使能更新中断。htim3.Init.Period PWM_BASE_ARR; htim3.Init.AutoReloadPreload TIM_AUTORELOAD_PRELOAD_ENABLE; HAL_TIM_PWM_Init(htim3); HAL_TIM_PWM_Start(htim3, TIM_CHANNEL_1); HAL_TIM_Base_Start_IT(htim3); /* 使能更新中断 */4.3 通道映射、油门死区与故障保护主循环把 PPM 脉宽映射成占空比。油门有个特殊要求摇杆机械回中误差会导致零油门时输出不为 0直接映射会造成电机低速爬行。常见做法是引入死区低于阈值直接输出 0。/* 1000us - 0%2000us - 100%底部 3% 行程视为零 */ uint32_t ppm_map_throttle(uint16_t pulse_us) { uint32_t pct_x10; if (pulse_us 1000u) pulse_us 1000u; if (pulse_us 2000u) pulse_us 2000u; pct_x10 (uint32_t)(pulse_us - 1000u); /* 0~1000 */ if (pct_x10 30u) pct_x10 0; /* 死区 3% */ return pct_x10; } /* 主循环消费 PPM 帧更新占空比检查帧超时 */ while (1) { if (ppm_frame_ready) { ppm_frame_ready 0; /* 通道数校验至少 2 通道防止开机瞬间半帧数据 */ if (ppm_ch_cnt 2) { duty_x10 ppm_map_throttle(ppm_raw[1]); /* 通道 2 */ ppm_last_frame_ms HAL_GetTick(); } } /* 帧超时 100ms接收机信号丢失进入故障保护 */ if (HAL_GetTick() - ppm_last_frame_ms 100u) { duty_x10 0; } }故障保护只把油门清零还不够如果系统带着舵机方向通道应锁到最后有效值或安全值避免信号丢失瞬间舵面猛打。ppm_last_frame_ms只允许在主循环消费完整帧时更新捕获中断里不要碰它原因在最后一章排错部分展开。4.4 舵机、BLDC 与 LED 三种负载的参数选择随机 PWM 不是万能的不同负载的容忍度差别很大选错比不随机更糟。负载基准频率随机策略占空比公式关键约束航模舵机50Hz 固定不随机duty% pulse_us / 20ms脉宽抖动超 5us 舵机会抖舵直流有刷电机1620kHz频率 ±5%±15%duty CCR/(ARR1)最低频率不低于 15kHzBLDC 三相20kHz频率 ±5%六路互补中心对齐死区大于随机化造成的最小有效脉宽RGB LED14kHz相位随机优先按颜色通道独立 CCR低占空比时上提频率防闪烁舵机最容易做错。舵机输入本质还是 PWM内部有位置环脉宽抖动会直接变成舵臂抖动所以即使整机在做随机 PWM 降 EMI舵机输出也必须独立走固定 50Hz PWM。LED 正好相反它对频率不敏感但对亮度一致性敏感相位随机比频率随机更合适——固定频率下随机移动脉冲位置等效亮度不变不会产生可感知闪烁。5. PPM 与随机 PWM 的验证排错示波器判据、频谱与死区代码写完不等于随机 PWM 生效这一章把每个环节的验证方法量化到示波器和参数上。5.1 示波器验证 PPM 解码的四个测量点探头接 PPM 输出和 GND上升沿触发时基 5ms/div。第一眼看四件事帧周期是否 20ms 左右、同步间隙是否 2ms 以上、通道脉冲是否落在 1ms2ms、通道间隔是否约 300us。用光标量通道间隔和同步间隙与 2.1 节参数表对照。解码程序是否正确最直接的办法是 UART 把 ppm_raw 值打出来。串口助手显示的值应是 10002000 之间的整数缓慢拨动摇杆对应通道值平滑变化无跳变。出现跳变优先查 ICFilter 配置和 PPM_MIN_TICKS多半是毛刺没滤干净。5.2 验证随机 PWM 确实摊开了频谱探头移到 TIM3_CH1 输出把示波器时基调到 20us/div 左右逐个测量脉冲周期。正常情况下周期应在基准周期 50us 的 ±8% 范围内变动如果周期恒定说明 LFSR 没跑起来多数是种子为 0 或更新中断没进。带 FFT 功能的数字示波器可以做前后对比固定频率模式在 20kHz 处有明显谱线切换到随机模式该谱线应下降 10dB 左右换来 18kHz22kHz 的一段连续能量平台。没有 FFT 就用余辉模式随机模式波形边界明显变宽说明周期持续变化。5.3 死区、最小脉宽与故障保护的参数检查针对 H 桥和 BLDC 场景。随机频率 PWM 下 ARR 每次在变CCR 逼近 ARR 时会出现极窄脉冲窄到小于死区时间就会被互补输出逻辑吞掉造成波形缺失和转矩突变。解决方法是钳位最小脉宽/* 在 PWM 更新中断追加最小脉宽钳位 */ if (duty_x10 50u) { /* 小于 5% 直接拉零 */ __HAL_TIM_SET_COMPARE(htim, TIM_CHANNEL_1, 0); } else if (duty_x10 950u) { /* 大于 95% 直接拉满 */ __HAL_TIM_SET_COMPARE(htim, TIM_CHANNEL_1, __HAL_TIM_GET_AUTORELOAD(htim) 1u); }死区时间本身在互补输出配置里单独设置典型 0.5us2us取决于功率管关断延迟。随机扰动百分比越大最小有效脉宽越小死区就要选小这是 BLDC 场景随机化只能取 5% 左右的原因。最后验证故障保护拔掉接收机信号线观察 100ms 内 PWM 输出是否归零。归零慢了就查ppm_last_frame_ms是否只在主循环消费完整帧时更新——如果在捕获中断里顺手写了时间戳每次脉冲都会刷新它帧超时永远不会触发拔线后输出不会归零。本文还有配套的精品资源点击获取