PIXHAWK姿态控制整体框架及控制算法的深度解析

    上文写了PIXHAWK的位置控制算法,该文对其姿态控制进行深入解析。其中姿态控制主要看PX4的mc_att_control_main.cpp,其中姿态控制主要执行函数为MulticopterAttitudeControl::control_attitude(float dt)。姿态控制整体框架:角度环采用P控制,角速度采用PID控制(但含有很多先进PID的控制方法,文章会提到)。

        姿态控制首先要获得位置控制计算而得的期望旋转矩阵R_sp和当前的姿态旋转矩阵R。

代码:R_sp.set(_v_att_sp.R_body);

          R.set(_v_att.R);

其次便要获取两个旋转矩阵之间的误差角度,根据方向余弦矩阵求取角度误差,PX4中思想是先对齐Z轴(由于Z轴航向的响应相对横滚和俯仰较慢,故先对其Z轴),然后求取横滚和俯仰维度的角度误差,最后再对航向误差进行相对补偿,这样飞机的行驶路径相对最小,否则飞机在横滚、俯仰补偿的同时转动航向飞机转动行驶路线是个曲线,路径相对较长(个人对矩阵旋转的运动学理解)。英文解释:/* try to move thrust vector shortest way, because yaw response is slower than roll/pitch */,

/* calculate rotation matrix after roll/pitch only rotation */。

        算法实现:首先对齐Z轴,求取横滚和俯仰的角度偏差:math::Vector<3> e_R = R.transposed() * (R_z % R_sp_z);

其中R_z % R_sp_z矩阵叉乘产生一个新的平面(新的参考系),R.transposed()为R的转置(旋转矩阵R见下图),航向z轴与该参考系垂直,故此时求得的e_R(2)为0,只能求出对应的横滚、俯仰的角度偏差向量(不是实际的偏差值)。


接下来对实际的横滚,俯仰偏差值进行求解。根据叉乘、点乘公式,如下:

求取横滚、俯仰总旋转角度的正弦和余弦,代码:

float e_R_z_sin = e_R.length();

float e_R_z_cos = R_z * R_sp_z;

接下来PX4角度控制中依据角度偏差大小,对于响应方法进行了分类求取角度偏差。

        第一种:当横滚、俯仰总体角度未达到180度时(当然飞机默认横滚、俯仰最大期望角度45度,一般不会达到第二种情况),依旧正切求取横滚、俯仰总体偏差角度,代码:

float e_R_z_angle = atan2f(e_R_z_sin, e_R_z_cos);

然后对之前求取的e_R进行归一化,分别求取横滚、俯仰对应的角度偏差,代码:

 e_R_z_axis = e_R / e_R_z_sin;
e_R = e_R_z_axis * e_R_z_angle;

最后依据求得的横滚、俯仰的角度偏差对旋转矩阵进行旋转,得到新的旋转矩阵R_rp,与原期望矩阵R_sp有相同的Z轴,然后计算航向角度偏差,原文注释讲解:/* R_rp and R_sp has the same Z axis, calculate yaw error */。在求取R_sp时,采用龙格库塔法,即有等效旋转单位矢量e_R_z_axis构造反对称矩阵math::Matrix<3, 3> e_R_cp(在秦永元的《惯性导航》第二版书p252中有讲解)来求取新的旋转矩阵R_rp。书中描写如下:


mahony的论文中也有提到,小的旋转可以由反对称矩阵来表示,文章截图如下:


代码实现如下:

math::Matrix<3, 3> e_R_cp;
e_R_cp.zero();
e_R_cp(0, 1) = -e_R_z_axis(2);
e_R_cp(0, 2) = e_R_z_axis(1);
e_R_cp(1, 0) = e_R_z_axis(2);
e_R_cp(1, 2) = -e_R_z_axis(0);
e_R_cp(2, 0) = -e_R_z_axis(1);

e_R_cp(2, 1) = e_R_z_axis(0);

/* rotation matrix for roll/pitch only rotation */

R_rp = R * (_I + e_R_cp * e_R_z_sin + e_R_cp * e_R_cp * (1.0f - e_R_z_cos));

最后依据对其Z轴的R_rp和R_sp求取航向角偏差,代码:

e_R(2) = atan2f((R_rp_x % R_sp_x) * R_sp_z, R_rp_x * R_sp_x) * yaw_w;

这里R_rp_x * R_sp_x相当于求取机头旋转偏差的余弦,R_rp_x % R_sp_x相当于求取机头旋转偏差的正弦,但仍是一向量,点乘R_sp_z后将该向量分解到z轴(求解思想与e_R = R.transposed() * (R_z % R_sp_z)一致,点乘可以互换位置(R_rp_x % R_sp_x) * R_sp_z的结果恰好是R_rp_x % R_sp_x的长度sinR_rp_x * R_sp_x为cos),yaw_w是一个与横滚、俯仰角旋转有关的衰减系数,其目的是先进行横滚、俯仰角的旋转、其次偏航角,从而旋转路径最短,其代码:

float yaw_w = R_sp(2, 2) * R_sp(2, 2);

旋转矩阵可见下式:


        当横滚、俯仰总体角度偏差较大时>180度,采用第二种求取角度偏差的方法,不考虑旋转顺序,这里采用四元数求取角度偏差(小角度的旋转可以由反对称矩阵来表示,可能不适合大角度旋转,同时可能龙格库塔法不适合大角度旋转),原文注释:/* for large thrust vector rotations use another rotation method:
* calculate angle and axis for R -> R_sp rotation directly */

PX4中由四元数求取角度偏差的方法:首先求取由当前旋转矩阵旋转到目标旋转矩阵(R -> R_sp)的过渡矩阵,从而将其转换成四元数,代码:q.from_dcm(R.transposed() * R_sp);(这里R的逆=R的转置,见下图)


其中四元数可以由三角式来表示:


求取四元数的虚部,归一化后根据三角表达式依据正切求取旋转角度,代码如下:

                e_R_d = q.imag();
e_R_d.normalize();

e_R_d *= 2.0f * atan2f(e_R_d.length(), q(0));

最后将第一种算法求得的结果与该种算法的结果互补一下,代码:

                e_R = e_R * (1.0f - direct_w) + e_R_d * direct_w;

其中互补系数与横滚、俯仰角度有关:direct_w = e_R_z_cos * e_R_z_cos * yaw_w;(可想当旋转角度>180度但<270度时后者作用减小,但>270度时后者作用变大,该者旋转计算方法很适合空翻)。

        求取到角度偏差后,便需要将其转化成角速度控制输入(P控制)即角速度设定值,代码:

                /* calculate angular rates setpoint */

        _rates_sp = _params.att_p.emult(e_R);

其中emult代表乘_params.att_p是角度环的一个比例控制参数。接下来对期望角速度进行限幅,由于航向响应较慢,故对航向加入前馈控制(这与前面提到的先进行横滚、俯仰的旋转不矛盾,最后的偏航响应也要跟上),代码:

        /* limit rates */
for (int i = 0; i < 3; i++) {
_rates_sp(i) = math::constrain(_rates_sp(i), -_params.mc_rate_max(i), _params.mc_rate_max(i));
}
/* feed forward yaw setpoint rate */

_rates_sp(2) += _v_att_sp.yaw_sp_move_rate * yaw_w * _params.yaw_ff;

接着依据PID求取角速度环的输出:

_att_control = _params.rate_p.emult(rates_err) + _params.rate_d.emult(_rates_prev - rates) / dt + _rates_int + _params.rate_ff.emult(_rates_sp - _rates_sp_prev) / dt;

分别对应比例、微分、积分还有期望值的微分对应的前馈。

对于积分,PX4中进行了积分分离和抗积分饱和的操作,代码:

/* update integral only if not saturated on low limit and if motor commands are not saturated */

if (_thrust_sp > MIN_TAKEOFF_THRUST && !_motor_limits.lower_limit && !_motor_limits.upper_limit ) {
for (int i = 0; i < 3; i++) {
if (fabsf(_att_control(i)) < _thrust_sp) {
float rate_i = _rates_int(i) + _params.rate_i(i) * rates_err(i) * dt;
if (isfinite(rate_i) && rate_i > -RATES_I_LIMIT && rate_i < RATES_I_LIMIT &&
   _att_control(i) > -RATES_I_LIMIT && _att_control(i) < RATES_I_LIMIT) {
_rates_int(i) = rate_i;
}
}
}

}

其中_thrust_sp > MIN_TAKEOFF_THRUST指须离地后采用积分;!_motor_limits.lower_limit && !_motor_limits.upper_limit 指计算得到的对应电机的输入值既未小于下限,也未大于上限才可以进行积分;if (fabsf(_att_control(i)) < _thrust_sp)为分通道角速度环计算输出值小于总体推力设置值才进行积分(角速度的输出对应力的量纲?后面需要思考),这三条限制为积分分离。(!_motor_limits.lower_limit && !_motor_limits.upper_limit 会在下一集PX4_mixer通道分配的深度解析中进行讲解

其中if (isfinite(rate_i) && rate_i > -RATES_I_LIMIT && rate_i < RATES_I_LIMIT &&
    _att_control(i) > -RATES_I_LIMIT && _att_control(i) < RATES_I_LIMIT) 为抗积分饱和,即进行积分限幅,输出值达到限幅时也进行积分限幅,可谓双重抗积分饱和,这也是相对底层算法的精细之处。下集将进行PX4_mixer通道分配的深度解析,也谓输入到电机的最底层算法啦大笑,涉及到PX4通道分配中的一个比例钳位的思想。

猜你喜欢

转载自blog.csdn.net/u014313096/article/details/79836919