- 嵌入式
- 物联网
- 机器人
- 自动驾驶
- 智能硬件
【免费下载链接】PX4-Autopilot
PX4 Autopilot Software
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,无多实例别名)。
| 字段名 | 类型 | 单位 | 说明 |
|---|---|---|---|
timestamp | uint64 | µs | 系统启动以来的时间(微秒),即采样时刻 |
error_count | uint64 | — | 定时器过捕获(overcapture)错误计数(AUX5 或 MAIN5 管脚) |
pulse_width | uint32 | µs | 脉冲宽度,定时器计数(微秒) |
period | uint32 | µ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)完成以下关键步骤:
- 预留管脚与定时器:遍历
DIRECT_PWM_OUTPUT_CHANNELS,找到与GPIO_PWM_IN端口/管脚匹配的输出通道,调用io_timer_allocate_channel()与io_timer_allocate_timer()以IOTimerChanMode_PWMIn模式独占该定时器通道; - 配置输入管脚:
px4_arch_configgpio(GPIO_PWM_IN); - 挂接中断向量:
irq_attach(PWMIN_TIMER_VECTOR, PWMIN::pwmin_tim_isr, NULL); - 预分频换算微秒:
/* 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)」,两者在数值上直接就是微秒;
- 计数器满量程:
rARR = UINT16_MAX,让计数器跨越完整 16 位量程,避免在长周期信号下过早溢出(所有 PX4 使用的定时器均支持 uint16 计数); - 使能定时器与中断:生成更新事件后置位
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 infopwm_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 MHz,
pulse_width/period的计数单位即微秒,字段无需额外换算即可直接使用; - 中断内发布:数据在定时器捕获 ISR 中填充并发布,保证测量与发布之间的延迟最小;首次发布提前到非中断上下文完成,规避 uORB 在中断中的初始化限制;
- 错误可观测:过捕获(溢出)时样本被丢弃并累计
error_count,下游可据此判断输入信号质量; - 板级可配置:输入管脚、定时器与通道由各板卡
board_config.h中的GPIO_PWM_IN、PWMIN_TIMER、PWMIN_TIMER_CHANNEL决定,编译期即校验约束; - 多场景复用:同一话题被 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
相关推荐
PX4-Autopilot HeaterStatus UORB 消息详解:字段定义、常量与加热器驱动源码实现
PX4 Autopilot HeaterStatus UORB 消息详解:字段定义、常量与加热器驱动源码实现 本文以 PX4 Autopilot 仓库中的 He
嵌入式物联网机器人自动驾驶智能硬件PX4-Autopilot ControlAllocatorStatus uORB 消息:从字段语义到控制分配器实现
PX4 Autopilot ControlAllocatorStatus uORB 消息:从字段语义到控制分配器实现 本篇技术指南以 PX4 Autopilot
嵌入式物联网机器人自动驾驶智能硬件PX4 uORB 消息 AirspeedValidatedV0 全解析:字段语义、数据来源与下游消费链路
PX4 uORB 消息 AirspeedValidatedV0 全解析:字段语义、数据来源与下游消费链路 airspeed_validated_v0 是 PX4
嵌入式物联网机器人自动驾驶智能硬件
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考