PX4飞控自定义Mavlink消息:实现UART传感器数据在QGC地面站的可视化

张开发
2026/4/6 3:45:00 15 分钟阅读

分享文章

PX4飞控自定义Mavlink消息:实现UART传感器数据在QGC地面站的可视化
1. 为什么需要自定义Mavlink消息在无人机开发中我们经常需要将各种传感器数据实时传输到地面站进行监控和分析。PX4飞控虽然内置了丰富的标准Mavlink消息但当我们接入一些特殊传感器时标准消息往往无法满足需求。比如你想通过UART串口接入一个电池管理系统(BMS)实时监测电压、电流、温度等参数这时候就需要自定义Mavlink消息了。我去年做过一个农业植保机项目需要实时监测药箱剩余药量和喷洒流量。标准Mavlink消息里没有这些字段当时就是通过自定义消息解决的。实测下来这种方案非常稳定可靠飞行2小时没出现过数据丢失。自定义Mavlink消息的核心优势在于可以自由定义数据结构完全适配你的传感器数据传输效率高一条消息就能打包所有相关参数QGC地面站可以原生支持不需要额外插件与标准消息使用相同的通信机制稳定性有保障2. 准备工作与环境搭建2.1 硬件准备首先确保你有这些硬件搭载PX4飞控的无人机Pixhawk系列或兼容硬件待接入的UART传感器如电压电流传感器调试用的电脑和USB数据线数传电台或WiFi链路用于地面站通信我推荐使用Pixhawk 4硬件它的UART接口丰富而且社区支持最好。传感器方面我用过Hobbywing的PL8电量计通过串口输出数据很稳定。2.2 软件环境需要准备这些软件工具PX4固件开发环境建议Ubuntu 18.04/20.04QGroundControl地面站版本3.5代码编辑器VS Code或Qt Creator串口调试工具如cutecom安装PX4开发环境时建议直接用官方提供的脚本bash ./Tools/setup/ubuntu.sh这个脚本会自动安装所有依赖项包括编译器、调试工具等。我第一次手动安装时漏了几个包编译老是报错后来用脚本一次就搞定了。3. 定义Mavlink消息结构3.1 创建消息定义文件在mavlink/message_definitions/v1.0/目录下新建read_sensor.xml文件?xml version1.0? mavlink version3/version messages message id223 nameREAD_UART_SENSOR descriptionBMS sensor data from UART/description field typefloat namevoltageVoltage in volts/field field typefloat namecurrentCurrent in amps/field field typefloat nametemperatureTemperature in °C/field field typeuint8_t namequantityRemaining percentage/field field typeuint8_t nameremainingRemaining minutes/field /message /messages /mavlink几个注意事项消息ID要避开标准消息范围建议200-300字段类型要匹配传感器数据类型字段名称要有意义方便后续使用记得添加单位说明我曾经因为字段类型用错该用float用了int导致数据精度丢失调试了半天才发现问题。3.2 生成消息头文件执行以下命令生成C语言头文件python3 -m pymavlink.tools.mavgen --langC --wire-protocol2.0 --outputgenerated/include/mavlink/v2.0 message_definitions/v1.0/read_sensor.xml生成的文件会放在generated/include/mavlink/v2.0目录下。把这个目录下的相关文件复制到PX4固件的mavlink/include/mavlink/v2.0/common目录中。4. PX4飞控端实现4.1 创建uORB消息在Firmware/msg/目录下创建read_uart_sensor.msg文件uint64 timestamp # 时间戳 float32 voltage # 电压值 float32 current # 电流值 float32 temperature # 温度值 uint8 quantity # 剩余电量百分比 uint8 remaining # 剩余飞行时间(分钟)然后在CMakeLists.txt中添加这个msg文件set(msg_files ... read_uart_sensor.msg )4.2 实现串口读取模块在src/modules/下新建read_uart_sensor目录创建主程序文件#include px4_platform_common/module.h #include drivers/drv_hrt.h #include uORB/uORB.h #include uORB/topics/read_uart_sensor.h // UART初始化 int uart_init(const char *device) { int fd open(device, O_RDWR | O_NOCTTY); // 配置串口参数... return fd; } // 主线程函数 int read_uart_thread(int argc, char *argv[]) { int uart_fd uart_init(/dev/ttyS3); struct read_uart_sensor_s sensor_data {}; orb_advert_t pub orb_advertise(ORB_ID(read_uart_sensor), sensor_data); while(!thread_should_exit) { // 读取串口数据并解析 char buffer[64]; read(uart_fd, buffer, sizeof(buffer)); // 示例数据格式: 15.4,2.1,25,80,30 sscanf(buffer, %f,%f,%f,%hhu,%hhu, sensor_data.voltage, sensor_data.current, sensor_data.temperature, sensor_data.quantity, sensor_data.remaining); sensor_data.timestamp hrt_absolute_time(); orb_publish(ORB_ID(read_uart_sensor), pub, sensor_data); usleep(100000); // 10Hz更新 } close(uart_fd); return 0; }4.3 配置Mavlink消息流在mavlink_messages.cpp中添加消息流类class MavlinkStreamReadUartSensor : public MavlinkStream { public: const char *get_name() const override { return READ_UART_SENSOR; } uint16_t get_id() override { return MAVLINK_MSG_ID_READ_UART_SENSOR; } bool send(const hrt_abstime t) override { read_uart_sensor_s data; if(_sub.update(data)) { mavlink_read_uart_sensor_t msg {}; msg.voltage data.voltage; msg.current data.current; msg.temperature data.temperature; msg.quantity data.quantity; msg.remaining data.remaining; mavlink_msg_read_uart_sensor_send_struct(_mavlink-get_channel(), msg); return true; } return false; } private: uORB::Subscription _sub{ORB_ID(read_uart_sensor)}; };然后在mavlink_main.cpp中设置发送频率configure_stream(READ_UART_SENSOR, 10.0f); // 10Hz5. QGC地面站端实现5.1 添加消息解析将生成的mavlink_msg_read_uart_sensor.h复制到QGC的libs/mavlink/include/mavlink/v2.0/common目录。在Vehicle.cc中添加消息处理函数void Vehicle::_handleReadUartSensor(mavlink_message_t message) { mavlink_read_uart_sensor_t sensor; mavlink_msg_read_uart_sensor_decode(message, sensor); _batteryFactGroup.voltage()-setRawValue(sensor.voltage); _batteryFactGroup.current()-setRawValue(sensor.current); _batteryFactGroup.temperature()-setRawValue(sensor.temperature); _batteryFactGroup.percentRemaining()-setRawValue(sensor.quantity); _batteryFactGroup.timeRemaining()-setRawValue(sensor.remaining); }5.2 修改UI界面在BatteryIndicator.qml中添加显示控件Column { spacing: 2 QGCLabel { text: 电压: _activeVehicle.battery.voltage.value.toFixed(1) V } QGCLabel { text: 电流: _activeVehicle.battery.current.value.toFixed(1) A } QGCLabel { text: 温度: _activeVehicle.battery.temperature.value.toFixed(0) °C } QGCLabel { text: 电量: _activeVehicle.battery.percentRemaining.value % } QGCLabel { text: 剩余时间: _activeVehicle.battery.timeRemaining.value 分钟 } }6. 调试与优化技巧6.1 常见问题排查数据不更新检查串口配置波特率、数据位、停止位数据错误验证消息ID在飞控和地面站端是否一致数据延迟调整Mavlink消息发送频率内存不足优化消息结构减少不必要字段我遇到过一个坑地面站收不到数据最后发现是消息ID在飞控和地面站版本不一致。建议在消息定义稳定前两边同时更新。6.2 性能优化建议合理设置消息频率关键数据10-20Hz足够使用适当的数据类型能用uint8就不要用float启用数据压缩对浮点数设置合理的精度使用多消息分流大数据量时分多个消息发送在植保机项目中我把所有喷洒数据打包到一个消息里结果导致通信延迟。后来拆分成两个消息问题就解决了。7. 扩展应用场景这种自定义消息的方案不仅适用于电池监测还可以用于农业植保机的药量监测物流无人机的货舱状态监控巡检无人机的专用传感器数据实验性传感器的快速集成去年给某科研团队做水下无人机时就用类似方案传输了水深、水温和水质数据。他们后来发论文时还特别感谢了这个数据采集方案。

更多文章