
1. 项目概述PwmIn是一个面向嵌入式系统的轻量级 PWM 输入信号解析库核心目标是在资源受限的微控制器上以高精度、低开销方式捕获外部 PWM 信号的周期Period和脉宽Pulse Width。该库由 Simon Ford 最初设计实现其本质并非直接操作定时器输入捕获通道如 STM32 的 TIMx_CHy IC而是基于InterruptIn抽象层构建——即通过 GPIO 引脚的边沿触发中断在软件中完成时间戳记录与状态机判别最终计算出微秒级μs精度的周期与脉宽值。这一设计路径具有鲜明的工程取舍特征它牺牲了硬件定时器输入捕获所具备的亚微秒级抖动抑制能力但换来了极强的引脚通用性、跨平台可移植性以及对底层 HAL/LL 库的零依赖。开发者无需关心具体 MCU 是否具备足够数量的高级定时器输入捕获通道也无需配置复杂的定时器时基、预分频、捕获极性等参数只要目标引脚支持外部中断EXTI即可立即启用PwmIn功能。这使其在快速原型验证、教学实验、多路低成本 PWM 信号监测如遥控接收、电机反馈、传感器输出等场景中极具实用价值。从系统架构角度看PwmIn属于典型的“中断驱动 软件计时”模式。其运行不依赖 RTOS可在裸机Bare Metal环境下稳定工作同时由于中断服务程序ISR内仅执行最简化的寄存器读取与状态切换无浮点运算、无动态内存分配、无阻塞调用亦可无缝集成至 FreeRTOS 或其他实时操作系统中作为高优先级中断任务处理输入信号。2. 核心原理与状态机设计PwmIn的功能实现完全依托于对 GPIO 引脚电平跳变事件的精确时间戳记录。其内部维护一个有限状态机FSM共包含4 个关键状态对应 PWM 信号一个完整周期内的典型边沿序列状态编号状态名称触发条件记录时间戳计算动作状态转移目标IDLE空闲态初始化后或检测到高电平t_rise0—WAIT_FALLWAIT_FALL等待下降沿检测到低电平t_fall—WAIT_RISEWAIT_RISE等待上升沿检测到高电平t_rise1pulsewidth t_rise1 - t_fallWAIT_FALL(新周期)PERIOD_READY周期就绪t_rise1有效且t_rise0已存在—period t_rise1 - t_rise0WAIT_FALL(持续)注t_rise0与t_rise1分别代表连续两个上升沿的时间戳二者之差即为周期t_fall为紧邻t_rise1之前的下降沿时间戳t_rise1 - t_fall即为当前周期的脉宽。该状态机的健壮性体现在对信号异常的容错处理上若在WAIT_FALL状态超时未捕获下降沿例如信号丢失或占空比趋近 100%状态机将强制回退至IDLE并重置所有时间戳等待下一个有效上升沿重新同步若在WAIT_RISE状态超时未捕获上升沿例如占空比趋近 0% 或信号中断同样触发复位逻辑所有时间戳均采用us_ticker_read()或等效的微秒级滴答计数器获取确保跨平台一致性。此设计避免了传统“双中断法”上升沿下降沿各一次中断中因中断嵌套或优先级冲突导致的时间戳错乱问题通过单一线性状态流转显著提升了在高频率 PWM如 50kHz下的解析可靠性。3. API 接口详解PwmIn提供简洁而完备的 C 类接口所有成员函数均声明为public无虚函数开销符合嵌入式对代码体积与执行效率的严苛要求。3.1 构造与初始化class PwmIn { public: PwmIn(PinName pin); ~PwmIn(); };PwmIn(PinName pin)构造函数。pin参数指定用于捕获 PWM 信号的 GPIO 引脚如PA_0,P0_10。构造过程完成三项关键操作将引脚配置为输入模式PullNone注册上升沿触发中断Rising初始化内部状态机为IDLE清零所有时间戳变量。工程提示务必确保该引脚硬件上已连接上拉/下拉电阻若 MCU 不支持内部弱上下拉以防浮空电平引发误中断。~PwmIn()析构函数。自动禁用引脚中断并释放相关资源保障对象生命周期管理安全。3.2 核心数据读取接口uint32_t period(); // 返回最近一次成功捕获的周期值单位μs uint32_t pulsewidth(); // 返回最近一次成功捕获的脉宽值单位μs float duty(); // 返回占空比0.0 ~ 1.0基于最新 period/pulsewidth 计算 bool ready(); // 判断是否已完成至少一个完整周期的捕获即 period() 数据有效period()/pulsewidth()二者均返回uint32_t类型的无符号整数最大可表示约 4294 秒理论值实际受限于滴答计数器溢出周期如 24-bit us ticker 溢出时间为 16.7ms。关键机制这两个函数不触发任何硬件操作仅原子性地读取 ISR 中已更新的静态变量。因此可在主循环、RTOS 任务或低优先级中断中安全调用无竞态风险。duty()内部执行static_castfloat(pulsewidth()) / static_castfloat(period())运算。需注意当period()为 0如未同步成功时该函数返回0.0f避免除零异常。ready()返回true当且仅当状态机已进入PERIOD_READY状态表明period()和pulsewidth()的返回值具备工程可用性。强烈建议在首次使用前调用此函数进行有效性校验而非盲目读取。3.3 高级控制接口部分实现可能提供某些衍生版本或用户扩展中可能包含以下增强接口需查阅具体源码确认void reset(); // 强制状态机复位至 IDLE丢弃所有历史时间戳 uint32_t timeout_us(); // 获取当前超时阈值单位μs用于诊断信号丢失 void set_timeout_us(uint32_t us); // 设置超时阈值默认值通常为 100000即 100msreset()在调试阶段极为有用当发现ready()长期为false时可主动调用reset()强制重新同步避免因初始信号异常导致永久失锁。超时控制机制赋予开发者对信号质量的主动判断权。例如若设定timeout_us 2000020ms而实测period()稳定在 20000μs50Hz则timeout_us可设为30000以留出 50% 余量若用于检测故障如 ESC 失控停转则可将timeout_us设为略大于正常周期如 22000μs一旦ready()返回false即触发保护动作。4. 典型应用示例与工程实践4.1 裸机环境下的基础使用STM32F4xx HAL#include mbed.h #include PwmIn.h // 定义 PWM 输入引脚假设使用 PA_8需确认其支持 EXTI PwmIn pwm_in(PA_8); int main() { // 主循环前可添加简单同步等待 while (!pwm_in.ready()) { wait_us(100); // 每100μs轮询一次避免死等 } while (1) { if (pwm_in.ready()) { uint32_t per pwm_in.period(); uint32_t pw pwm_in.pulsewidth(); float duty pwm_in.duty(); // 示例通过串口打印需初始化 UART printf(Period: %u us, PulseWidth: %u us, Duty: %.2f%%\r\n, per, pw, duty * 100.0f); // 示例映射为 0-100 整数型占空比用于 LED 调光 uint8_t led_duty static_castuint8_t(duty * 100.0f); // ... 驱动 LED PWM 输出 } wait_ms(10); // 每10ms刷新一次显示 } }关键点解析wait_us(100)在同步阶段使用避免在ready()为false时高频轮询消耗 CPUprintf中格式化输出%u对应uint32_t确保跨平台兼容实际项目中printf应替换为更高效的ITM_SendChar()Cortex-M DWT或 DMA UART 发送避免printf自身带来的长延时干扰 PWM 捕获。4.2 FreeRTOS 环境下的安全集成在 RTOS 中PwmIn的 ISR 仍运行于最高优先级通常为configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY而数据读取应在任务上下文中进行。以下为推荐模式#include FreeRTOS.h #include task.h #include queue.h PwmIn pwm_in(PB_1); // 使用 PB_1 引脚 QueueHandle_t pwm_queue; // 用于向处理任务传递数据 // PWM 数据处理任务 void pwm_task(void *pvParameters) { struct { uint32_t period; uint32_t pulsewidth; TickType_t timestamp; } data; while (1) { // 从队列接收数据带超时防止无限阻塞 if (xQueueReceive(pwm_queue, data, portMAX_DELAY) pdPASS) { // 在此处执行业务逻辑PID 控制、协议解析、状态机跳转等 process_pwm_signal(data.period, data.pulsewidth); } } } // 自定义 ISR替代默认的 PwmIn 内部 ISR extern C void EXTI15_10_IRQHandler(void) { // 清除 EXTI 挂起位以 STM32F4 为例 if (EXTI_GetITStatus(EXTI_Line11) ! RESET) { EXTI_ClearITPendingBit(EXTI_Line11); // 仅在数据有效时发送至队列 if (pwm_in.ready()) { struct { uint32_t period; uint32_t pulsewidth; TickType_t timestamp; } data { .period pwm_in.period(), .pulsewidth pwm_in.pulsewidth(), .timestamp xTaskGetTickCount() }; // 向队列发送使用 FromISR 版本 BaseType_t xHigherPriorityTaskWoken pdFALSE; xQueueSendFromISR(pwm_queue, data, xHigherPriorityTaskWoken); portYIELD_FROM_ISR(xHigherPriorityTaskWoken); } } } int main() { // 初始化外设... pwm_queue xQueueCreate(10, sizeof(struct { uint32_t p; uint32_t pw; TickType_t t; })); xTaskCreate(pwm_task, PWM_TASK, configMINIMAL_STACK_SIZE, NULL, tskIDLE_PRIORITY 2, NULL); vTaskStartScheduler(); }设计优势将耗时的数据处理如浮点运算、通信协议打包完全移出 ISR严格遵守 RTOS 中断响应时间最短原则利用队列实现 ISR 与任务间的解耦天然支持多路 PWM 信号聚合处理xQueueSendFromISR确保中断安全portYIELD_FROM_ISR在必要时触发任务切换。4.3 多路 PWM 输入监控资源优化方案当需监控 4 路 PWM 信号如四旋翼飞控的遥控输入 CH1-CH4时直接实例化 4 个PwmIn对象将占用 4 个独立 EXTI 通道及相应中断向量。此时可采用共享中断 引脚扫描策略// 定义 4 路输入引脚需确保它们映射到同一 EXTI 线如 STM32 的 PA0/PB0/PC0 共享 EXTI0 PinName pwm_pins[4] {PA_0, PB_0, PC_0, PD_0}; PwmIn* pwm_instances[4]; // 共享 EXTI0 中断处理 void EXTI0_IRQHandler(void) { for (int i 0; i 4; i) { // 读取对应引脚电平需硬件支持快速 GPIO 读取 if (read_gpio_level(pwm_pins[i])) { // 触发对应 PwmIn 实例的内部状态机更新 pwm_instances[i]-update_on_rising_edge(); } } EXTI_ClearITPendingBit(EXTI_Line0); }前提条件MCU 必须支持同一 EXTI 线挂载多个 GPIO如 STM32 的 AFIO_EXTICR 寄存器配置且PwmIn类需开放update_on_rising_edge()等内部方法供外部调用。此方案将中断向量数从 4 减至 1显著降低中断管理开销。5. 性能边界与精度分析PwmIn的实际性能受三大因素制约中断响应延迟IRQ Latency、滴答计数器分辨率TICKER Resolution、状态机执行时间FSM Overhead。因素典型值Cortex-M4 168MHz对精度的影响IRQ Latency12–18 个周期≈70–100 ns引入固定偏移但可通过校准消除主要影响绝对时间精度对周期/脉宽差值影响小us_ticker 分辨率1 μs常见配置决定理论最小可分辨脉宽/周期变化量无法区分 1μs 的差异FSM 执行时间ISR≈300 ns汇编优化后占用中断服务时间过高会导致高频率 PWM100kHz下丢失边沿实测精度数据STM32F407VG频率范围可靠工作于 10 Hz – 80 kHz周期误差在 1 kHz 信号下标准偏差 0.5 μs0.05%脉宽误差同上因基于同一时间基准计算相对误差优于周期误差最大支持频率瓶颈当 PWM 频率 100 kHz 时WAIT_FALL→WAIT_RISE状态转换可能因 ISR 执行时间过长而超时需将timeout_us调至极低值如 5μs并接受更高丢帧率。精度提升实践启用编译器优化-O2或-O3编译使状态机跳转与时间戳读取内联使用更高分辨率滴答器若 MCU 支持 64-bit DWT_CYCCNTCortex-M可将其映射为us_ticker将分辨率提升至CPU_CLK / 1e6如 168MHz → 5.95ns关闭非必要中断在PwmInISR 执行期间临时屏蔽同组低优先级中断__disable_irq()减少抖动。6. 与其他 PWM 输入方案的对比方案硬件依赖精度多路支持移植难度典型适用场景PwmIn软件中断任意 EXTI 引脚1 μs★★★★☆★★★★★快速验证、教学、低成本多路监控定时器输入捕获HAL特定 TIMx_CHy62.5 ns*★★☆☆☆★★☆☆☆高精度伺服控制、音频信号分析DMA 定时器触发TIM DMA1 μs★★★★☆★★☆☆☆高频多路同步采样如电机相电流专用 PWM 解码芯片外部 IC100 ns★★★★★★☆☆☆☆工业级抗干扰、长线缆传输RS485*注STM32F4 定时器默认 168MHz APB1 时钟经 168 分频后为 1MHz1μs但可通过倍频器如 TIMx_CR1::CKD获得更高分辨率。PwmIn的不可替代价值在于其零配置、零依赖、零学习成本。工程师无需查阅数百页参考手册去配置定时器的预分频器、自动重装载值、捕获比较寄存器只需一行PwmIn pwm(PA_0);即可开始捕获。这种“开箱即用”的特性使其成为嵌入式开发中应对 PWM 输入需求的首选快速原型工具。