PX4 FMU [17] stabilize

PX4 FMU [17] stabilize
                                                                             -------- 转载请注明出处 
                                                                             -------- 更多笔记请访问我的博客:merafour.blog.163.com

-------- 2015-3-2.冷月追风

-------- email:merafour@163.com

1.stabilize_init 
     目前为止,在 APM官网上公布的飞行模式有十几种," stabilize"也只不过是其中一种,即自稳,或者叫姿态。处于该飞行模式下, roll跟 pitch摇杆控制的是飞行器的倾斜角度,油门控制的是电机的平均转速,特点是摇杆回中则姿态回到水平。 
     "ardupilot/ArduCopter"目录下有不少以 "control_"开头的源文件,大部分都各自对应了一种飞行模式。

bitcraze@bitcraze-vm:~/apm$ ls ardupilot/ArduCopter/control_*
ardupilot/ArduCopter/control_acro.pde      ardupilot/ArduCopter/control_flip.pde      ardupilot/ArduCopter/control_poshold.pde
ardupilot/ArduCopter/control_althold.pde   ardupilot/ArduCopter/control_guided.pde    ardupilot/ArduCopter/control_rtl.pde
ardupilot/ArduCopter/control_auto.pde      ardupilot/ArduCopter/control_land.pde      ardupilot/ArduCopter/control_sport.pde
ardupilot/ArduCopter/control_autotune.pde  ardupilot/ArduCopter/control_loiter.pde    ardupilot/ArduCopter/control_stabilize.pde
ardupilot/ArduCopter/control_circle.pde    ardupilot/ArduCopter/control_modes.pde
ardupilot/ArduCopter/control_drift.pde     ardupilot/ArduCopter/control_ofloiter.pde
bitcraze@bitcraze-vm:~/apm$

所以" stabilize"模式的相关代码在 "ardupilot/ArduCopter/control_stabilize.pde"文件中。 
     "ardupilot/ArduCopter/control_stabilize.pde"源文件中其实就俩函数: stabilize_init跟 stabilize_run。从函数名就很容易看出来前者是用来初始化的,后者则是该飞行模式区别与其它飞行模式的主要代码。

// stabilize_init - initialise stabilize controller
static bool stabilize_init(bool ignore_checks)
{
    // set target altitude to zero for reporting
    // To-Do: make pos controller aware when it's active/inactive so it can always report the altitude error?
    pos_control.set_alt_target(0);
    // stabilize should never be made to fail
    return true;
}

初始化倒是挺简单的,但其实 "pos_control"并没有在 stabilize_run函数中使用,而这里显然是设置高度的初值的。既然都不使用,那么设置它干嘛?但是别忘了还有定高定点这样一些模式,这里是不使用,但是它们要用啊。你想想,当我以 " stabilize"模式起飞,在半空中直接切到定高模式,那么总不能以此时的高度作为初始高度,这显然不合适,而初始高度最合理的当然是起飞是的高度,所以最好的办法就是在这里设置初值。 
    下面一个问题是 stabilize_init函数是在哪里调用的?这个跟踪下代码就清楚了:

bitcraze@bitcraze-vm:~/apm$ grep -nr stabilize_init ardupilot/ArduCopter/
ardupilot/ArduCopter/flight_mode.pde:34:                success = heli_stabilize_init(ignore_checks);
ardupilot/ArduCopter/flight_mode.pde:36:                success = stabilize_init(ignore_checks);
ardupilot/ArduCopter/control_stabilize.pde:7:// stabilize_init - initialise stabilize controller
ardupilot/ArduCopter/control_stabilize.pde:8:static bool stabilize_init(boolignore_checks)
ardupilot/ArduCopter/heli_control_stabilize.pde:7:// stabilize_init - initialise stabilize controller
ardupilot/ArduCopter/heli_control_stabilize.pde:8:static bool heli_stabilize_init(boolignore_checks)
bitcraze@bitcraze-vm:~/apm$
static bool set_mode(uint8_t mode)
{
    // boolean to record if flight mode could be set
    bool success = false;
    bool ignore_checks = !motors.armed();   // allow switching to any mode if disarmed.  We rely on the arming check to perform
    // return immediately if we are already in the desired mode
    if (mode == control_mode) {
        return true;
    }
    switch(mode) {
        case STABILIZE:
            #if FRAME_CONFIG == HELI_FRAME
                success = heli_stabilize_init(ignore_checks);
            #else
                success = stabilize_init(ignore_checks);
            #endif
            break;

代码跟踪到这里直接匹配的话结果很多,工作量比较大,而且很多类都由这个方法。 
    此时又得回到 scheduler_tasks数组中了,其中第一个函数是:rc_loop,

static void rc_loop()
{
    // Read radio and 3-position switch on radio
    // -----------------------------------------
    read_radio();
    read_control_switch();
}
#define CONTROL_SWITCH_COUNTER  20  // 20 iterations at 100hz (i.e. 2/10th of a second) at a new switch position will cause flight mode change
static void read_control_switch()
{
    static uint8_t switch_counter = 0;
    uint8_t switchPosition = readSwitch();
    // has switch moved?
    // ignore flight mode changes if in failsafe
    if (oldSwitchPosition != switchPosition && !failsafe.radio && failsafe.radio_counter == 0) {
        switch_counter++;
        if(switch_counter >= CONTROL_SWITCH_COUNTER) {
            oldSwitchPosition       = switchPosition;
            switch_counter          = 0;
            // set flight mode and simple mode setting
            if (set_mode(flight_modes[switchPosition])) {
                if(g.ch7_option != AUX_SWITCH_SIMPLE_MODE && g.ch8_option != AUX_SWITCH_SIMPLE_MODE && g.ch7_option != AUX_SWITCH_SUPERSIMPLE_MODE && g.ch8_option != AUX_SWITCH_SUPERSIMPLE_MODE) {
                    // set Simple mode using stored paramters from Mission planner
                    // rather than by the control switch
                    if (BIT_IS_SET(g.super_simple, switchPosition)) {
                        set_simple_mode(2);
                    }else{
                        set_simple_mode(BIT_IS_SET(g.simple_modes, switchPosition));
                    }
                }
            }
        }
    }else{
        // reset switch_counter if there's been no change
        // we don't want 10 intermittant blips causing a flight mode change
        switch_counter = 0;
    }
}

可能你会觉得为什么是在这里?在别的地方其实可以找到想这样的调用:"set_mode(STABILIZE)",但那不是我们要找的代码,为什么?因为这里才是我们在上位机上设置的飞行模式。也许那部分代码也值得去看一看,但鉴于对 PX4源码还不是很熟悉,暂时就先放一放。

2.stabilize_run  
    那么现在我们就来看看 stabilize_run这个函数。

// stabilize_run - runs the main stabilize controller
// should be called at 100hz or more
static void stabilize_run()
{
    int16_t target_roll, target_pitch;
    float target_yaw_rate;
    int16_t pilot_throttle_scaled;
    // if not armed or throttle at zero, set throttle to zero and exit immediately
    if(!motors.armed() || g.rc_3.control_in <= 0) {
        attitude_control.relax_bf_rate_controller();
        attitude_control.set_yaw_target_to_current_heading();
        attitude_control.set_throttle_out(0, false);
        return;
    }
    // apply SIMPLE mode transform to pilot inputs
    update_simple_mode();
    // convert pilot input to lean angles
    // To-Do: convert get_pilot_desired_lean_angles to return angles as floats
    get_pilot_desired_lean_angles(g.rc_1.control_in, g.rc_2.control_in, target_roll, target_pitch);
    // get pilot's desired yaw rate
    target_yaw_rate = get_pilot_desired_yaw_rate(g.rc_4.control_in);
    // get pilot's desired throttle
    pilot_throttle_scaled = get_pilot_desired_throttle(g.rc_3.control_in);
    // call attitude controller
    attitude_control.angle_ef_roll_pitch_rate_ef_yaw_smooth(target_roll, target_pitch, target_yaw_rate, get_smoothing_gain());
    // body-frame rate controller is run directly from 100hz loop
    // output pilot's throttle
    attitude_control.set_throttle_out(pilot_throttle_scaled, true);
}

这个函数代码倒不是很多,理解起来困难也不是太大。 
    当然,任何飞行模式都需要区分解锁跟加锁。需要说明的是 PX4采用的是 PID控制器级联的方式进行姿态控制的,即 PD+PID。而我们在这里看到的其实只是 PD控制器。 
    没有解锁或者油门为 0的时候,做了三件事。关闭输出自然不必多说,设置机头也能够理解,关键是 "relax_bf_rate_controller",这是个什么东西?

// relax_bf_rate_controller - ensure body-frame rate controller has zero errors to relax rate controller output
void AC_AttitudeControl::relax_bf_rate_controller()
{
    // ensure zero error in body frame rate controllers
    const Vector3f& gyro = _ins.get_gyro();
    _rate_bf_target = gyro * AC_ATTITUDE_CONTROL_DEGX100;
}

从代码上看的话,其实就是关闭 PD输出,从而关闭 PID输出。 
    下来的 update_simple_mode函数其实包含了简单模式跟超级简单模式。两者都是无头模式,更多的介绍见 APM官网介绍。 
    后就是四个通道的映射。其实前面已经映射过一次了,为什么这里还要再映射一次?最直接的解释就是前面映射的并不是我们这里需要的。其实那一步映射每一种飞行模式都需要用到,那我想两次映射也许是 APM觉得比较好的一个做法了吧。

// get_pilot_desired_angle - transform pilot's roll or pitch input into a desired lean angle
// returns desired angle in centi-degrees
static void get_pilot_desired_lean_angles(int16_t roll_in, int16_t pitch_in, int16_t &roll_out, int16_t &pitch_out)
{
    static float _scaler = 1.0;
    static int16_t _angle_max = 0;
    // range check the input
    roll_in = constrain_int16(roll_in, -ROLL_PITCH_INPUT_MAX, ROLL_PITCH_INPUT_MAX);
    pitch_in = constrain_int16(pitch_in, -ROLL_PITCH_INPUT_MAX, ROLL_PITCH_INPUT_MAX);
    // return filtered roll if no scaling required
    if (aparm.angle_max == ROLL_PITCH_INPUT_MAX) {
        roll_out = roll_in;
        pitch_out = pitch_in;
        return;
    }
    // check if angle_max has been updated and redo scaler
    if (aparm.angle_max != _angle_max) {
        _angle_max = aparm.angle_max;
        _scaler = (float)aparm.angle_max/(float)ROLL_PITCH_INPUT_MAX;
    }
    // convert pilot input to lean angle
    roll_out = (int16_t)((float)roll_in * _scaler);
    pitch_out = (int16_t)((float)pitch_in * _scaler);
}
// get_pilot_desired_heading - transform pilot's yaw input into a desired heading
// returns desired angle in centi-degrees
// To-Do: return heading as a float?
static float get_pilot_desired_yaw_rate(int16_t stick_angle)
{
    // convert pilot input to the desired yaw rate
    return stick_angle * g.acro_yaw_p;
}

上面我们看到的是姿态跟偏航的映射。对于之态,如果行程不一样(默认是+/-45°)则计算一个系数进行修正。而对于偏航则通过一个系数进行调正。但是对于三通道油门就稍微复杂一点,原因是三通道进行了分段:

// get_pilot_desired_throttle - transform pilot's throttle input to make cruise throttle mid stick
// used only for manual throttle modes
// returns throttle output 0 to 1000
#define THROTTLE_IN_MIDDLE 500          // the throttle mid point
static int16_t get_pilot_desired_throttle(int16_t throttle_control)
{
    int16_t throttle_out;
    // exit immediately in the simple cases
    if( throttle_control == 0 || g.throttle_mid == 500) {
        return throttle_control;
    }
    // ensure reasonable throttle values
    throttle_control = constrain_int16(throttle_control,0,1000);
    g.throttle_mid = constrain_int16(g.throttle_mid,300,700);
    // check throttle is above, below or in the deadband
    if (throttle_control < THROTTLE_IN_MIDDLE) {
        // below the deadband
        throttle_out = g.throttle_min + ((float)(throttle_control-g.throttle_min))*((float)(g.throttle_mid - g.throttle_min))/((float)(500-g.throttle_min));
    }else if(throttle_control > THROTTLE_IN_MIDDLE) {
        // above the deadband
        throttle_out = g.throttle_mid + ((float)(throttle_control-500))*(float)(1000-g.throttle_mid)/500.0f;
    }else{
        // must be in the deadband
        throttle_out = g.throttle_mid;
    }
    return throttle_out;
}

虽然进行了分段,但把每一段剖开其实还是等比例映射。最复杂的当然还是 angle_ef_roll_pitch_rate_ef_yaw_smooth,它是 PD控制器,自然不会简单。所以我们还是先从最简单的开始,先来看看 set_throttle_out函数:

// set_throttle_out - to be called by upper throttle controllers when they wish to provide throttle output directly to motors
// provide 0 to cut motors
void AC_AttitudeControl::set_throttle_out(int16_t throttle_out, boolapply_angle_boost)
{
    if (apply_angle_boost) {
        _motors.set_throttle(get_angle_boost(throttle_out));
    }else{
        _motors.set_throttle(throttle_out);
        // clear angle_boost for logging purposes
        _angle_boost = 0;
    }
    // update compass with throttle value
    // To-Do: find another method to grab the throttle out and feed to the compass.  Could be done completely outside this class
    //compass.set_throttle((float)g.rc_3.servo_out/1000.0f);
}
void                set_throttle(int16_t throttle_in) { _rc_throttle.servo_out = throttle_in; };    // range 0 ~ 1000

其实我们看到,最终将油门数据写入电机是可以选择进行补偿的,补偿方式如下:

// get_angle_boost - returns a throttle including compensation for roll/pitch angle
// throttle value should be 0 ~ 1000
int16_t AC_AttitudeControl::get_angle_boost(int16_t throttle_pwm)
{
    float temp = _ahrs.cos_pitch() * _ahrs.cos_roll();
    int16_t throttle_out;
    temp = constrain_float(temp, 0.5f, 1.0f);
    // reduce throttle if we go inverted
    temp = constrain_float(9000-max(labs(_ahrs.roll_sensor),labs(_ahrs.pitch_sensor)), 0, 3000) / (3000 * temp);
    // apply scale and constrain throttle
    // To-Do: move throttle_min and throttle_max into the AP_Vehicles class?
    throttle_out = constrain_float((float)(throttle_pwm-_motors.throttle_min()) * temp + _motors.throttle_min(), _motors.throttle_min(), 1000);
    // record angle boost for logging
    _angle_boost = throttle_out - throttle_pwm;
    return throttle_out;
}

根据注释,这段代码是使用 roll/pitch对油门进行补偿,那么具体又是如何补偿的呢?

throttle_out = constrain_float((float)(throttle_pwm-_motors.throttle_min()) * temp + _motors.throttle_min(), _motors.throttle_min(), 1000);
==>
throttle_out = (throttle_pwm-_motors.throttle_min()) * temp + _motors.throttle_min();

代码处理之后就好理解得多。其实就是把高于 throttle_min的部分通过 temp进行补偿,所以关键在于 temp的计算。那么这里为了便于理解,我们假定 pitch为 0,即 cos_pitch为 1,那么把代码处理一下:

float temp = _ahrs.cos_roll();
    temp = constrain_float(9000-labs(_ahrs.roll_sensor), 0, 3000) / (3000 * temp);
//正常情况下 labs(_ahrs.roll_sensor)值在 4500以内,所以
    float temp = _ahrs.cos_roll();
    temp = 3000/ (3000 * temp);
==>
    float temp = 1/_ahrs.cos_roll();

所以正常情况下,考虑两个方向的姿态,补偿系数就是: " temp = 1/(_ahrs.cos_pitch() * _ahrs.cos_roll());"。但是从数学上分析, temp是可以无穷大的,显然不能这么去补偿,所以这时候语句 "temp = constrain_float(temp, 0.5f, 1.0f);"就起作用了,它可以对结果进行约束,使得最大补偿为 1倍,即最大 2倍油门输出。当然,这里还有一个变量:"_angle_boost",它是用来记录 log的,这里我们可以不必关心。 
    么,现在这里有个问题,就是什么情况下需要进行油门补偿?这个太简单了,匹配一下不就知道了嘛:

bitcraze@bitcraze-vm:~/apm$ grep -nr set_throttle_out ardupilot/ArduCopter/
ardupilot/ArduCopter/control_guided.pde:95:        attitude_control.set_throttle_out(0, false);
ardupilot/ArduCopter/heli_control_acro.pde:46:    attitude_control.set_throttle_out(g.rc_3.control_in, false);
ardupilot/ArduCopter/control_autotune.pde:218:        attitude_control.set_throttle_out(0, false);
ardupilot/ArduCopter/control_autotune.pde:247:        attitude_control.set_throttle_out(0, false);
ardupilot/ArduCopter/control_land.pde:59:        attitude_control.set_throttle_out(0, false);
ardupilot/ArduCopter/control_land.pde:126:        attitude_control.set_throttle_out(0, false);
ardupilot/ArduCopter/control_auto.pde:108:        attitude_control.set_throttle_out(0, false);
ardupilot/ArduCopter/control_auto.pde:157:        attitude_control.set_throttle_out(0, false);
ardupilot/ArduCopter/control_auto.pde:215:        attitude_control.set_throttle_out(0, false);
ardupilot/ArduCopter/control_auto.pde:284:        attitude_control.set_throttle_out(0, false);
ardupilot/ArduCopter/control_flip.pde:175:    attitude_control.set_throttle_out(throttle_out, false);
ardupilot/ArduCopter/control_althold.pde:33:        attitude_control.set_throttle_out(0, false);
ardupilot/ArduCopter/control_althold.pde:63:        attitude_control.set_throttle_out(0, false);
ardupilot/ArduCopter/control_rtl.pde:132:        attitude_control.set_throttle_out(0, false);
ardupilot/ArduCopter/control_rtl.pde:190:        attitude_control.set_throttle_out(0, false);
ardupilot/ArduCopter/control_rtl.pde:261:        attitude_control.set_throttle_out(0, false);
ardupilot/ArduCopter/control_rtl.pde:325:        attitude_control.set_throttle_out(0, false);
ardupilot/ArduCopter/control_loiter.pde:40:        attitude_control.set_throttle_out(0, false);
ardupilot/ArduCopter/control_loiter.pde:75:        attitude_control.set_throttle_out(0, false);
ardupilot/ArduCopter/control_ofloiter.pde:41:        attitude_control.set_throttle_out(0, false);
ardupilot/ArduCopter/control_ofloiter.pde:73:        attitude_control.set_throttle_out(0, false);
ardupilot/ArduCopter/control_acro.pde:25:        attitude_control.set_throttle_out(0, false);
ardupilot/ArduCopter/control_acro.pde:39:    attitude_control.set_throttle_out(pilot_throttle_scaled, false);
ardupilot/ArduCopter/control_sport.pde:31:        attitude_control.set_throttle_out(0, false);
ardupilot/ArduCopter/control_sport.pde:82:        attitude_control.set_throttle_out(0, false);
ardupilot/ArduCopter/control_poshold.pde:162:        attitude_control.set_throttle_out(0, false);
ardupilot/ArduCopter/control_poshold.pde:191:        attitude_control.set_throttle_out(0, false);
ardupilot/ArduCopter/control_drift.pde:49:        attitude_control.set_throttle_out(0, false);
ardupilot/ArduCopter/control_drift.pde:108:    attitude_control.set_throttle_out(pilot_throttle_scaled + thr_assist, true);
ardupilot/ArduCopter/control_stabilize.pde:30:        attitude_control.set_throttle_out(0, false);
ardupilot/ArduCopter/control_stabilize.pde:53:    attitude_control.set_throttle_out(pilot_throttle_scaled, true);
ardupilot/ArduCopter/control_circle.pde:40:        attitude_control.set_throttle_out(0, false);
ardupilot/ArduCopter/heli_control_stabilize.pde:57:    attitude_control.set_throttle_out(pilot_throttle_scaled, false);
bitcraze@bitcraze-vm:~/apm$

根据匹配结果,只有 stabilize跟 drift两种飞行模式需要对油门进行补偿,为什么? drift是漂移模式,在 APM网站上是这样解释的: 
    飘移模式能让用户就像飞行安装有“自动协调转弯”的飞机一样飞行多旋翼飞行器。
    用户直接控制Yaw和Pitch,但是Roll是由自动驾驶仪控制的。 如果使用美国手的发射机,可以非常方便的用一个控制杆来控制的飞行器
 
其实 drift跟 stabilize是相似的,只不过 Roll变成了自动控制。那么这两种飞行模式跟其它的飞行模式之间有什么不同呢?其它一些飞行模式除 acro(特技)外都是带气压计定高的,既然有定高那么自然就不需要油门补偿了,因为油门补偿本身就是为了应对打舵时引起的高度降低。那么为什么特技模式不需要呢?这是因为特技模式本身的特点所决定的,在特技模式遥控器对飞行器进行直接的控制。 
    么下面我们就来看看 angle_ef_roll_pitch_rate_ef_yaw_smooth具体是怎么回事。

3.angle_ef_roll_pitch_rate_ef_yaw_smooth  
    函数是类 AC_AttitudeControl的方法,实现导航级的 PD控制,但是这里的 PD控制更我们之前用的 PD控制在实现形式上大有不同,传统 PID是按照下面这样的形式来实现的:

从形式上我们也可以看出,这里的控制输入是 e,但 APM并没有这么设计,因为归根结底, PD控制器中, P跟 D它是一个微分的关系,换句话说只要符合 PD这样一个微分关系即可构成 PD控制器。具体我们用代码来进行分析。 
    过,在调用该函数的时候,实参除了映射之后的摇杆数据还有一个数据是由函数 get_smoothing_gain获取的,

// get_smoothing_gain - returns smoothing gain to be passed into attitude_control.angle_ef_roll_pitch_rate_ef_yaw_smooth
//      result is a number from 2 to 12 with 2 being very sluggish and 12 being very crisp
float get_smoothing_gain()
{
    return (2.0f + (float)g.rc_feel_rp/10.0f);
}

从注释来看,这是用来获取平滑的增益的,具体这个增益是做什么用的,我们下面会看到。如果我们在上位机上把该参数给读出来,我们将会看到 g.rc_feel_rp值为 100,即该函数返回 12。

// methods to be called by upper controllers to request and implement a desired attitude
// angle_ef_roll_pitch_rate_ef_yaw_smooth - attempts to maintain a roll and pitch angle and yaw rate (all earth frame) while smoothing the attitude based on the feel parameter
//      smoothing_gain : a number from 1 to 50 with 1 being sluggish and 50 being very crisp
void AC_AttitudeControl::angle_ef_roll_pitch_rate_ef_yaw_smooth(floatroll_angle_ef, float pitch_angle_ef, float yaw_rate_ef, float smoothing_gain)
{
    Vector3f angle_ef_error;    // earth frame angle errors
    float rate_change_limit;
    // sanity check smoothing gain
    smoothing_gain = constrain_float(smoothing_gain,1.0f,50.0f);
    float linear_angle = _accel_rp_max/(smoothing_gain*smoothing_gain);
    rate_change_limit = _accel_rp_max * _dt;
    float rate_ef_desired;
    float angle_to_target;
    if (_accel_rp_max > 0.0f) {
        // calculate earth-frame feed forward roll rate using linear response when close to the target, sqrt response when we're further away
        angle_to_target = roll_angle_ef - _angle_ef_target.x;
        if (angle_to_target > linear_angle) {
                rate_ef_desired = safe_sqrt(2.0f*_accel_rp_max*(fabs(angle_to_target)-(linear_angle/2.0f)));
        } else if (angle_to_target < -linear_angle) {
                rate_ef_desired = -safe_sqrt(2.0f*_accel_rp_max*(fabs(angle_to_target)-(linear_angle/2.0f)));
        } else {
                rate_ef_desired = smoothing_gain*angle_to_target;
        }
        _rate_ef_desired.x = constrain_float(rate_ef_desired, _rate_ef_desired.x-rate_change_limit, _rate_ef_desired.x+rate_change_limit);
        // update earth-frame roll angle target using desired roll rate
        update_ef_roll_angle_and_error(_rate_ef_desired.x, angle_ef_error);
        // calculate earth-frame feed forward pitch rate using linear response when close to the target, sqrt response when we're further away
        angle_to_target = pitch_angle_ef - _angle_ef_target.y;
        if (angle_to_target > linear_angle) {
                rate_ef_desired = safe_sqrt(2.0f*_accel_rp_max*(fabs(angle_to_target)-(linear_angle/2.0f)));
        } else if (angle_to_target < -linear_angle) {
                rate_ef_desired = -safe_sqrt(2.0f*_accel_rp_max*(fabs(angle_to_target)-(linear_angle/2.0f)));
        } else {
                rate_ef_desired = smoothing_gain*angle_to_target;
        }
        _rate_ef_desired.y = constrain_float(rate_ef_desired, _rate_ef_desired.y-rate_change_limit, _rate_ef_desired.y+rate_change_limit);
        // update earth-frame pitch angle target using desired pitch rate
        update_ef_pitch_angle_and_error(_rate_ef_desired.y, angle_ef_error);
    } else {
        // target roll and pitch to desired input roll and pitch
        _angle_ef_target.x = roll_angle_ef;
        angle_ef_error.x = wrap_180_cd_float(_angle_ef_target.x - _ahrs.roll_sensor);
        _angle_ef_target.y = pitch_angle_ef;
        angle_ef_error.y = wrap_180_cd_float(_angle_ef_target.y - _ahrs.pitch_sensor);
        // set roll and pitch feed forward to zero
        _rate_ef_desired.x = 0;
        _rate_ef_desired.y = 0;
    }
    if (_accel_y_max > 0.0f) {
        // set earth-frame feed forward rate for yaw
        rate_change_limit = _accel_y_max * _dt;
        float rate_change = yaw_rate_ef - _rate_ef_desired.z;
        rate_change = constrain_float(rate_change, -rate_change_limit, rate_change_limit);
        _rate_ef_desired.z += rate_change;
        // calculate yaw target angle and angle error
        update_ef_yaw_angle_and_error(_rate_ef_desired.z, angle_ef_error);
    } else {
        // set yaw feed forward to zero
        _rate_ef_desired.z = 0;
        // calculate yaw target angle and angle error
        update_ef_yaw_angle_and_error(yaw_rate_ef, angle_ef_error);
    }
    // constrain earth-frame angle targets
    _angle_ef_target.x = constrain_float(_angle_ef_target.x, -_aparm.angle_max, _aparm.angle_max);
    _angle_ef_target.y = constrain_float(_angle_ef_target.y, -_aparm.angle_max, _aparm.angle_max);
    // convert earth-frame angle errors to body-frame angle errors
    frame_conversion_ef_to_bf(angle_ef_error, _angle_bf_error);
    // convert earth-frame feed forward rates to body-frame feed forward rates
    frame_conversion_ef_to_bf(_rate_ef_desired, _rate_bf_desired);
    // convert body-frame angle errors to body-frame rate targets
    update_rate_bf_targets();
    // add body frame rate feed forward
    if (_rate_bf_ff_enabled) {
        _rate_bf_target += _rate_bf_desired;
    }
    // body-frame to motor outputs should be called separately
}

我们现在看到的是该函数完整的代码。为了便于理解,我们需要对代码作一些处理,我们假设 _accel_y_max为 0,那么:

// methods to be called by upper controllers to request and implement a desired attitude
// angle_ef_roll_pitch_rate_ef_yaw_smooth - attempts to maintain a roll and pitch angle and yaw rate (all earth frame) while smoothing the attitude based on the feel parameter
//      smoothing_gain : a number from 1 to 50 with 1 being sluggish and 50 being very crisp
void AC_AttitudeControl::angle_ef_roll_pitch_rate_ef_yaw_smooth(floatroll_angle_ef, float pitch_angle_ef, float yaw_rate_ef, float smoothing_gain)
{
    Vector3f angle_ef_error;    // earth frame angle errors
    float rate_change_limit;
    // sanity check smoothing gain
    smoothing_gain = constrain_float(smoothing_gain,1.0f,50.0f);
    float linear_angle = _accel_rp_max/(smoothing_gain*smoothing_gain);
    rate_change_limit = _accel_rp_max * _dt;
    float rate_ef_desired;
    float angle_to_target;
    if (_accel_rp_max > 0.0f) {
    } else {
        // target roll and pitch to desired input roll and pitch
        _angle_ef_target.x = roll_angle_ef;
        angle_ef_error.x = wrap_180_cd_float(_angle_ef_target.x - _ahrs.roll_sensor);
        _angle_ef_target.y = pitch_angle_ef;
        angle_ef_error.y = wrap_180_cd_float(_angle_ef_target.y - _ahrs.pitch_sensor);
        // set roll and pitch feed forward to zero
        _rate_ef_desired.x = 0;
        _rate_ef_desired.y = 0;
    }
    if (_accel_y_max > 0.0f) {
    } else {
        // set yaw feed forward to zero
        _rate_ef_desired.z = 0;
        // calculate yaw target angle and angle error
        update_ef_yaw_angle_and_error(yaw_rate_ef, angle_ef_error);
    }
    // constrain earth-frame angle targets
    _angle_ef_target.x = constrain_float(_angle_ef_target.x, -_aparm.angle_max, _aparm.angle_max);
    _angle_ef_target.y = constrain_float(_angle_ef_target.y, -_aparm.angle_max, _aparm.angle_max);
    // convert earth-frame angle errors to body-frame angle errors
    frame_conversion_ef_to_bf(angle_ef_error, _angle_bf_error);
    // convert earth-frame feed forward rates to body-frame feed forward rates
    frame_conversion_ef_to_bf(_rate_ef_desired, _rate_bf_desired);
    // convert body-frame angle errors to body-frame rate targets
    update_rate_bf_targets();
    // add body frame rate feed forward
    if (_rate_bf_ff_enabled) {
        _rate_bf_target += _rate_bf_desired;
    }
    // body-frame to motor outputs should be called separately
}
// update_ef_yaw_angle_and_error - update _angle_ef_target.z using an earth frame yaw rate request
void AC_AttitudeControl::update_ef_yaw_angle_and_error(float yaw_rate_ef, Vector3f &angle_ef_error)
{
    // calculate angle error with maximum of +- max angle overshoot
    angle_ef_error.z = wrap_180_cd(_angle_ef_target.z - _ahrs.yaw_sensor);
    angle_ef_error.z  = constrain_float(angle_ef_error.z, -AC_ATTITUDE_RATE_STAB_YAW_OVERSHOOT_ANGLE_MAX, AC_ATTITUDE_RATE_STAB_YAW_OVERSHOOT_ANGLE_MAX);
    // update yaw angle target to be within max angle overshoot of our current heading
    _angle_ef_target.z = angle_ef_error.z + _ahrs.yaw_sensor;
    // increment the yaw angle target
    _angle_ef_target.z += yaw_rate_ef * _dt;
    _angle_ef_target.z = wrap_360_cd(_angle_ef_target.z);
}
// update_rate_bf_targets - converts body-frame angle error to body-frame rate targets for roll, pitch and yaw axis
//   targets rates in centi-degrees taken from _angle_bf_error
//   results in centi-degrees/sec put into _rate_bf_target
void AC_AttitudeControl::update_rate_bf_targets()
{
    // stab roll calculation
    _rate_bf_target.x = _p_angle_roll.kP() * _angle_bf_error.x;
    // constrain roll rate request
    if (_flags.limit_angle_to_rate_request) {
        _rate_bf_target.x = constrain_float(_rate_bf_target.x,-_angle_rate_rp_max,_angle_rate_rp_max);
    }
    // stab pitch calculation
    _rate_bf_target.y = _p_angle_pitch.kP() * _angle_bf_error.y;
    // constrain pitch rate request
    if (_flags.limit_angle_to_rate_request) {
        _rate_bf_target.y = constrain_float(_rate_bf_target.y,-_angle_rate_rp_max,_angle_rate_rp_max);
    }
    // stab yaw calculation
    _rate_bf_target.z = _p_angle_yaw.kP() * _angle_bf_error.z;
    // constrain yaw rate request
    if (_flags.limit_angle_to_rate_request) {
        _rate_bf_target.z = constrain_float(_rate_bf_target.z,-_angle_rate_y_max,_angle_rate_y_max);
    }
        _rate_bf_target.x += -_angle_bf_error.y * _ins.get_gyro().z;
        _rate_bf_target.y +=  _angle_bf_error.x * _ins.get_gyro().z;
}

把代码处理了一下就很清楚了,原来也只不过是计算角度差 angle_ef_error,然后通过 update_rate_bf_targets函数进行 P运算。函数 frame_conversion_ef_to_bf则是用来进行坐标系之间的转换,将参考坐标系中的误差转换到机体坐标系。但是前面我们不是是说了 PX4姿态级使用的是 PD控制的吗,为什么值看到了 P控制,D呢?别忘了,在函数的最后还有一条语句:" _rate_bf_target += _rate_bf_desired",注释说这是前反馈,实际上就是 D控制,只不过现在我们所看到的是 0而已。 
    果我们把参数通过上位机给读出来,我们将会看到这样两个参数:

ATC_ACCEL_RP_MAX,126000
ATC_ACCEL_Y_MAX,36000

也就是说实际情况是 _accel_rp_max跟 _accel_y_max是大于 0的值,执行的是 if段代码:

Vector3f angle_ef_error;    // earth frame angle errors
    float rate_change_limit;
    // sanity check smoothing gain
    smoothing_gain = constrain_float(smoothing_gain,1.0f,50.0f);
    float linear_angle = _accel_rp_max/(smoothing_gain*smoothing_gain);
    rate_change_limit = _accel_rp_max * _dt;
    float rate_ef_desired;
    float angle_to_target;
        // calculate earth-frame feed forward roll rate using linear response when close to the target, sqrt response when we're further away
        angle_to_target = roll_angle_ef - _angle_ef_target.x;
        if (angle_to_target > linear_angle) {
                rate_ef_desired = safe_sqrt(2.0f*_accel_rp_max*(fabs(angle_to_target)-(linear_angle/2.0f)));
        } else if (angle_to_target < -linear_angle) {
                rate_ef_desired = -safe_sqrt(2.0f*_accel_rp_max*(fabs(angle_to_target)-(linear_angle/2.0f)));
        } else {
                rate_ef_desired = smoothing_gain*angle_to_target;
        }
        _rate_ef_desired.x = constrain_float(rate_ef_desired, _rate_ef_desired.x-rate_change_limit, _rate_ef_desired.x+rate_change_limit);
        // update earth-frame roll angle target using desired roll rate
        update_ef_roll_angle_and_error(_rate_ef_desired.x, angle_ef_error);
        // calculate earth-frame feed forward pitch rate using linear response when close to the target, sqrt response when we're further away
        angle_to_target = pitch_angle_ef - _angle_ef_target.y;
        if (angle_to_target > linear_angle) {
                rate_ef_desired = safe_sqrt(2.0f*_accel_rp_max*(fabs(angle_to_target)-(linear_angle/2.0f)));
        } else if (angle_to_target < -linear_angle) {
                rate_ef_desired = -safe_sqrt(2.0f*_accel_rp_max*(fabs(angle_to_target)-(linear_angle/2.0f)));
        } else {
                rate_ef_desired = smoothing_gain*angle_to_target;
        }
        _rate_ef_desired.y = constrain_float(rate_ef_desired, _rate_ef_desired.y-rate_change_limit, _rate_ef_desired.y+rate_change_limit);
        // update earth-frame pitch angle target using desired pitch rate
        update_ef_pitch_angle_and_error(_rate_ef_desired.y, angle_ef_error);
        // set earth-frame feed forward rate for yaw
        rate_change_limit = _accel_y_max * _dt;
        float rate_change = yaw_rate_ef - _rate_ef_desired.z;
        rate_change = constrain_float(rate_change, -rate_change_limit, rate_change_limit);
        _rate_ef_desired.z += rate_change;
        // calculate yaw target angle and angle error
        update_ef_yaw_angle_and_error(_rate_ef_desired.z, angle_ef_error);

这段代码分析起来会比较绕。但说透了它其实也就是在 _angle_ef_target跟 _ahrs之间插了一个值,就是 _angle_ef_target。这样的好处是什么呢?好处在于,这样处理之后速率是像抛物线一样先增后减的,而不是一打舵猛地给速率控制器一个很高的速率,然后慢慢往 0靠拢。按我的说法就是 PX4在导航级做了一个平滑处理。想了解更多的细节我们还需要去看下 update_ef_roll_angle_and_error跟 update_ef_pitch_angle_and_error函数:

// update_ef_roll_angle_and_error - update _angle_ef_target.x using an earth frame roll rate request
void AC_AttitudeControl::update_ef_roll_angle_and_error(float roll_rate_ef, Vector3f &angle_ef_error)
{
    // calculate angle error with maximum of +- max angle overshoot
    angle_ef_error.x = wrap_180_cd(_angle_ef_target.x - _ahrs.roll_sensor);
    angle_ef_error.x  = constrain_float(angle_ef_error.x, -AC_ATTITUDE_RATE_STAB_ROLL_OVERSHOOT_ANGLE_MAX, AC_ATTITUDE_RATE_STAB_ROLL_OVERSHOOT_ANGLE_MAX);
    // update roll angle target to be within max angle overshoot of our roll angle
    _angle_ef_target.x = angle_ef_error.x + _ahrs.roll_sensor;
    // increment the roll angle target
    _angle_ef_target.x += roll_rate_ef * _dt;
    _angle_ef_target.x = wrap_180_cd(_angle_ef_target.x);
}
// update_ef_pitch_angle_and_error - update _angle_ef_target.y using an earth frame pitch rate request
void AC_AttitudeControl::update_ef_pitch_angle_and_error(float pitch_rate_ef, Vector3f &angle_ef_error)
{
    // calculate angle error with maximum of +- max angle overshoot
    // To-Do: should we do something better as we cross 90 degrees?
    angle_ef_error.y = wrap_180_cd(_angle_ef_target.y - _ahrs.pitch_sensor);
    angle_ef_error.y  = constrain_float(angle_ef_error.y, -AC_ATTITUDE_RATE_STAB_PITCH_OVERSHOOT_ANGLE_MAX, AC_ATTITUDE_RATE_STAB_PITCH_OVERSHOOT_ANGLE_MAX);
    // update pitch angle target to be within max angle overshoot of our pitch angle
    _angle_ef_target.y = angle_ef_error.y + _ahrs.pitch_sensor;
    // increment the pitch angle target
    _angle_ef_target.y += pitch_rate_ef * _dt;
    _angle_ef_target.y = wrap_180_cd(_angle_ef_target.y);
}

这样我们就清楚了,PX4是通过 smoothing_gain得到一个新的速率 rate_ef_desired,然后用这个新的速率去更新 _angle_ef_target,并使用 _angle_ef_target计算姿态误差参与控制。这个时候可能你会觉得 linear_angle是用来干嘛的?通过前面我们给出的数据,可以算出 linear_angle值为 875,PX4是精确到 0.01°,所以对应的是 8.75°。也就是说在 8.75°以内是一个线性区域,速率是线性变化的,超出这个角度就采用飞线性的方式计算 rate_ef_desired。具体是什么方式呢?我们可以根据我们的数据稍微整理下得到:

rate_ef_desired = safe_sqrt(2.0f*126000*(fabs(angle_to_target)-437.5));
==>
rate_ef_desired = safe_sqrt(252000)*safe_sqrt(fabs(angle_to_target)-437.5);
==>
rate_ef_desired = 501*safe_sqrt(fabs(angle_to_target)-437.5);

当然,如果能够写成数学表达式就更加清晰了。 
    这个时候,由于 _rate_ef_desired不为 0,所以 _rate_bf_desired也不为 0。而且从计算过程来看,PX4是通过对 _rate_ef_desired积分计算姿态,所以姿态级使用的是 PD控制,只不过将 PD控制器中的主角稍稍换了一下。 
    么下面我们将要看到的是姿态控制中相当重要的一环:速率控制器,即 PID控制器。 
4.rate_controller_run 
    源码 " ardupilot/ArduCopter/ArduCopter.pde"中有这样一段代码:

// Main loop - 100hz
static void fast_loop()
{
    // IMU DCM Algorithm
    // --------------------
    read_AHRS();
    // run low level rate controllers that only require IMU data
    attitude_control.rate_controller_run();

我们没知道,fast_loop是快速循环,直接由 loop调用。而这里的 rate_controller_run便是我们的 PID控制器,即速率控制器。

// rate_controller_run - run lowest level body-frame rate controller and send outputs to the motors
//      should be called at 100hz or more
void AC_AttitudeControl::rate_controller_run()
{
    // call rate controllers and send output to motors object
    // To-Do: should the outputs from get_rate_roll, pitch, yaw be int16_t which is the input to the motors library?
    // To-Do: skip this step if the throttle out is zero?
    _motors.set_roll(rate_bf_to_motor_roll(_rate_bf_target.x));
    _motors.set_pitch(rate_bf_to_motor_pitch(_rate_bf_target.y));
    _motors.set_yaw(rate_bf_to_motor_yaw(_rate_bf_target.z));
}

_rate_bf_target就是前面导航级计算得到的速率,rate_bf_to_motor_用来把速率转换成输出,它用的就是我们传统的 PID形式。

// rate_bf_to_motor_roll - ask the rate controller to calculate the motor outputs to achieve the target rate in centi-degrees / second
float AC_AttitudeControl::rate_bf_to_motor_roll(float rate_target_cds)
{
    float p,i,d;            // used to capture pid values for logging
    float current_rate;     // this iteration's rate
    float rate_error;       // simply target_rate - current_rate
    // get current rate
    // To-Do: make getting gyro rates more efficient?
    current_rate = (_ins.get_gyro().x * AC_ATTITUDE_CONTROL_DEGX100);
    // calculate error and call pid controller
    rate_error = rate_target_cds - current_rate;
    p = _pid_rate_roll.get_p(rate_error);
    // get i term
    i = _pid_rate_roll.get_integrator();
    // update i term as long as we haven't breached the limits or the I term will certainly reduce
    if (!_motors.limit.roll_pitch

转载于:https://www.cnblogs.com/eastgeneral/p/10879599.html

PX4 FMU [17] stabilize相关推荐

  1. PX4 FMU [7] rgbled [转载]

    PX4 FMU [7] rgbled PX4 FMU [7] rgbled                                                                ...

  2. PX4 FMU启动流程 1.nsh

    PX4 FMU启动流程 1.nsh PX4 FMU启动流程 1.nsh -------- 转载请注明出处 -------- 2014-11-27.冷月追风 -------- email:merafou ...

  3. PX4 FMU启动流程 2. 一、 nsh_newconsole

    PX4 FMU启动流程 2. 一. nsh_newconsole PX4 FMU启动流程 2. 一. nsh_newconsole -------- 转载请注明出处 -------- 2014-11- ...

  4. PX4 FMU启动流程 2. 二、 nsh_initscript

    PX4 FMU启动流程 2. 二. nsh_initscript PX4 FMU启动流程 2. 二. nsh_initscript -------- 转载请注明出处 -------- 2014-11- ...

  5. ArduCopter相关

     1.ArduPilot_main 我们知道,在 C语言中最经典的程序是 "Hello World!",这应该是我们在 C语言中最早接触的一个程序了.而在单片机中,最经典的一个 ...

  6. ArduCopter调试

     1.ArduPilot_main 我们知道,在 C语言中最经典的程序是 "Hello World!",这应该是我们在 C语言中最早接触的一个程序了.而在单片机中,最经典的一个 ...

  7. 基于Ardupilot/PX4固件,APM/PIXhawk硬件的VTOL垂直起降固定翼软硬件参数调试(第三篇)故障保护及问题诊断

    基于Ardupilot/PX4固件,APM/PIXhawk硬件的VTOL垂直起降固定翼软硬件参数调试(第三篇)故障保护及问题诊断 PIX无法安装驱动 双击下载的px4_driver_installer ...

  8. px4原生源码学习一

    接触px4代码有几天了,想把自己所学所想记录下来. px4就不过多介绍了,算得上目前使用的最广泛的开源飞控了.为什么说"px4原生代码学习呢",这还得追溯到被大疆干死的3DR,3D ...

  9. px4原生源码学习-(1)

    接触px4代码有几天了,想把自己所学所想记录下来. px4就不过多介绍了,算得上目前使用的最广泛的开源飞控了.为什么说"px4原生代码学习呢",这还得追溯到被大疆干死的3DR,3D ...

  10. PixHawk飞控和Mission Planner地面站安装调试

    PixHawk飞控和Mission Planner地面站安装调试 PixHawk是著名飞控厂商3DR推出的新一代独立.开源.高效的飞行控制器,前身为APM飞控,不仅提供了丰富的外设模块和可靠的飞行体验 ...

最新文章

  1. 『计算机视觉』经典RCNN_其一:从RCNN到Faster-RCNN
  2. SparkStreaming从Kafka读取数据两种方式
  3. python的dir()和__dict__属性的区别
  4. webstorm 激活方法
  5. 10分钟就能学会的.NET Core配置
  6. 1.10 编程基础之简单排序 04 奖学金 7分 python
  7. php设计的意义,PHP设计模式
  8. DBeaver 导出EXCEL文件
  9. linux误删除 dev disk文件,误删除 linux 系统文件了?这个方法教你解决
  10. Avalon二数据填充
  11. 树莓派搭建VSFTP记录---自用简记
  12. 数据库实验一——数据定义
  13. 线性拟合(回归)的小结
  14. 微博、微信、qq、空间、等分享功能
  15. kail详细安装教程
  16. hadoop存储与分析
  17. 微信公众号历史数据采集和推文监控
  18. 怎样优化IM即时通讯移动端APP的日志上报机制
  19. Ubuntu安装Caffe .build_release/tools/caffe: error while loading shared libraries: libcudnn.so.5
  20. Unity利用单反相机拍照、录制视频

热门文章

  1. 改进平滑滚动,修改音量调节级数实现音量微调【编译自XDA 适用于大部分设备】
  2. UE4 合并静态网格体
  3. win7连接xp共享打印机方法
  4. access中本年度的四月一日_2014年3月计算机二级ACCESS上机试题及详解十二
  5. tomcat 8.5.31启动报错:org.apache.jasper.servlet.TldScanner.scanJars At least one JAR was scanned for TLD
  6. 上古卷轴5 Papyrus的LOG日志分析,科学解决ctd,bug的方法
  7. Windowed reading and writing
  8. 网站被劫持怎么办?别着急看完这篇文章您就知道如何应对了!
  9. ValueError X has 2 features, but LogisticRegression is expecting 5 features as input
  10. 设备管理之I/O系统