PX4Ctrl进程
介绍
主进程
主要对象:LinearControl类的对象controller与PX4CtrlFSM类的对象fsm。
订阅mavros广播的消息遥控通道,在回调更新fsm中的rc_data。
cpp
ros::Subscriber rc_sub;
if (!param.takeoff_land.no_RC) // mavros will still publish wrong rc messages although no RC is connected
{
rc_sub = nh.subscribe<mavros_msgs::RCIn>("/mavros/rc/in",
10,
boost::bind(&RC_Data_t::feed, &fsm.rc_data, _1));
}
在src/realflight_modules/px4ctrl/src/input.cpp实现,原通道输出一般在1000 - 2000,模式相关的通道归一化为0-1。
reboot_cmd是RC通道8,与教程中紧急停止开关RC的通道7不是同一个。
cpp
for (int i = 0; i < 4; i++)
{
ch[i] = ((double)msg.channels[i] - 1500.0) / 500.0;
if (ch[i] > DEAD_ZONE)
ch[i] = (ch[i] - DEAD_ZONE) / (1 - DEAD_ZONE);
else if (ch[i] < -DEAD_ZONE)
ch[i] = (ch[i] + DEAD_ZONE) / (1 - DEAD_ZONE);
else
ch[i] = 0.0;
}
mode = ((double)msg.channels[4] - 1000.0) / 1000.0;
gear = ((double)msg.channels[5] - 1000.0) / 1000.0;
reboot_cmd = ((double)msg.channels[7] - 1000.0) / 1000.0;
使用ROS服务设置飞控模式、解锁与操控
cpp
fsm.set_FCU_mode_srv = nh.serviceClient<mavros_msgs::SetMode>("/mavros/set_mode");
fsm.arming_client_srv = nh.serviceClient<mavros_msgs::CommandBool>("/mavros/cmd/arming");
fsm.reboot_FCU_srv = nh.serviceClient<mavros_msgs::CommandLong>("/mavros/cmd/command");
# 在src/realflight_modules/px4ctrl/src/PX4CtrlFSM.h中PX4CtrlFSM类中,
ros::ServiceClient set_FCU_mode_srv;
状态机逻辑
根据state值、遥控通道、定位信息等控制state切换。
核心思路:
MANUAL_CTRL 状态(遥控器控制)
↓
多重检查条件
├─ 收到定位信息?
├─ 没有命令冲突?
├─ 速度合理?
↓
state = AUTO_HOVER
controller.resetThrustMapping()
set_hov_with_odom()
toggle_offboard_mode(true) ← ★ 切换控制模式
↓
AUTO_HOVER 状态(程序自动控制)
↓
px4ctrl可以向PX4发送控制指令
cpp
void PX4CtrlFSM::process()
{
ros::Time now_time = ros::Time::now();
Controller_Output_t u;
Desired_State_t des(odom_data);
bool rotor_low_speed_during_land = false;
// STEP1: state machine runs
switch (state)
{
case MANUAL_CTRL:
if (rc_data.enter_hover_mode) // Try to jump to AUTO_HOVER
{
if (!odom_is_received(now_time))
{
ROS_ERROR("[px4ctrl] Reject AUTO_HOVER(L2). No odom!");
break;
}
//...
}
}
}
Fast-Drone-250/src/realflight_modules/px4ctrl/src/input.cpp中函数 void RC_Data_t::feed(mavros_msgs::RCInConstPtr pMsg)切换与state相关的变量enter_hover_mode、
第5通道 mode > 0.75,设置enter_hover_mode,注释1下面的if-else被后面的覆盖了,
第6通道 gear > 0.75,is_command_mode = true
cpp
if (!have_init_last_mode)
{
have_init_last_mode = true;
last_mode = mode;
}
if (!have_init_last_gear)
{
have_init_last_gear = true;
last_gear = gear;
}
if (!have_init_last_reboot_cmd)
{
have_init_last_reboot_cmd = true;
last_reboot_cmd = reboot_cmd;
}
// 1
// 注意遥控第5通道档位控制模式,只能从小PWM值到高PWM值切换,才能使得enter_hover_mode为true。
// 这个条件的含义,在mode、last_mode均大于阈值,即通道5在拨下后,想要使得enter_hover_mode = true,得上下拨通道5
if (last_mode < API_MODE_THRESHOLD_VALUE && mode > API_MODE_THRESHOLD_VALUE)
enter_hover_mode = true;
else
enter_hover_mode = false;
if (mode > API_MODE_THRESHOLD_VALUE)
is_hover_mode = true;
else
is_hover_mode = false;
if (is_hover_mode)
{
if (last_gear < GEAR_SHIFT_VALUE && gear > GEAR_SHIFT_VALUE)
enter_command_mode = true;
else if (gear < GEAR_SHIFT_VALUE)
enter_command_mode = false;
if (gear > GEAR_SHIFT_VALUE)
is_command_mode = true;
else
is_command_mode = false;
}
src/realflight_modules/px4ctrl/config/ctrl_param_fpv.yaml中thrust_model的hover_percentage设置等于无人机重力的推力对应油门百分比,thr2acc_则是单位百分比产生的加速度,不包含重力加速度。
cpp
void
LinearControl::resetThrustMapping(void)
{
thr2acc_ = param_.gra / param_.thr_map.hover_percentage;
P_ = 1e6;
}
| 参数 | 符号 | 单位 | 物理意义 |
|---|---|---|---|
param_.gra |
ggg | m/s² | 重力加速度。无人机所在环境的重力加速度(通常为9.8 m/s²) |
param_.thr_map.hover_percentage |
ThoverT_{hover}Thover | 百分比 (0~1) | 悬停油门比例。指无人机保持静止悬停时所需的油门信号占总油门范围的百分比 |
thr2acc_ |
kthr2acck_{thr2acc}kthr2acc | m/s²/% | 推力-加速度映射系数 。表示单位油门信号能产生的加速度,是后续控制中用来从期望加速度计算所需油门的关键参数 |
P_ |
P0P_0P0 | 无量纲 | 递推最小二乘算法的初始协方差。用于后续的在线推力模型估计,初值较大表示对初始估计的不确定性很高 |
ROS消息控制起飞降落的实现
自定义控制起降的ROS消息,FAST-DRONE-250/src/utils/quadrotor_msgs/msg/TakeoffLand.msg,
前两项是枚举,第三项是用到的。
txt
uint8 TAKEOFF = 1
uint8 LAND = 2
uint8 takeoff_land_cmd
使用方式,rostopic pub -1 /px4ctrl/takeoff_land quadrotor_msgs/TakeoffLand "takeoff_land_cmd: 2" 或 rostopic pub -1 /px4ctrl/takeoff_land quadrotor_msgs/TakeoffLand "takeoff_land_cmd: 1"
PX4Ctrl接收该消息的回调函数,
cpp
void Takeoff_Land_Data_t::feed(quadrotor_msgs::TakeoffLandConstPtr pMsg)
{
msg = *pMsg;
rcv_stamp = ros::Time::now();
triggered = true;
takeoff_land_cmd = pMsg->takeoff_land_cmd;
}
主进程循环调用 void PX4CtrlFSM::process(),
先判断rc_data.enter_hover_mode,由于为flase,进入else,
前面执行takeoff.sh使得takeoff_land_data.takeoff_land_cmd == quadrotor_msgs::TakeoffLand::TAKEOFF成立,
获得cmd信息,
cpp
// 从HOVER状态切换到CMD_CTRL状态,
else if (rc_data.is_command_mode && cmd_is_received(now_time))
{
if (state_data.current_state.mode == "OFFBOARD")
{
state = CMD_CTRL;
des = get_cmd_des(); // 获取命令期望值
}
}
// 或者处于CMD_CTRL状态,
{
des = get_cmd_des();
}
在switch语句后,按步骤执行,
cpp
if (state == AUTO_HOVER || state == CMD_CTRL)
{
// controller.estimateThrustModel(imu_data.a, bat_data.volt, param);
controller.estimateThrustModel(imu_data.a, param);
}
// STEP3: solve and update new control commands
if (rotor_low_speed_during_land) // used at the start of auto takeoff
{
motors_idling(imu_data, u);
}
else
{
debug_msg = controller.calculateControl(des, odom_data, imu_data, u);
debug_msg.header.stamp = now_time;
debug_pub.publish(debug_msg);
}
// STEP4: publish control commands to mavros
if (param.use_bodyrate_ctrl)
{
publish_bodyrate_ctrl(u, now_time);
}
else
{
publish_attitude_ctrl(u, now_time);
}
综合条件成立,执行
c++
state = AUTO_TAKEOFF;
controller.resetThrustMapping();
set_start_pose_for_takeoff_land(odom_data);
toggle_offboard_mode(true); // toggle on offboard before arm
for (int i = 0; i < 10 && ros::ok(); ++i) // wait for 0.1 seconds to allow mode change by FMU // mark
{
ros::Duration(0.01).sleep();
ros::spinOnce();
}
if (param.takeoff_land.enable_auto_arm)
{
toggle_arm_disarm(true);
}
takeoff_land.toggle_takeoff_land_time = now_time;
函数 toggle_offboard_mode在 px4ctrl/src/PX4CtrlFSM.cpp,参数true意为进入offboard模式,反之退出并回到进入前的模式。
c++
bool PX4CtrlFSM::toggle_offboard_mode(bool on_off)
{
mavros_msgs::SetMode offb_set_mode;
if (on_off)
{
state_data.state_before_offboard = state_data.current_state;
if (state_data.state_before_offboard.mode == "OFFBOARD") // Not allowed
state_data.state_before_offboard.mode = "MANUAL";
offb_set_mode.request.custom_mode = "OFFBOARD";
if (!(set_FCU_mode_srv.call(offb_set_mode) && offb_set_mode.response.mode_sent))
{
ROS_ERROR("Enter OFFBOARD rejected by PX4!");
return false;
}
}
else
{
offb_set_mode.request.custom_mode = state_data.state_before_offboard.mode;
if (!(set_FCU_mode_srv.call(offb_set_mode) && offb_set_mode.response.mode_sent))
{
ROS_ERROR("Exit OFFBOARD rejected by PX4!");
return false;
}
}
return true;
// if (param.print_dbg)
// printf("offb_set_mode mode_sent=%d(uint8_t)\n", offb_set_mode.response.mode_sent);
}
逻辑背景:上述takeoff.sh执行前的操作及其顺序,遥控器5通道拨到内侧,六通道拨到下侧,油门打到中位
再执行roslaunch px4ctrl run_ctrl.launch
src/realflight_modules/px4ctrl/src/input.cpp
c++
if (last_mode < API_MODE_THRESHOLD_VALUE && mode > API_MODE_THRESHOLD_VALUE)
enter_hover_mode = true;
else
enter_hover_mode = false;
因此,enter_hover_mode = false;
double des_a_z = exp((delta_t - AutoTakeoffLand_t::MOTORS_SPEEDUP_TIME) * 6.0) * 7.0 - 7.0;
实现控制
配置文件"px4ctrl/config/ctrl_param_fpv.yaml"中use_bodyrate_ctrl: false,因此使用的是publish_attitude_ctrl(u, now_time);
细节
-
起飞
分三个状态,准备、上升、完成。
准备,3秒时长,

-
估计推力与加速度系数
-
计算控制输出
cpp
if (rotor_low_speed_during_land) // used at the start of auto takeoff
{
motors_idling(imu_data, u);
}
else
{
debug_msg = controller.calculateControl(des, odom_data, imu_data, u);
debug_msg.header.stamp = now_time;
debug_pub.publish(debug_msg);
}
有趣细节
- 检查IMU消息频率的写法很妙,小于100Hz就警告
cpp
// check the frequency
static int one_min_count = 9999;
static ros::Time last_clear_count_time = ros::Time(0.0);
if ( (now - last_clear_count_time).toSec() > 1.0 )
{
if ( one_min_count < 100 )
{
ROS_WARN("IMU frequency seems lower than 100Hz, which is too low!");
}
one_min_count = 0;
last_clear_count_time = now;
}
one_min_count ++;
- 判断降落的逻辑
cpp
void PX4CtrlFSM::land_detector(const State_t state, const Desired_State_t &des, const Odom_Data_t &odom)
{
static State_t last_state = State_t::MANUAL_CTRL;
if (last_state == State_t::MANUAL_CTRL && (state == State_t::AUTO_HOVER || state == State_t::AUTO_TAKEOFF))
{
takeoff_land.landed = false; // Always holds
}
last_state = state;
if (state == State_t::MANUAL_CTRL && !state_data.current_state.armed)
{
takeoff_land.landed = true;
return; // No need of other decisions
}
// land_detector parameters
constexpr double POSITION_DEVIATION_C = -0.5; // Constraint 1: target position below real position for POSITION_DEVIATION_C meters.
constexpr double VELOCITY_THR_C = 0.1; // Constraint 2: velocity below VELOCITY_MIN_C m/s.
constexpr double TIME_KEEP_C = 3.0; // Constraint 3: Time(s) the Constraint 1&2 need to keep.
static ros::Time time_C12_reached; // time_Constraints12_reached
static bool is_last_C12_satisfy;
if (takeoff_land.landed)
{
time_C12_reached = ros::Time::now();
is_last_C12_satisfy = false;
}
else
{
bool C12_satisfy = (des.p(2) - odom.p(2)) < POSITION_DEVIATION_C && odom.v.norm() < VELOCITY_THR_C;
if (C12_satisfy && !is_last_C12_satisfy)
{
time_C12_reached = ros::Time::now();
}
else if (C12_satisfy && is_last_C12_satisfy)
{
if ((ros::Time::now() - time_C12_reached).toSec() > TIME_KEEP_C) // Constraint 3 reached
{
takeoff_land.landed = true;
}
}
is_last_C12_satisfy = C12_satisfy;
}
}