PX4源码阅读笔记:姿态外环PID控制器:AttitudeControl类

简介

实现了基于四元数的姿态外环PID控制器(实际上是P+前馈)。
类的定义及实现存放在:mc_att_control/AttitudeControl
控制器输入为NED坐标系下四元数表示的当前姿态q和期望姿态qd,输出为机体坐标系下三轴的角速度。
如何由NED坐标系下表示的q和q_d得到在机体坐标系下的姿态误差是关键,本来这个误差由q.inversed() * qd就可以得到,但是考虑了优先控制roll和pitch,就有一些复杂,下面进行解释。
注:网上查到的这部分的源码解读大部分是旧版本的,本文基于V1.10.1版本。

类的定义

class AttitudeControl
{
public:
    AttitudeControl() = default;
    ~AttitudeControl() = default;

    /**
     * Set proportional attitude control gain
     * @param proportional_gain 3D vector containing gains for roll, pitch, yaw
     */
    void setProportionalGain(const matrix::Vector3f &proportional_gain);

    /**
     * Set hard limit for output rate setpoints
     * @param rate_limit [rad/s] 3D vector containing limits for roll, pitch, yaw
     */
    void setRateLimit(const matrix::Vector3f &rate_limit) { _rate_limit = rate_limit; }

    /**
     * Run one control loop cycle calculation
     * @param q estimation of the current vehicle attitude unit quaternion
     * @param qd desired vehicle attitude setpoint
     * @param yawspeed_feedforward [rad/s] yaw feed forward angular rate in world frame
     * @return [rad/s] body frame 3D angular rate setpoint vector to be executed by the rate controller
     */
    matrix::Vector3f update(matrix::Quatf q, matrix::Quatf qd, float yawspeed_feedforward);

private:
    matrix::Vector3f _proportional_gain;
    matrix::Vector3f _rate_limit;
    float _yaw_w = 0.0f; /**< yaw weight [0,1] to prioritize roll and pitch */
}

setProportionalGain

作用是给P参数赋值。
由于控制策略是优先控制roll和pitch,所以并不是直接把proportional_gain赋值给私有变量_proportional_gain。
具体方式为:

  • 把输入参数proportional_gain的前两项(roll和pitch的P参数)直接赋值给_proportional_gain的对应位置。
  • 对输入参数proportional_gain的前两项(roll和pitch的P参数)求均值,赋值给_proportional_gain中的yaw的P参数的位置。
  • 用私有变量_yaw_w去储存给定yaw的P参数与求均值计算出来的yaw的P参数的比值(经过0-1constrain),用来表征优先控制roll和pitch的程度(后面会介绍)。
void AttitudeControl::setProportionalGain(const matrix::Vector3f &proportional_gain)
{
    _proportional_gain = proportional_gain;

    // prepare yaw weight from the ratio between roll/pitch and yaw gains
    const float roll_pitch_gain = (proportional_gain(0) + proportional_gain(1)) / 2.f;
    _yaw_w = math::constrain(proportional_gain(2) / roll_pitch_gain, 0.f, 1.f);

    _proportional_gain(2) = roll_pitch_gain;
}

setRateLimit

给控制器输出的角速度设置limit

void setRateLimit(const matrix::Vector3f &rate_limit) { _rate_limit = rate_limit; }

update

PID控制器的实现。

输入
  • matrix::Quatf q:当前姿态在NED坐标系下的四元数表示,由姿态解算得到
  • matrix::Quatf qd:期望姿态在NED坐标系下的四元数表示,不同模式下来源不同
  • const float yawspeed_feedforward:yaw的前馈增益
输出
  • 机体坐标系下的三轴角速度(Vector3f)
matrix::Vector3f AttitudeControl::update(matrix::Quatf q, matrix::Quatf qd, const float yawspeed_feedforward)
{
    // ensure input quaternions are exactly normalized because acosf(1.00001) == NaN
    q.normalize();
    qd.normalize();

    // calculate reduced desired attitude neglecting vehicle's yaw to prioritize roll and pitch
    const Vector3f e_z = q.dcm_z();
    const Vector3f e_z_d = qd.dcm_z();
    Quatf qd_red(e_z, e_z_d);

    if (fabsf(qd_red(1)) > (1.f - 1e-5f) || fabsf(qd_red(2)) > (1.f - 1e-5f)) {
        // In the infinitesimal corner case where the vehicle and thrust have the completely opposite direction,
        // full attitude control anyways generates no yaw input and directly takes the combination of
        // roll and pitch leading to the correct desired yaw. Ignoring this case would still be totally safe and stable.
        qd_red = qd;

    } else {
        // transform rotation from current to desired thrust vector into a world frame reduced desired attitude
        qd_red *= q;
    }

    // mix full and reduced desired attitude
    Quatf q_mix = qd_red.inversed() * qd;
    q_mix.canonicalize();
    // catch numerical problems with the domain of acosf and asinf
    q_mix(0) = math::constrain(q_mix(0), -1.f, 1.f);
    q_mix(3) = math::constrain(q_mix(3), -1.f, 1.f);
    qd = qd_red * Quatf(cosf(_yaw_w * acosf(q_mix(0))), 0, 0, sinf(_yaw_w * asinf(q_mix(3))));

    // quaternion attitude control law, qe is rotation from q to qd
    const Quatf qe = q.inversed() * qd;

    // using sin(alpha/2) scaled rotation axis as attitude error (see quaternion definition by axis angle)
    // also taking care of the antipodal unit quaternion ambiguity
    const Vector3f eq = 2.f * qe.canonical().imag();

    // calculate angular rates setpoint
    matrix::Vector3f rate_setpoint = eq.emult(_proportional_gain);

    // Feed forward the yaw setpoint rate.
    // yaw_sp_move_rate is the feed forward commanded rotation around the world z-axis,
    // but we need to apply it in the body frame (because _rates_sp is expressed in the body frame).
    // Therefore we infer the world z-axis (expressed in the body frame) by taking the last column of R.transposed (== q.inversed)
    // and multiply it by the yaw setpoint rate (yaw_sp_move_rate).
    // This yields a vector representing the commanded rotatation around the world z-axis expressed in the body frame
    // such that it can be added to the rates setpoint.
    rate_setpoint += q.inversed().dcm_z() * yawspeed_feedforward;

    // limit rates
    for (int i = 0; i < 3; i++) {
        rate_setpoint(i) = math::constrain(rate_setpoint(i), -_rate_limit(i), _rate_limit(i));
    }

    return rate_setpoint;
}
下面逐行分析

    q.normalize();
    qd.normalize();

将q和qd单位化


    const Vector3f e_z = q.dcm_z();
    const Vector3f e_z_d = qd.dcm_z();
    Quatf qd_red(e_z, e_z_d);

q.dcm_z()得到的是将q转化为方向余弦矩阵,并取第三列形成的向量。
所以根据方向余弦的定义,e_z和e_z_d即为当前机体坐标系和期望机体坐标系的z轴在NED坐标系下的表示。

第三行调用了Quatf类的其中一种构造函数,qd_red代表的是从e_z旋转到e_z_d的四元数。
可以这样理解:当前姿态坐标系经过qd_red代表的旋转之后,两个坐标系(当前姿态坐标系和期望姿态坐标系)的z轴是对齐的。我们把这个z轴对齐的状态定义为一个中间状态。可以认为qd_red包含了roll和pitch的误差信息
注意此时qd_red只是代表了一个旋转(q->qd),并不像q和qd代表了姿态(NED->q和NED->qd)。
由于于e_z和e_z_d是在NED坐标系下的向量,所以得到的qd_red也是在NED下表示的。


    if (fabsf(qd_red(1)) > (1.f - 1e-5f) || fabsf(qd_red(2)) > (1.f - 1e-5f)) {
        // In the infinitesimal corner case where the vehicle and thrust have the completely opposite direction,
        // full attitude control anyways generates no yaw input and directly takes the combination of
        // roll and pitch leading to the correct desired yaw. Ignoring this case would still be totally safe and stable.
        qd_red = qd;

    } else {
        // transform rotation from current to desired thrust vector into a world frame reduced desired attitude
        qd_red *= q;
    }

接下来是对一种极端情况的考虑

  • 先看else里的一般情况:qd_red=qd_red*q,由四元数乘法的几何意义可知,此时得到的qd_red即代表了NED系表示下的中间状态(z轴对齐的状态,NED->q_red)。
  • 再看if里的情况:if里代表了一种极端情况,此时q_red接近(0,1,0,0)或者(0,0,1,0),由四元数代表旋转的意义知道,q_red代表的旋转角度为180°,这时不考虑偏航误差,直接把qd赋值给q_red。

    Quatf q_mix = qd_red.inversed() * qd;

q_mix代表从中间状态qd_red到期望姿态qd的旋转四元数在qd_red系下的表示,可以表征yaw的误差。下面进行解释。

  • 首先设q_1代表了从中间状态qd_red到期望姿态qd的旋转在NED系下的表示,那么由四元数乘法的几何意义,有q_1*qd\_red=qd,因此q_1=qd*qd\_red^{-1}
  • 那么从q_1到q_mix差一个从NED系到qd_red系的坐标系变换,因此q\_mix=qd\_red^{-1}*q_1*qd\_red=qd\_red^{-1}*qd,即为上面代码所示。
  • 注意要区分向量旋转与坐标系变换的区别!

    q_mix.canonicalize();

对q_mix规范化。
因为同一个旋转可以由互为相反数的两个四元数表示。为了避免奇异,进行统一表示。


    q_mix(0) = math::constrain(q_mix(0), -1.f, 1.f);
    q_mix(3) = math::constrain(q_mix(3), -1.f, 1.f);
    qd = qd_red * Quatf(cosf(_yaw_w * acosf(q_mix(0))), 0, 0, sinf(_yaw_w * asinf(q_mix(3))));

由前面我们知道,q_mix代表从中间状态qd_red到期望姿态qd的旋转四元数在qd_red系下的表示,可以表征yaw的误差。,由于中间状态qd_red坐标系与期望姿态qd坐标系的z轴是重合的,所以q_mix在qd_red系下的表示应该是q\_mix=(\cos\frac{\theta}{2},0,0,\sin\frac{\theta}{2})\theta为旋转角,即只有q_mix(0)和q_mix(3)的值是我们需要的。

  • 前两行对q_mix进行constrain
  • 第三行实际为:
    qd=qd\_red*(\cos\frac{w\theta}{2},0,0,\sin\frac{w\theta}{2}),其中w=_yaw_w,前面说了,这是AttitudeControl类的私有变量,在setProportionalGain方法里进行了计算, 用来表征优先控制roll和pitch的程度,范围是[0,1],这里就可以看出解释:
    因为w在0到1之间,w\theta\leq\theta,经过这样操作,yaw的误差被缩小计算了。

所以经过了_yaw_w参数作用后的,在反推回来得到的qd与最开始给定的qd以经有所变化,即最后算出来的roll和pitch的期望角速度相同,但计算出来的yaw的期望角速度根据_yaw_w的大小会有所减小,这样就实现了优先控制roll和pitch。


    const Quatf qe = q.inversed() * qd;

qe即为从q旋转到qd的的四元数在机体坐标系下的表示,即误差。


    const Vector3f eq = 2.f * qe.canonical().imag();

使用sin(alpha/2)缩放的旋转轴作为姿态误差(参见轴角定义的四元数)
还要考虑对映单元四元数的歧义性


    matrix::Vector3f rate_setpoint = eq.emult(_proportional_gain);
    rate_setpoint += q.inversed().dcm_z() * yawspeed_feedforward;

得到期望的三轴角速度(用P参数乘以误差),并且加上yaw的前馈。
yawspeed_feedforward是绕NED系z轴旋转的前馈增益,但是我们需要在机体坐标系中应用它。
因此,我们通过取R的最后一列来推断出世界z轴(在body frame中表示)。将其取逆(== q.inversed)并将其乘以偏航设定值率(yawspeed_feedforward)。
这就产生了一个在机体系下表示的前馈的向量。


    for (int i = 0; i < 3; i++) {
        rate_setpoint(i) = math::constrain(rate_setpoint(i), -_rate_limit(i), _rate_limit(i));
    }

    return rate_setpoint;

限幅,返回期望角速度。

总结

整个过程大致为:

  • 把通过四元数运算,把得到仅表征roll&pitch误差的四元数qd_red和仅表示yaw误差的四元数q_mix。
  • 用_yaw_w去改变q_mix,使计算出来的yaw的误差偏小,导致最后计算出来的yaw的期望角速度变小。
  • 再用qd_red和q_mix合成新的期望姿态,用新的期望姿态计算总误差,并进行P+前馈,得到期望角速度。

这样一系列操作,可以达到优先控制roll&pitch的效果。

关于四元数乘法的总结

有惯性坐标系I,以及表示在I下的两个单位四元数q_1,q_2,此时若存在q_x,q_y,满足:
q_xq_1=q_2
q_1q_y=q_2
那么q_x,q_y有什么几何意义?

  • q_xq_1=q_2
    q_x为从q_1q_2的旋转在I系下的表示,这可以由四元数乘法的几何意义得到,即在I系下先进行q_1旋转,在进行q_x旋转等于直接进行q_2旋转,期间没有涉及坐标系转换。
  • q_1q_y=q_2
    q_y为从q_1q_2的旋转在q_1代表的坐标系下的表示,推导如下:
    因为q_2=q_xq_1,因此q_y=q_1^{-1}q_2=q_1^{-1}q_xq_1,在坐标变换的意义下即为将q_x转换到了q_1代表的坐标系下。

初次接触,如有错误欢迎批评指正。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,525评论 6 507
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,203评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,862评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,728评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,743评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,590评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,330评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,244评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,693评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,885评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,001评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,723评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,343评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,919评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,042评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,191评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,955评论 2 355

推荐阅读更多精彩内容