OFFBOARD模式是PX4飞控系统中最强大的外部控制接口,它允许开发者通过uORB消息直接向飞控注入控制指令。我第一次接触这个功能时,被它简洁高效的实现方式惊艳到了——只需要两个关键消息就能让无人机完全听从外部指令。
要让无人机进入OFFBOARD模式,需要同时处理两个关键环节:模式切换和持续心跳。这里有个容易踩坑的地方:很多新手会忘记OFFBOARD模式需要持续发送心跳信号,导致无人机自动退出该模式。我建议使用单独的线程以至少2Hz的频率发布offboard_control_mode消息。
具体实现代码可以这样写:
cpp复制// 初始化发布器
uORB::Publication<offboard_control_mode_s> _ocm_pub{ORB_ID(offboard_control_mode)};
uORB::Publication<vehicle_command_s> _cmd_pub{ORB_ID(vehicle_command)};
// 进入OFFBOARD模式
void enter_offboard_mode() {
vehicle_command_s cmd{};
cmd.command = vehicle_command_s::VEHICLE_CMD_DO_SET_MODE;
cmd.param1 = 1.0f; // 主模式
cmd.param2 = PX4_CUSTOM_MAIN_MODE_OFFBOARD; // OFFBOARD模式标识
cmd.timestamp = hrt_absolute_time();
_cmd_pub.publish(cmd);
// 解锁电机
cmd.command = vehicle_command_s::VEHICLE_CMD_COMPONENT_ARM_DISARM;
cmd.param1 = 1.0f; // 1表示解锁
_cmd_pub.publish(cmd);
}
在实际项目中,我发现OFFBOARD模式最强大的地方在于它支持多种控制维度。你可以选择只控制位置,也可以混合控制姿态、速度等参数。这就好比开车时可以选择定速巡航,也可以完全手动控制方向盘和油门。
当无人机进入OFFBOARD模式后,最常用的就是位置控制了。这里有个重要概念需要理解:PX4的位置控制采用级联PID结构,外层是位置环,内层是速度环。这种结构使得控制更加平滑稳定。
我曾在项目中需要实现一个精确的悬停功能,经过多次调试发现几个关键点:
一个典型的位置控制实现如下:
cpp复制// 发布位置设定点
void publish_position_setpoint(float x, float y, float z) {
vehicle_local_position_setpoint_s sp{};
sp.x = x; // 北向位置
sp.y = y; // 东向位置
sp.z = z; // 地轴位置(负值表示高度)
sp.timestamp = hrt_absolute_time();
// 同时需要激活位置控制模式
offboard_control_mode_s ocm{};
ocm.position = true;
ocm.timestamp = hrt_absolute_time();
_ocm_pub.publish(ocm);
_traj_pub.publish(sp);
}
对于更复杂的轨迹跟踪,我推荐使用时间参数化的方法。比如要实现一个正方形轨迹,可以这样处理:
PX4的COMMAND系统提供了丰富的控制指令,远比大多数人想象的强大。根据我的经验,这些指令可以归纳为三大类:
最常用的模式切换命令实现起来非常简单:
cpp复制// 切换到自动降落模式
void switch_to_land_mode() {
vehicle_command_s cmd{};
cmd.command = vehicle_command_s::VEHICLE_CMD_DO_SET_MODE;
cmd.param1 = 1.0f; // 主模式
cmd.param2 = PX4_CUSTOM_MAIN_MODE_AUTO; // 自动模式
cmd.param3 = PX4_CUSTOM_SUB_MODE_AUTO_LAND; // 降落子模式
cmd.timestamp = hrt_absolute_time();
_cmd_pub.publish(cmd);
}
在VTOL(垂直起降)无人机开发中,旋翼模式与固定翼模式的切换尤为关键。经过多次实测,我发现模式转换时需要注意:
VTOL转换的代码示例如下:
cpp复制// 从旋翼模式切换到固定翼模式
void transition_to_fixed_wing() {
vehicle_command_s cmd{};
cmd.command = vehicle_command_s::VEHICLE_CMD_DO_VTOL_TRANSITION;
cmd.param1 = vtol_vehicle_status_s::VEHICLE_VTOL_STATE_FW; // 固定翼模式
cmd.timestamp = hrt_absolute_time();
_cmd_pub.publish(cmd);
}
随着PX4版本的更新,传统的PWM直接输出方式已经被更现代的actuator_controls消息所取代。这个变化让很多老用户感到不适应,但实际上新方案提供了更多优势:
在新版本中控制执行器的正确姿势是使用actuator_controls消息:
cpp复制// 新版执行器控制
void set_actuator_output() {
actuator_controls_s ctrls{};
ctrls.control[0] = 0.5f; // 副翼
ctrls.control[1] = -0.3f; // 升降舵
ctrls.control[2] = 0.8f; // 油门
ctrls.timestamp = hrt_absolute_time();
uORB::Publication<actuator_controls_s> _act_pub{ORB_ID(actuator_controls_0)};
_act_pub.publish(ctrls);
}
对于需要精确控制舵机的场景,比如机械臂或相机云台,我推荐使用专用的话题。PX1.14之后提供了actuator_outputs话题,可以直接控制每个PWM通道的输出值:
cpp复制// 精确控制PWM输出
void set_pwm_output(uint8_t channel, float value) {
actuator_outputs_s outputs{};
outputs.output[channel] = value; // 归一化的PWM值(0~1)
outputs.timestamp = hrt_absolute_time();
uORB::Publication<actuator_outputs_s> _out_pub{ORB_ID(actuator_outputs)};
_out_pub.publish(outputs);
}
在最近的一个农业无人机项目中,我们使用这套接口实现了精准的喷洒控制。通过实时调节PWM占空比,可以精确控制药液的流量,相比旧方案节省了约15%的农药使用量。