MAVLINK消息在Ardupilot中的接收和发送过程

本文详细介绍了MAVLINK消息在Ardupilot中如何通过update_receive()和update_send()函数实现接收与发送的过程。重点分析了消息的解包与处理流程,以及发送消息时的时机选择。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

MAVLINK消息在Ardupilot中的接收和发送过程


由于现在网上很多的都是APM旧版本的解释,因此把自己的一些学习所得记录下来。截至写博客日期(2020/10/21),本文APM版本为4.0(针对各种车辆的)。

SCHED_TASK

关于MAVLINK消息在APM中的初始化过程,在之前的博文里面已经写过了:MAVLINK在Ardupilot中的初始化过程。建议先阅读一下,虽然写得不是很完整,但还是能理一下思路的,后面有时间的话会进一步更新修改。

打开Ardupilot源码项目,进入随机车辆类型的主目录文件下找到对应的主程序文件,如在ArduCopter路径下,Copter.cpp中,对应文件下始终实现了两个函数的任务调度器。

SCHED_TASK_CLASS(GCS,                  (GCS*)&copter._gcs,          update_receive, 400, 180),
SCHED_TASK_CLASS(GCS,                  (GCS*)&copter._gcs,          update_send,    400, 550),

又如在ArduSub文件夹下的ArduSub.cpp文件中,同样实现了

SCHED_TASK_CLASS(GCS,                 (GCS*)&sub._gcs,   update_receive,     400, 180),
SCHED_TASK_CLASS(GCS,                 (GCS*)&sub._gcs,   update_send,        400, 550),

这两个函数始终以400Hz的频率运行,在APM上进行MAVLINK消息的发送和接收。

update_receive()

这个函数实现在ardupilot/libraries/GCS_MAVLINK/GCS_Common.cpp文件中。在之前的博文里面我们说过,GCS类相当于是在APM中实现了一个“地面站”的功能,用于发送和接收消息(当然并不完全正确)。具体来看里面的内容:

void GCS::update_receive(void)
{
    for (uint8_t i=0; i<num_gcs(); i++) {
        chan(i)->update_receive();
    }
    // also update UART pass-thru, if enabled
    update_passthru();
}

虽然这个函数在之前已经介绍过了,这边还是再说一下。num_gcs()获取到总共的MAVLINK数据传输接口(或链路)。然后对于每一个通道执行更新接收操作update_receive()。这个函数才是实际上对于每一个通道接口真正执行接收MAVLINK消息的函数。

这部分代码有点长就不全部放上来了,实际上对于MAVLINK接收消息并且解包的过程之前也有博文讲过(重读Ardupilot中stabilize model+MAVLINK解包过程),但是前面的一些知识点还是比较零散的,这边还是再汇总一下。

还是挑重点说一下,首先是在原函数中找到该段

        // Try to get a new message
        if (mavlink_parse_char(chan, c, &msg, &status)) {
            hal.util->persistent_data.last_mavlink_msgid = msg.msgid;
            hal.util->perf_begin(_perf_packet);
            packetReceived(status, msg);
            hal.util->perf_end(_perf_packet);
            parsed_packet = true;
            gcs_alternative_active[chan] = false;
            alternative.last_mavlink_ms = now_ms;
            hal.util->persistent_data.last_mavlink_msgid = 0;
        }

其他的可以先不追究细节,重点找到里面的

packetReceived(status, msg);

进入该函数

void GCS_MAVLINK::packetReceived(const mavlink_status_t &status,
                                 const mavlink_message_t &msg)
{
    // we exclude radio packets because we historically used this to
    // make it possible to use the CLI over the radio
    if (msg.msgid != MAVLINK_MSG_ID_RADIO && msg.msgid != MAVLINK_MSG_ID_RADIO_STATUS) {
        const uint8_t mask = (1U<<(chan-MAVLINK_COMM_0));
        if (!(mask & mavlink_private)) {
            mavlink_active |= mask;
        }
    }
    if (!(status.flags & MAVLINK_STATUS_FLAG_IN_MAVLINK1) &&
        (status.flags & MAVLINK_STATUS_FLAG_OUT_MAVLINK1) &&
        AP::serialmanager().get_mavlink_protocol(chan) == AP_SerialManager::SerialProtocol_MAVLink2) {
        // if we receive any MAVLink2 packets on a connection
        // currently sending MAVLink1 then switch to sending
        // MAVLink2
        mavlink_status_t *cstatus = mavlink_get_channel_status(chan);
        if (cstatus != nullptr) {
            cstatus->flags &= ~MAVLINK_STATUS_FLAG_OUT_MAVLINK1;
        }
    }
    if (!routing.check_and_forward(chan, msg)) {
        // the routing code has indicated we should not handle this packet locally
        return;
    }
    if (!accept_packet(status, msg)) {
        // e.g. enforce-sysid says we shouldn't look at this packet
        return;
    }
    handleMessage(msg);
}

packetReceived() 实现了对于MAVLINK消息包的接收。前面一些内容大家看注释理解一下即可。我们只需要关注,packetReceived()函数在最后面调用了 handleMessage()这个函数。这个函数才是对于接收到的消息包进行真正处理的函数。

定位到该函数定义于库内的GCS.h文件中

virtual void        handleMessage(const mavlink_message_t &msg) = 0;

在GCS_MAVLINK类中实现为一个虚函数,因此我们必须在各个具体的车辆类型中找到对应派生类中的具体实现。

以ArduSub为例,在上层车辆文件夹中的GCS_Mavlink.h中实现了GCS_MAVLINK_Sub类对于GCS_MAVLINK消息的继承,并且在GCS_Mavlink.cpp文件中实现了对于handleMessage()的重写。

void GCS_MAVLINK_Sub::handleMessage(const mavlink_message_t &msg)
{
    switch (msg.msgid) {

    case MAVLINK_MSG_ID_HEARTBEAT: {    // MAV ID: 0
        // We keep track of the last time we received a heartbeat from our GCS for failsafe purposes
        if (msg.sysid != sub.g.sysid_my_gcs) {
            break;
        }
        sub.failsafe.last_heartbeat_ms = AP_HAL::millis();
        break;
    }

	case ...
	...
}

可以看到在内部通过switch-case语句实现了根据具体的mavlink消息id进行不同的事件处理的功能(内容太长就不全部放上来了)。

关于update_receive()函数了解到这里就差不多了。

update_send()

重新回到ArduSub.cpp中,我们对于update_send()进行介绍。

同样在ardupilot/libraries/GCS_MAVLINK/GCS_Common.cpp文件中

void GCS::update_send()
{
    if (!initialised_missionitemprotocol_objects) {
        initialised_missionitemprotocol_objects = true;
        // once-only initialisation of MissionItemProtocol objects:
        AP_Mission *mission = AP::mission();
        if (mission != nullptr) {
            _missionitemprotocol_waypoints = new MissionItemProtocol_Waypoints(*mission);
        }
        AP_Rally *rally = AP::rally();
        if (rally != nullptr) {
            _missionitemprotocol_rally = new MissionItemProtocol_Rally(*rally);
        }
    }
    if (_missionitemprotocol_waypoints != nullptr) {
        _missionitemprotocol_waypoints->update();
    }
    if (_missionitemprotocol_rally != nullptr) {
        _missionitemprotocol_rally->update();
    }
    for (uint8_t i=0; i<num_gcs(); i++) {
        chan(i)->update_send();
    }
    WITH_SEMAPHORE(_statustext_sem);
    service_statustext();
}

多余部分不再过多讲解,定位到chan(i)->update_send()函数,该语句实现对于每一个通道上的MAVLINK消息的发送。在同一文件下的 void GCS_MAVLINK::update_send()函数中(内容过长不放上来了),找到do_try_send_message(),其是最主要的功能部分实现。

这个函数会在合适的时间调用try_send_message()函数,这也是do_try_send_message()函数的最重要的功能。

try_send_message()函数内部同样使用了switch-case语句通过判断具体的MAVLINK消息id来实现对于不同APM系统信息的发送功能,并返回布尔类型的值,发送成功即返回true。

bool GCS_MAVLINK::try_send_message(const enum ap_message id)
{
    bool ret = true;

    switch(id) {

    case MSG_ATTITUDE:
        CHECK_PAYLOAD_SIZE(ATTITUDE);
        send_attitude();
        break;
    
    case …
    …
}

以MSG_ATTITUDE消息为例,该case语句中首先对PAYLOAD长度进行判断,长度合适返回true。然后调用具体的消息处理函数。

void GCS_MAVLINK::send_attitude() const
{
    const AP_AHRS &ahrs = AP::ahrs();
    const Vector3f omega = ahrs.get_gyro();
    mavlink_msg_attitude_send(
        chan,
        AP_HAL::millis(),
        ahrs.roll,
        ahrs.pitch,
        ahrs.yaw,
        omega.x,
        omega.y,
        omega.z);
}

这个函数(其他消息也类似)会在内部调用由消息头文件所提供的函数接口,用来实现MAVLINK消息的发送功能(不理解的同学看这边:QGC添加自定义组件和发送自定义MAVLINK消息,这篇博文的第二章)。

这个函数实现在对应消息的头文件中,通过将需要传输的信息生成buf或者packet(究竟是哪个就需要看协议是怎么定的啦,可以全局找一下protocol.h这个文件),然后通过_mav_finalize_message_chan_send()函数发送出去(怎么发送的就不用关心太多啦~)。

关于这部分内容,了解这么多应该就足够了。

<think>好的,我需要帮助用户了解如何在ArduPilot中通过串口发送MAVLink消息。首先,我应该回忆一下ArduPilot的基本架构,特别是关于MAVLink通信的部分。MAVLink是无人机系统中常用的通信协议,用于地面站飞控之间的数据传输。 用户的问题是关于通过串口发送消息,所以需要明确ArduPilot的串口配置。ArduPilot支持多个串口,每个串口可以配置为不同的协议,比如MAVLink、GPS、Telem等。发送MAVLink消息需要确保对应的串口已经启用了MAVLink协议。 接下来,可能需要修改参数。比如,SERIALx_PROTOCOL参数(x是串口号,如0、1、2等)需要设置为MAVLink对应的值,通常是1。此外,波特率参数SERIALx_BAUD也要设置正确,确保发送接收端一致。 然后,用户可能需要编写代码,或者使用现有的MAVLink库来生成发送消息。如果是开发者,可能需要修改ArduPilot的固件代码,在适当的位置调用发送函数。例如,使用GCS_MAVLINK::send_message()这样的函数来发送特定的MAVLink消息。 还需要考虑消息发送频率,避免占用过多带宽。同时,要确保消息的目标系统ID组件ID正确,以便接收方能正确解析。 另外,用户可能需要通过地面站软件(如Mission Planner)来验证消息是否成功发送,或者使用串口调试工具监听数据流,确认MAVLink数据包的存在。 可能的误区包括串口配置错误,比如协议未启用或波特率不匹配。还有可能消息构造不正确,导致解析失败。需要提醒用户检查这些方面。 总结步骤应该是:配置串口参数、编写或调用发送函数、验证通信。同时,给出具体的参数设置示例代码片段会更有帮助。</think>在ArduPilot中通过串口发送MAVLink消息,需遵循以下步骤: --- ### **1. 配置串口协议** ArduPilot通过`SERIALx_PROTOCOL`参数定义串口用途(x为串口号,如0,1,2...): - 将目标串口的协议设为**MAVLink**(值为`1`) ```bash SERIAL2_PROTOCOL = 1 # 启用串口2的MAVLink协议 ``` - 设置波特率(需与接收端一致): ```bash SERIAL2_BAUD = 57600 # 常用波特率为57600或115200 ``` --- ### **2. 选择MAVLink消息通道** ArduPilot默认有3个MAVLink通道: - **通道0**:主通信链路(通常对应串口0,连接数传/电台) - **通道1**:辅助链路(对应串口1或其他) - **通道2**:日志传输 通过`MAV_0_CONFIG`等参数可绑定通道到指定串口: ```bash MAV_1_CONFIG = 2 # 将MAVLink通道1绑定到串口2 ``` --- ### **3. 发送消息的方法** #### **方法1:通过GCS_MAVLINK对象** 在飞控代码中(如C++)调用内置函数: ```cpp // 获取MAVLink通道对象 GCS_MAVLINK &gcs = drone.gcs(); // 发送HEARTBEAT消息示例 mavlink_msg_heartbeat_pack_chan( gcs.sysid(), // 系统ID(默认1) gcs.compid(), // 组件ID(通常1为飞控,190为GCS) gcs.mavlink_chan(), // 通道号(0,1,2...) &msg_heartbeat, // 消息对象 MAV_TYPE_QUADROTOR, // 无人机类型 MAV_AUTOPILOT_ARDUPILOTMEGA, MAV_MODE_GUIDED_ARMED, 0, MAV_STATE_ACTIVE ); // 发送消息 gcs.send_message(MAVLINK_MSG_ID_HEARTBEAT, &msg_heartbeat); ``` #### **方法2:使用AP_HAL库直接写串口** ```cpp // 获取串口对象(例如串口2) AP_HAL::UARTDriver *uart = drone.serial_manager.find_serial(AP_SerialManager::SerialProtocol_MAVLink, 1); // 构造MAVLink消息 mavlink_message_t msg; mavlink_msg_command_long_pack( drone.gcs().sysid(), drone.gcs().compid(), &msg, target_sysid, // 目标系统ID target_compid, // 目标组件ID MAV_CMD_NAV_TAKEOFF, // 指令类型 0, // 确认标记 0, 0, 0, 0, 0, 0, 0 // 参数 ); // 将消息写入缓冲区 uint8_t buffer[MAVLINK_MAX_PACKET_LEN]; uint16_t len = mavlink_msg_to_send_buffer(buffer, &msg); // 通过串口发送 uart->write(buffer, len); ``` --- ### **4. 验证消息发送** - **地面站软件**:使用Mission Planner或QGroundControl连接串口,查看接收消息。 - **MAVLink Inspector**:在地面站中启用`MAVLink Inspector`工具,实时解析消息内容。 - **逻辑分析仪/串口助手**:直接抓取串口数据,检查HEX格式是否符合MAVLink协议。 --- ### **注意事项** 1. **系统/组件ID**:确保目标ID与接收端匹配,否则消息会被忽略。 2. **消息频率控制**:避免高频发送(如超过50Hz)导致串口阻塞。 3. **线程安全**:在ArduPilot多线程环境中,需通过`hal.scheduler->register_io_process()`注册回调函数安全操作串口。 通过上述配置代码,即可实现ArduPilot通过串口发送自定义MAVLink消息
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值