longer95479@home:~$

px4ctrl understanding and tunning


状态机

Px4ctrl的状态机对象如下:

class PX4CtrlFSM
{
public:

    // somedata

private:
	State_t state; // Should only be changed in PX4CtrlFSM::process() function!
	AutoTakeoffLand_t takeoff_land;

	// ---- control related ----
	Desired_State_t get_hover_des();
	Desired_State_t get_cmd_des();

	// ---- auto takeoff/land ----
	void motors_idling(const Imu_Data_t &imu, Controller_Output_t &u);
	void land_detector(const State_t state, const Desired_State_t &des, const Odom_Data_t &odom); // Detect landing 
	void set_start_pose_for_takeoff_land(const Odom_Data_t &odom);
	Desired_State_t get_rotor_speed_up_des(const ros::Time now);
	Desired_State_t get_takeoff_land_des(const double speed);

	// ---- tools ----
	void set_hov_with_odom();
	void set_hov_with_rc();

	bool toggle_offboard_mode(bool on_off); // It will only try to toggle once, so not blocked.
	bool toggle_arm_disarm(bool arm); // It will only try to toggle once, so not blocked.
	void reboot_FCU();

	void publish_bodyrate_ctrl(const Controller_Output_t &u, const ros::Time &stamp);
	void publish_attitude_ctrl(const Controller_Output_t &u, const ros::Time &stamp);
	void publish_trigger(const nav_msgs::Odometry &odom_msg);
};

Input.cpp 里的 rc_data 的回调函数 feed()包含了通道的含义,涉及各通道切换的功能:

  • enter_hover_mode, is_hover_mode
  • enter_command_mode, is_command_mode

类成员函数 feed() 是如何被绑定为 ros subscriber 的回调函数的?使用 std::bind,例子如下,bind有三个参数,第一个参数指明哪一个类成员函数,第二个参数指明类的实例,第三个参数指明将接收到的 mavros_msgs::State 消息传递给 feed 函数的第一个参数。

ros::Subscriber state_sub =
        nh.subscribe<mavros_msgs::State>("/mavros/state",
                                         10,
                                         boost::bind(&State_Data_t::feed, &fsm.state_data, _1));

起飞流程

接下来梳理一下自动起飞的状态切换流程。首先要保证遥控器 5、6 通道已拨,分别表示进入 offboard 模式(也称 hover mode)和 command 模式,只有 两个条件 均满足,且 起飞使能(在yaml文件里配置)起飞信号触发 才会从 MANUAL_CTRL 切换到 AUTO_TAKEOFF 状态,当然中间还有很多检查。在进入 AUTO_TAKEOFF 前会重置 thr2acc_ 和设置起飞的起始点(已当前的里程计的值作为起点,四自由度),然后px4发送消息使其切换为 offboard 模式并自动解锁,记录当前时间戳 takeoff_land.toggle_takeoff_land_time = now_time;

void PX4CtrlFSM::set_start_pose_for_takeoff_land(const Odom_Data_t &odom)
{
	takeoff_land.start_pose.head<3>() = odom_data.p;
	takeoff_land.start_pose(3) = get_yaw_from_quaternion(odom_data.q);

	takeoff_land.toggle_takeoff_land_time = ros::Time::now();
}
struct AutoTakeoffLand_t
{
	bool landed{true};
	ros::Time toggle_takeoff_land_time;
	std::pair<bool, ros::Time> delay_trigger{std::pair<bool, ros::Time>(false, ros::Time(0))};
	Eigen::Vector4d start_pose;
	
	static constexpr double MOTORS_SPEEDUP_TIME = 3.0; // motors idle running for 3 seconds before takeoff
	static constexpr double TAKEOFF_SPEEDUP_TIME = 0.5; // Time to speed up during takeoff font stage
	static constexpr double DELAY_TRIGGER_TIME = 2.0;  // Time to be delayed when reach at target height
};

进入 AUTO_TAKEOFF 模式后,电机需要先怠速旋转一段时间,其速度指数增长,但提供很小的推力,用于警告周围的人。怠速旋转时长到了之后,则执行起飞,具体的控制可看下文的控制器章节,简单来说即目标位置是斜坡函数,目标速度是阶跃函数,也就是期望飞行器匀速上升。当到达指定高度后,状态将会在下一时间片切到 AUTO_HOVER,在切换之前执行 set_hov_with_odom(),将当前的里程计赋值给 hov_pose(4DoF)。

进入 AUTO_HOVER 后:

  • 如果没有里程计 或者 不是 hover mode,切回 MANUAL_CTRL
  • 如果是 command mode 且 收到了 cmd 且 没进入 emergency hover,则进一步判断是否是 offboard 模式,是的话则切换到 CMD_CTRL
  • 如果收到降落信号 或 emergency_hover,则进入 AUTO_LAND 状态
  • 最后才是 执行悬停控制
      set_hov_with_rc();
      des = get_hover_des();
      if ((rc_data.enter_command_mode) ||
          (takeoff_land.delay_trigger.first && now_time > takeoff_land.delay_trigger.second))
      {
          takeoff_land.delay_trigger.first = false;
          publish_trigger(odom_data.msg);
          ROS_INFO("\033[32m[px4ctrl] TRIGGER sent, allow user command.\033[32m");
      }
    

控制器

其位置速度P控制器是并联的,其实和串联的等价。(闭环传函推导、参数选择详见 onenote)

但如果速度环增加 I D,则串联和并联不再等价,目前的速度环增加了 I D,因此将控制器结构改成了串联形式。

改进 1:增加起飞的加速度前馈

起飞阶段的前期设置期望加速度,因为速度不可能突变,因此非零的期望加速度相当于前馈,符合系统的变化,提高相应速度。 \(a_{des} = 0.02||g||\sin(\frac{\pi}{t_{speedup}}\Delta t)\)

Desired_State_t PX4CtrlFSM::get_takeoff_land_des(const double speed)
{
	ros::Time now = ros::Time::now();
	double delta_t = (now - takeoff_land.toggle_takeoff_land_time).toSec() - (speed > 0 ? AutoTakeoffLand_t::MOTORS_SPEEDUP_TIME : 0); // speed > 0 means takeoff
	// takeoff_land.last_set_cmd_time = now;
	// takeoff_land.start_pose(2) += speed * delta_t;
	Desired_State_t des;
	des.p = takeoff_land.start_pose.head<3>() + Eigen::Vector3d(0, 0, speed * delta_t);
	des.v = Eigen::Vector3d(0, 0, speed);
	if (speed > 0) {
		double des_a_z = (delta_t < AutoTakeoffLand_t::TAKEOFF_SPEEDUP_TIME) ? (0.02 * param_.gra * sin(M_PI/AutoTakeoffLand_t::TAKEOFF_SPEEDUP_TIME * delta_t)) : 0;
		des.a = Eigen::Vector3d(0, 0, des_a_z);
	}
	else {
		des.a = Eigen::Vector3d::Zero();
	}
	des.j = Eigen::Vector3d::Zero();
	des.yaw = takeoff_land.start_pose(3);
	des.yaw_rate = 0.0;
	return des;
}

实验测试结果表明,确实能够提高起飞离开地面的速度,响应更快,xy偏离更少。

改进 2:在起飞阶段也进行油门估计

油门估计的原理是带记忆消逝的迭代最小二乘,利用一系列的油门值和加速度测量值,来估计二者对应直线的斜率(假设二者是线性关系)。

px4ctrl 里的油门估计存在一个假设,即无人机只受重力和油门推力,因此当无人机还受地面支持力时,是不能够使用油门估计的。

当电机怠速转动结束的一段时间后,此时推力大概率已经等于或大于重力,也就是此时不存在地面支撑力了,经验选择为怠速转动结束后的0.2s,运行油门估计函数。

实验表明,在起飞阶段使用油门估计,无论电池电压高低,四旋翼均能稳定地起飞和悬停,并且起飞到悬停的切换也不会带来跟之前一样的震荡。

改进 3:最大化相位裕度原则,简化调参

可将速度环的 P I 两个参数,转换为为阻尼因子 $\delta$ 的选择

\[P = \frac{1}{\tau \delta}\] \[I = \frac{1}{\tau^2\delta^3}\]

如果是离散化,I 还需要乘上控制时间间隔 $\Delta t_{ctrl}$,或者除以控制频率。

Total views. cowboys. Hits