☰
深入解析 PX4 的 PwmInput uORB 消息:PWM 输入捕获的字段语义、驱动实现与典型应用
2026/9/29 5:24:19 网站建设 项目流程
  • 嵌入式
  • 物联网
  • 机器人
  • 自动驾驶
  • 智能硬件

【免费下载链接】PX4-Autopilot

PX4 Autopilot Software

项目地址:https://gitcode.com/gh_mirrors/px/PX4-Autopilot
点击查看免费下载

PwmInput是 PX4 Autopilot 中专门承载「PWM 输入脉冲测量结果」的 uORB 话题,由pwm_input驱动通过定时器输入捕获中断(ISR)在 AUX5(或 MAIN5)管脚上测量脉冲宽度与信号周期,并以微秒精度对外发布。本文以 docs/en/msg_docs/PwmInput.md 定义的消息结构为骨架,逐字段讲解其语义,并结合 src/drivers/pwm_input 驱动源码、板级配置以及ll40ls_pwm、rpm_capture等下游订阅者,讲清「硬件管脚 → 定时器捕获 → uORB 消息 → 应用模块」的完整数据链路。读完本文,你将能准确理解每个字段的取值来源与单位换算,知道如何启用、观测该话题,并掌握基于pwm_input实现 PWM 测距、转速捕获等功能的底层原理。

一、消息定义:一张表看懂 PwmInput

PwmInput消息由 4 个字段组成,全部为无符号整型,单位为微秒(µs)。其话题名为pwm_input(单一 TOPIC,无多实例别名)。

字段名类型单位说明
timestampuint64µs系统启动以来的时间(微秒),即采样时刻
error_countuint64—定时器过捕获(overcapture)错误计数(AUX5 或 MAIN5 管脚)
pulse_widthuint32µs脉冲宽度,定时器计数(微秒)
perioduint32µs信号周期,定时器计数(微秒)

对应的原始消息定义位于 msg/PwmInput.msg,全文仅 4 行:

uint64 timestamp # Time since system start (microseconds) uint64 error_count # Timer overcapture error flag (AUX5 or MAIN5) uint32 pulse_width # Pulse width, timer counts (microseconds) uint32 period # Period, timer counts (microseconds)

该文件已被纳入 msg/CMakeLists.txt 的 uORB 消息生成流程,构建时由Tools/msg下的生成脚本(如 Tools/msg/px_generate_uorb_topic_files.py)自动生成对应的 C/C++ 结构体pwm_input_s与话题定义。

二、四个字段的完整语义与取值来源

1.timestamp:采样时间戳

uint64类型,单位微秒,表示「系统启动以来的时间」。在 pwm_input.cpp 的publish()中由hrt_absolute_time()填充:

_pwm.timestamp = hrt_absolute_time();

该时间戳是 ISR 触发时刻的高精度硬件时间,下游模块(如测距传感器)可直接基于它计算数据年龄或做时间同步,无需关心 uORB 发布本身引入的延迟。

2.error_count:定时器过捕获错误计数

字段注释写作「Timer overcapture error flag」,语义上是累计的错误次数而非单次标志位。驱动在发布前检查定时器状态寄存器(rSR)中的溢出标志位SR_OVF_PWMIN(即 CC1OF / CC2OF 捕获溢出标志):

// if we missed an edge, we have to give up if (status & SR_OVF_PWMIN) { _error_count++; return; }

也就是说,一旦某次边沿到来时上一次捕获尚未被读取(过捕获),驱动认为该样本不可靠,丢弃本次数据并递增错误计数,而不是发布错误数值。因此error_count是衡量输入信号质量 / 驱动健康度的重要指标——若持续增长,说明 PWM 信号频率过高、定时器配置不当或系统中断响应不及时。

3.pulse_width:脉冲宽度

uint32,单位微秒,表示一个 PWM 周期内高电平持续的时间。它来自定时器捕获比较寄存器 B(rCCR_PWMIN_B),在 ISR 中直接读出:

uint32_t period = rCCR_PWMIN_A; uint32_t pulse_width = rCCR_PWMIN_B;

对典型的 RC 遥控器接收机或舵机类 PWM 信号,脉宽通常位于 1000–2000 µs 区间(如 1500 µs 为中立点);对 Lidar-Lite 这类 PWM 测距传感器,脉宽则直接编码了距离信息(详见下文下游应用章节)。

4.period:信号周期

uint32,单位微秒,表示相邻两次上升沿之间的时间间隔(即整个 PWM 信号的周期)。RC 接收机通常以 50 Hz 更新,对应周期约 20000 µs。period与pulse_width组合可计算占空比,也可用于判断输入是否丢失(例如rpm_capture以超时阈值区分「有信号」与「无信号」状态)。

三、数据来源:pwm_input 驱动与定时器捕获原理

驱动整体结构

pwm_input是一个基于ModuleBase的 NuttX 平台驱动,源码仅两个文件:

  • src/drivers/pwm_input/pwm_input.cpp
  • src/drivers/pwm_input/pwm_input.h

模块的官方描述为:

Measures the PWM input on AUX5 (or MAIN5) via a timer capture ISR and publishes via the uORB 'pwm_input' message.

即:通过定时器捕获中断在 AUX5(或 MAIN5,具体取决于板卡)管脚上测量 PWM 输入,并通过 uORBpwm_input话题发布。

启动流程与首个样本

task_spawn()创建PWMIN实例并调用start()(pwm_input.cpp):

void PWMIN::start() { // NOTE: must first publish here, first publication cannot be in interrupt context _pwm_input_pub.update(); // Initialize the timer isr for measuring pulse widths. Publishing is done inside the isr. timer_init(); }

这里有一个值得注意的细节:驱动在初始化定时器之前先主动发布一次空样本(_pwm_input_pub.update())。注释明确指出,首次发布不能发生在中断上下文,提前发布可确保 uORB 话题已正确注册,为后续在 ISR 内发布铺路。此后真正的测量与发布全部在定时器 ISR 中完成。

定时器初始化:微秒分辨率如何实现

timer_init()(pwm_input.cpp)完成以下关键步骤:

  1. 预留管脚与定时器:遍历DIRECT_PWM_OUTPUT_CHANNELS,找到与GPIO_PWM_IN端口/管脚匹配的输出通道,调用io_timer_allocate_channel()与io_timer_allocate_timer()以IOTimerChanMode_PWMIn模式独占该定时器通道;
  2. 配置输入管脚:px4_arch_configgpio(GPIO_PWM_IN);
  3. 挂接中断向量:irq_attach(PWMIN_TIMER_VECTOR, PWMIN::pwmin_tim_isr, NULL);
  4. 预分频换算微秒:
/* for simplicity scale by the clock in MHz. This gives us * readings in microseconds which is typically what is needed * for a PWM input driver */ uint32_t prescaler = PWMIN_TIMER_CLOCK / 1000000UL; ... rPSC = prescaler - 1;

预分频器把定时器时钟按 MHz 缩放到 1 MHz 计数频率,使每个计数器 tick 恰好等于 1 µs——这就是为什么pulse_width与period的注释写作「timer counts (microseconds)」,两者在数值上直接就是微秒;

  1. 计数器满量程:rARR = UINT16_MAX,让计数器跨越完整 16 位量程,避免在长周期信号下过早溢出(所有 PX4 使用的定时器均支持 uint16 计数);
  2. 使能定时器与中断:生成更新事件后置位GTIM_CR1_CEN,退出临界区后up_enable_irq(PWMIN_TIMER_VECTOR)。

ISR 中的数据采集与发布

中断服务程序 pwmin_tim_isr 非常精简——读取状态与两个捕获寄存器,清中断标志,然后直接在中断上下文发布:

int PWMIN::pwmin_tim_isr(int irq, void *context, void *arg) { uint16_t status = rSR; uint32_t period = rCCR_PWMIN_A; uint32_t pulse_width = rCCR_PWMIN_B; /* ack the interrupts we just read */ rSR = 0; auto obj = get_instance<PWMIN>(desc); if (obj != nullptr) { obj->publish(status, period, pulse_width); } return PX4_OK; }

从 pwm_input.h 中的寄存器位定义可以推断其捕获原理:定时器工作于从模式复位(SMS = 0x04)的 PWM 输入模式——通道 A 捕获信号作为触发源(CH1 配置使用TS = TI1FP1,CH2 配置使用TI2FP2),上升沿使计数器复位并锁存周期值到捕获寄存器 A,下降沿则把脉宽锁存到捕获寄存器 B(CCxP 极性位与 CCxE 使能位的组合配置即为此服务)。这正是一般通用定时器(如 STM32 GTIM)标准的 PWM 输入测量方案。

publish()(pwm_input.cpp)在确认无过捕获后,按序填充timestamp/error_count/period/pulse_width四个字段并发布,同时更新_last_period、_last_width、_pulses_captured统计量供status命令输出。

编译期硬约束

src/drivers/pwm_input/pwm_input.h 对板级集成提出了明确约束,任何一条不满足都会直接编译失败:

  • HRT_TIMER与PWMIN_TIMER不能共用同一个定时器(#error cannot share timer between HRT and PWMIN);
  • 板卡必须在board_config.h中定义GPIO_PWM_IN、PWMIN_TIMER、PWMIN_TIMER_CHANNEL三个宏;
  • PWMIN_TIMER_CLOCK必须大于 1 MHz(保证预分频后分辨率可用);
  • PWMIN_TIMER_CHANNEL仅允许取 1 或 2。

以 boards/ark/fmu-v6x/src/board_config.h 为例:

#define PWMIN_TIMER 4 #define PWMIN_TIMER_CHANNEL /* T4C2 */ 2 #define GPIO_PWM_IN /* PD13 */ GPIO_TIM4_CH2IN_2

即该板卡使用定时器 TIM4 的通道 2(PD13 管脚)作为 PWM 输入。类似的定义还存在于 boards/px4/fmu-v6x/src/board_config.h、boards/cuav/7-nano/src/board_config.h、boards/mro/ctrl-zero-h7/src/board_config.h 等众多板卡中,AUX5/MAIN5 的具体物理管脚由各板卡的board_config.h决定。

四、如何启用与使用 pwm_input 驱动

编译开关

驱动由 Kconfig 开关控制,默认关闭(src/drivers/pwm_input/Kconfig):

menuconfig DRIVERS_PWM_INPUT bool "pwm_input" default n

构建模块定义见 src/drivers/pwm_input/CMakeLists.txt:模块名drivers__pwm_input,入口pwm_input。板卡需要在自己的.px4board配置(CONFIG_DRIVERS_PWM_INPUT=y)中启用后,pwm_input命令才会被编译进固件。

命令行用法

根据 docs/en/modules/modules_system.md,驱动支持三条标准命令:

pwm_input <command> [arguments...] Commands: start stop status print status info
  • pwm_input start:启动驱动,执行定时器初始化并开始发布;
  • pwm_input stop:停止驱动;
  • pwm_input status:打印统计信息。

status命令的输出格式在 pwm_input.cpp 中定义,形如:

count=<已捕获脉冲数> period=<最近周期(µs)> width=<最近脉宽(µs)>

自动启动时机

在真实固件中,pwm_input通常由系统启动脚本按参数自动拉起。ROMFS/px4fmu_common/init.d/rc.sensors 中的逻辑如下:

# Sensors on the PWM interface bank if param compare -s SENS_EN_LL40LS 1 then if pwm_input start then ll40ls_pwm start fi fi # External automatic trigger system if param compare FD_EXT_ATS_EN 1 then pwm_input start fi

即两种情况会触发pwm_input start:

  • SENS_EN_LL40LS=1:启用 PWM 接口的 Lidar-Lite 测距传感器,先启动pwm_input再启动ll40ls_pwm;
  • FD_EXT_ATS_EN=1:启用外部自动触发系统(External Automatic Trigger System),直接启动pwm_input。

观测消息

固件运行后,可通过 PX4 自带的listener系统命令(源码位于 src/systemcmds/listener)实时查看该话题的发布内容,例如在 NuttShell 中执行:

listener pwm_input

输出会循环打印timestamp、error_count、pulse_width、period的最新值,是验证捕获链路是否工作的最直接手段。

五、谁在消费 PwmInput:下游订阅与典型应用

pwm_input话题本身只是测量结果的载体,真正有价值的是消费它的应用模块。仓库中有多处典型的上下游关系。

1. Lidar-Lite PWM 测距(ll40ls_pwm)

src/drivers/distance_sensor/ll40ls_pwm/LidarLitePWM.cpp 的文件头注释直接说明了二者关系:

Driver for the PulsedLight Lidar-Lite range finders connected via PWM. This driver accesses the pwm_input published by the pwm_input driver.

其collect()(LidarLitePWM.cpp)订阅pwm_input话题:

int LidarLitePWM::collect() { pwm_input_s pwm_input; if (_sub_pwm_input.update(&pwm_input)) { _pwm = pwm_input; return PX4_OK; } return EAGAIN; }

随后在measure()(LidarLitePWM.cpp)中完成脉宽到距离的换算并发布distance_sensor:

const float current_distance = static_cast<float>(_pwm.pulse_width) * 1e-3f; // 1us = 1mm distance for LIDAR-Lite ... _px4_rangefinder.update(timestamp_sample, current_distance);

1 µs 脉宽 = 1 mm 距离,即pulse_width字段被直接当作测距结果使用。这正是「PWM 输入测量 → 应用级传感器」的经典案例,也解释了为何pwm_input的发布频率与信号同步、时间戳精度至关重要。

2. 转速输入捕获(rpm_capture)

src/drivers/rpm_capture/RPMCapture.cpp 是pwm_input话题的另一个生产者兼消费者——它通过参数系统查找被配置为RPM_Input(输出功能编号 2070,见mixer_module/output_functions.yaml参数元数据)的输出通道,用 GPIO 外部中断捕获转速脉冲,并在 Run() 中同时发布rpm与pwm_input两个话题:

pwm_input_s pwm_input{}; pwm_input.timestamp = now; pwm_input.period = _period; pwm_input.error_count = _error_count; _pwm_input_pub.publish(pwm_input);

这里period即相邻两次转速脉冲的时间间隔(µs),进而换算转速:

rpm_raw = 60.f * 1e6f / static_cast<float>(_param_rpm_puls_per_rev.get() * _period);

值得注意的是,与定时器捕获方案不同,rpm_capture走的是「GPIO EXTI + 高优先级工作队列」路径(调用px4_arch_gpiosetevent),并通过io_timer_channel_get_as_pwm_input()把输出通道复用为输入,体现了 PX4 输入捕获体系的灵活性。

3. PPS 秒脉冲捕获(pps_capture)

src/drivers/pps_capture/PPSCapture.cpp 用于捕获 GNSS 模块的 PPS(Pulse Per Second)信号并计算 PPS 与实时时钟的漂移。它与rpm_capture采用相同的基础设施——通过io_timer_channel_get_as_pwm_input(_channel)获取输入 GPIO(PPSCapture.cpp)并注册外部中断回调,最终发布独立的pps_capture话题。它证明了「把 PWM 输出通道当作高精度输入捕获使用」这一模式在 PX4 中已被广泛复用。

4. 外部自动触发系统

如前所述,FD_EXT_ATS_EN=1时系统会单独启动pwm_input(不启动测距驱动),此时pwm_input话题成为外部自动触发(如相机触发)信号测量通道,供触发/同步相关逻辑使用。

六、总结:PwmInput 的设计要点

  1. 微秒级精度:通过预分频把定时器时钟缩放到 1 MHz,pulse_width/period的计数单位即微秒,字段无需额外换算即可直接使用;
  2. 中断内发布:数据在定时器捕获 ISR 中填充并发布,保证测量与发布之间的延迟最小;首次发布提前到非中断上下文完成,规避 uORB 在中断中的初始化限制;
  3. 错误可观测:过捕获(溢出)时样本被丢弃并累计error_count,下游可据此判断输入信号质量;
  4. 板级可配置:输入管脚、定时器与通道由各板卡board_config.h中的GPIO_PWM_IN、PWMIN_TIMER、PWMIN_TIMER_CHANNEL决定,编译期即校验约束;
  5. 多场景复用:同一话题被 PWM 测距(Lidar-Lite)、转速捕获(rpm_capture)等模块消费,是 PX4 中「通用 PWM 脉冲测量」的标准数据接口。

从消息定义(msg/PwmInput.msg)到驱动实现(src/drivers/pwm_input/pwm_input.cpp),再到下游应用(LidarLitePWM.cpp、RPMCapture.cpp),pwm_input展示了一条完整、可追踪的传感器数据链路——这也正是阅读 uORB 消息文档时最值得延伸的视角。

  • 嵌入式
  • 物联网
  • 机器人
  • 自动驾驶
  • 智能硬件

【免费下载链接】PX4-Autopilot

PX4 Autopilot Software

项目地址:https://gitcode.com/gh_mirrors/px/PX4-Autopilot
点击查看免费下载

相关推荐

上一篇:Wand-Enhancer 使用指南:给 WeMod 加个远程控制面板,还能解锁这些本地功能
下一篇:如何快速修复Windows更新:老旧系统的完整解决方案指南

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询