3、无人机基础控制:起飞与降落(action插件)、上锁/解锁、紧急停止、获取飞行状态
各位同学,欢迎来到第三章。这一章我们聊点实在的——怎么让无人机真正动起来。
前面两章我们把环境搭好了,也把MAVSDK的通信链路搞通了。但说实话,光能连上飞控,飞机不动,那跟块砖头没啥区别。今天我们就来搞定起飞、降落、上锁解锁、紧急停止,还有怎么实时看飞机的状态。
我个人习惯把这一章叫做「让飞机听话的第一步」。你想想看,如果连起飞降落都控制不好,后面的自主航线根本无从谈起。
本章核心知识点一览:
- Action插件的基本使用
- 起飞(arm + takeoff)与降落(land)
- 上锁(disarm)与解锁(arm)
- 紧急停止(kill)—— 保命技能
- 获取飞行状态(in_air, armed, flight_mode等)
3.1 Action插件——控制飞机的遥控器
MAVSDK里,所有跟飞行控制相关的操作都集中在Action插件里。你可以把它理解成「程序员的遥控器」——只不过你不再用手掰摇杆,而是用代码发指令。
初始化Action插件很简单,跟上一章我们初始化System一样:
#include <mavsdk/mavsdk.h>
#include <mavsdk/plugins/action/action.h>
using namespace mavsdk;
// 假设我们已经有了一个System对象 system
Action action(system);
嗯,这里要注意:Action插件依赖一个已经连接好的System对象。如果你还没连上飞控,后面的所有操作都会报错。我在项目里见过不少新手上来就调takeoff(),结果飞控没连上,程序直接崩溃——所以记得先检查连接状态。
3.2 起飞与降落——最基础的操作
起飞和降落,说白了就是两个函数调用。但背后的逻辑,我建议你好好理解一下。
3.2.1 解锁并起飞
在PX4或ArduPilot里,起飞前必须先解锁(arm)。MAVSDK把这两个步骤封装成了两个独立的函数:
// 1. 解锁
Action::Result arm_result = action.arm();
if (arm_result == Action::Result::Success) {
std::cout << "解锁成功!" << std::endl;
} else {
std::cerr << "解锁失败: " << arm_result << std::endl;
return;
}
// 2. 起飞到10米高度
Action::Result takeoff_result = action.takeoff();
if (takeoff_result == Action::Result::Success) {
std::cout << "起飞指令已发送" << std::endl;
} else {
std::cerr << "起飞失败: " << takeoff_result << std::endl;
}
为什么要把解锁和起飞分开?
我个人习惯是:解锁后先等1-2秒,确认电机转动正常,再发起飞指令。有一次我在项目里解锁后立刻起飞,结果有个电机没转起来,飞机直接侧翻——从那以后我都在解锁和起飞之间加个延时。
小技巧:你可以用action.arm()的返回值来判断解锁是否成功。常见的失败原因有:
- 飞控未处于「预解锁」状态(比如遥控器没校准)
- 电池电压过低
- GPS信号不好(某些飞控要求GPS锁定才能解锁)
3.2.2 降落
降落相对简单,调用action.land()即可:
Action::Result land_result = action.land();
if (land_result == Action::Result::Success) {
std::cout << "降落指令已发送" << std::endl;
} else {
std::cerr << "降落失败: " << land_result << std::endl;
}
降落过程中,飞控会自动控制油门和姿态。你不需要做任何额外操作。但有一点要注意:降落完成后,飞控会自动上锁(disarm),这时候电机就停了。
⚠️ 重要提醒:降落指令发出后,飞控会以默认的下降速度(通常是0.5-1m/s)下降。如果你在室内或狭小空间操作,建议先设置降落速度。我在一个仓库项目里就遇到过——默认下降速度太快,飞机直接砸到了货架上。
3.3 上锁与解锁——安全第一
上锁(disarm)和解锁(arm)是无人机最基础的安全机制。解锁意味着电机可以转动,上锁则禁止电机转动。
代码实现:
// 解锁
Action::Result arm_result = action.arm();
// 上锁
Action::Result disarm_result = action.disarm();
你可能会问:什么时候需要手动上锁?
举个例子:飞机降落后,飞控会自动上锁。但如果你在测试时,飞机还没起飞就想停止电机,或者降落时飞控没自动上锁(某些异常情况),你就需要手动调用disarm()。
我曾经在野外测试时,飞机降落在草地上,飞控检测到地面接触但没自动上锁。我赶紧手动调用了disarm(),才避免电机一直空转烧坏。嗯,这种场景虽然不常见,但遇到了就是救命。
3.4 紧急停止——最后的保命手段
紧急停止(kill)是无人机控制的「核按钮」。一旦调用,飞控会立即切断所有电机的动力输出——飞机直接自由落体。
Action::Result kill_result = action.kill();
if (kill_result == Action::Result::Success) {
std::cout << "紧急停止已触发" << std::endl;
} else {
std::cerr << "紧急停止失败: " << kill_result << std::endl;
}
⚠️ 极度危险!紧急停止不是降落!不是降落!不是降落!
调用kill()后,飞控不会尝试任何姿态控制,飞机直接掉下来。只有在以下情况才使用:
- 飞机失控,即将撞向人群
- 电机或螺旋桨出现严重故障
- 其他任何可能导致重大损失的情况
我在测试中只用过一次——那次飞机在空中突然偏航,怎么都拉不回来,我果断按了紧急停止。飞机从20米高度掉下来,摔坏了机架和电池,但至少没伤到人。事后想想,这个决定是对的。
3.5 获取飞行状态——实时掌握飞机动态
光会发指令还不够,你还得知道飞机当前在干嘛。MAVSDK的Telemetry插件提供了丰富的状态信息。
常用的状态包括:
| 状态字段 | 类型 | 说明 |
|---|---|---|
in_air |
bool | 是否在空中(true=飞行中,false=在地面) |
armed |
bool | 是否已解锁(true=电机可转动) |
flight_mode |
string | 当前飞行模式(如"Mission"、"Hold"、"Land"等) |
landed_state |
enum | 着陆状态(Unknown、OnGround、InAir、TakingOff、Landing) |
获取这些状态的代码示例:
#include <mavsdk/plugins/telemetry/telemetry.h>
Telemetry telemetry(system);
// 订阅飞行状态(推荐使用回调方式)
telemetry.subscribe_flight_mode([](Telemetry::FlightMode flight_mode) {
std::cout << "当前飞行模式: " << flight_mode << std::endl;
});
telemetry.subscribe_armed([](bool armed) {
std::cout << (armed ? "已解锁" : "已上锁") << std::endl;
});
telemetry.subscribe_landed_state([](Telemetry::LandedState landed_state) {
std::cout << "着陆状态: " << landed_state << std::endl;
});
// 或者一次性获取当前状态(非阻塞)
Telemetry::FlightMode current_mode = telemetry.flight_mode();
bool is_armed = telemetry.armed();
bool is_in_air = telemetry.in_air();
实用技巧:我建议你在起飞前先检查in_air和armed状态。如果飞机已经在空中,再调用takeoff()会报错。同样,如果飞机已经解锁,再调用arm()也会失败。写代码时养成「先检查,后操作」的习惯,能省去很多调试时间。
3.6 综合示例:一个完整的起飞-悬停-降落流程
最后,我把今天讲的内容串起来,写一个完整的示例:
#include <mavsdk/mavsdk.h>
#include <mavsdk/plugins/action/action.h>
#include <mavsdk/plugins/telemetry/telemetry.h>
#include <iostream>
#include <chrono>
#include <thread>
using namespace mavsdk;
int main() {
Mavsdk mavsdk;
ConnectionResult conn_result = mavsdk.add_any_connection("udp://:14540");
if (conn_result != ConnectionResult::Success) {
std::cerr << "连接失败!" << std::endl;
return 1;
}
// 等待飞控连接
std::this_thread::sleep_for(std::chrono::seconds(2));
auto system = mavsdk.first_autopilot(10.0);
if (!system) {
std::cerr << "未发现飞控" << std::endl;
return 1;
}
Action action(system.value());
Telemetry telemetry(system.value());
// 订阅状态
telemetry.subscribe_armed([](bool armed) {
std::cout << (armed ? "已解锁" : "已上锁") << std::endl;
});
// 1. 解锁
std::cout << "正在解锁..." << std::endl;
Action::Result arm_result = action.arm();
if (arm_result != Action::Result::Success) {
std::cerr << "解锁失败" << std::endl;
return 1;
}
// 等待1秒,确认电机转动
std::this_thread::sleep_for(std::chrono::seconds(1));
// 2. 起飞到10米
std::cout << "正在起飞..." << std::endl;
Action::Result takeoff_result = action.takeoff();
if (takeoff_result != Action::Result::Success) {
std::cerr << "起飞失败" << std::endl;
return 1;
}
// 悬停5秒
std::this_thread::sleep_for(std::chrono::seconds(5));
// 3. 降落
std::cout << "正在降落..." << std::endl;
Action::Result land_result = action.land();
if (land_result != Action::Result::Success) {
std::cerr << "降落失败" << std::endl;
return 1;
}
// 等待降落完成
std::this_thread::sleep_for(std::chrono::seconds(10));
std::cout << "流程结束" << std::endl;
return 0;
}
这个示例虽然简单,但涵盖了今天讲的所有核心操作。你可以在仿真环境里先跑一遍,看看效果。
好了,这一章的内容就到这里。起飞降落、上锁解锁、紧急停止、状态获取——这些是无人机控制的「基本功」。别觉得简单,我见过太多人在这些基础操作上翻车。多练练,把每个函数的返回值都打印出来看看,你会对飞控的行为有更深的理解。