ArduPilot源码阅读——底层油门转向篇

低通滤波器

在AC_AttitudeControl_Sub中写到了一个使用低通滤波器,来防止突然变化的高度信息,使控制器缓慢达到目标值

_althold_lean_angle_max = _althold_lean_angle_max + (_dt/(_dt+_angle_limit_tc))*(althold_lean_angle_max-_althold_lean_angle_max);

简化版本为:

y(n+1) = y(n) + dt / (dt + Tc) * (x(n) - y(n))

简单的一节低通滤波,Tc代表时间常数,dt采样时间;它是通过拉普拉斯反变换后,在经过后向差分离散得来的。

具体一阶低通滤波器实现请参考 LowPassFilter.h/.cpp

Matlab演示

分别给出几个输入值,可以从图中看出输出缓慢靠近输入的值;

转向缩放(用于油门转弯模型)

输出油门量throttle和转弯量steering,其函数原型为

void AP_MotorsUGV::output_regular(bool armed, float ground_speed, float steering, float throttle)

输入分析:

armed: 电机是否在控

ground_speed: 当前地速

steering: 输入转弯量(横向)

throttle: 输入油门量(纵向)

源码分析:

void AP_MotorsUGV::output_regular(bool armed, float ground_speed, float steering, float throttle){
	if (armed) {	//电机是否在控
		if (_scale_steering) {	//按速度或者角度来缩放转向
			if (have_vectored_thrust()) {   //向量推进器,适用对象一般为螺旋桨可变向的船
				....
			}
			....
			else{ //按地速来控制转向缩放
				....
			}	
		}
		else{ //不启用转向缩放
			....
		}
		output_throttle(SRV_Channel::k_throttle, throttle);	//输出油门量为pwn(多为无刷电机)
	}
	else {	//电机不在控
		if (_disarm_disable_pwm) {	//限制电机输出
			SRV_Channels::set_output_limit(SRV_Channel::k_throttle, SRV_Channel::Limit::ZERO_PWM);
		} else {
			SRV_Channels::set_output_limit(SRV_Channel::k_throttle, SRV_Channel::Limit::TRIM);
		}
	}
	set_limits_from_input(armed, steering, throttle);	//转弯量,油门量是否到极限,是,则设置limit通知其他进程
	steering = constrain_float(steering, -4500.0f, 4500.0f);
	SRV_Channels::set_output_scaled(SRV_Channel::k_steering, steering);	//输出转弯量为pwm(多为舵机)
}

if (have_vectored_thrust()) {   //向量推进器,适用对象一般为螺旋桨可变向的船
	
	// normalise desired steering and throttle to ease calculations
	// 这里所传入的steering与throttle为横向与纵向量!
	float steering_norm = steering / 4500.0f;   	//从归一化可以看出 允许输入的转弯变化范围-+45
	const float throttle_norm = throttle * 0.01f;   //允许输入的油门范围为百分比表示
	
	/* ************************************************************************* */
	// steering can never be more than throttle * tan(_vector_angle_max)
	/* 
	 * 这里是保证 steering/throttle 不超过tan(vector_angle_max_rad)
	 * 确保后面计算steering_angle的时候,steering_angle不会超过 vector_angle_max
	*/
	const float vector_angle_max_rad = radians(constrain_float(_vector_angle_max, 0.0f, 90.0f));
	const float steering_norm_lim = fabsf(throttle_norm * tanf(vector_angle_max_rad));	
	if (fabsf(steering_norm) > steering_norm_lim) {
		if (is_positive(steering_norm)) {
			steering_norm = steering_norm_lim;
		}
		if (is_negative(steering_norm)) {
			steering_norm = -steering_norm_lim;
		}
		limit.steer_right = true;
		limit.steer_left = true;
	}
	/* ************************************************************************* */
		if (!is_zero(throttle_norm)) {
			// 计算实际steering_angle转弯角度
			float steering_angle_rad = atanf(steering_norm / throttle_norm);
			// limit steering angle to vector_angle_max
			if (fabsf(steering_angle_rad) > vector_angle_max_rad) {
				steering_angle_rad = constrain_float(steering_angle_rad, -vector_angle_max_rad,\
													vector_angle_max_rad);
				limit.steer_right = true;
				limit.steer_left = true;
			}
			// 将steering_angle按照最大转角映射为-4500~4500 
			steering = steering_angle_rad / vector_angle_max_rad * 4500.0f;
	
			// throttle(纵向)根据转角大小转化真实的throttle(沿矢量方向)
			const float throttle_scaler_inv = cosf(steering_angle_rad);
			if (!is_zero(throttle_scaler_inv)) {
				throttle /= throttle_scaler_inv;
			}
		}
} else {	//正常的后轮驱动,前轮转向模型
	// scale steering down as speed increase above MOT_SPD_SCA_BASE (1 m/s default)
	if (is_positive(_speed_scale_base) && (fabsf(ground_speed) > _speed_scale_base)) {
		steering *= (_speed_scale_base / fabsf(ground_speed));
	} else {
		// regular steering rover at low speed so set limits to stop I-term build-up in controllers
		if (!have_skid_steering()) {
			limit.steer_left = true;
			imit.steer_right = true;
		}
	}
	// reverse steering direction when backing up
	if (is_negative(ground_speed)) {
		steering *= -1.0f;
	}
}	

滑动转向(用于履带车等差速模型)

输出油门量throttle和转弯量steering,函数原型:

void AP_MotorsUGV::output_skid_steering(bool armed, float steering, float throttle, float dt)

输入参数:

armed: 电机是否在控

steering: 输入转弯量(横向)

throttle: 输入油门量(纵向)

源码分析:

{
	//判断是否滑动转弯模型
    if (!have_skid_steering()) {
        return;
    }
    // 清除转向,油门限制
    set_limits_from_input(armed, steering, throttle);
    // 限制转向
    steering = constrain_float(steering, -4500.0f, 4500.0f);
    // 处理不在控电机情况
    if (!armed) {
        if (_disarm_disable_pwm) {
            SRV_Channels::set_output_limit(SRV_Channel::k_throttleLeft,RV_Channel::Limit::ZERO_PWM);
            SRV_Channels::set_output_limit(SRV_Channel::k_throttleRight,RV_Channel::Limit::ZERO_PWM);
        } else {
            SRV_Channels::set_output_limit(SRV_Channel::k_throttleLeft, SRV_Channel::Limit::TRIM);
            SRV_Channels::set_output_limit(SRV_Channel::k_throttleRight, SRV_Channel::Limit::TRIM);
        }
        return;
    }

	/* ************************************************************************* */
	/* **************************  滑动转向混合器 ******************************** */
    float steering_scaled = steering / 4500.0f; // steering scaled -1 to +1
    float throttle_scaled = throttle * 0.01f;  // throttle scaled -1 to +1
    // 界定推力不对称程度thrust_asymmetry
	//不对称因子 —— 车速前进与后退是否一致的速度
    const float thrust_asymmetry = MAX(_thrust_asymmetry, 1.0); 
	//反向油门能力最大值(这里的限值量是用作量化计算的,衡量理论上最大倒退能力,相对于正向)
    const float lower_throttle_limit = -1.0 / thrust_asymmetry;

    /**
     * 根据前进能力与后退能力的中值 来衡量其最佳转向油门
     * 比如说 当反向后退能力最大为-0.5时(此时不对称因子为2),最佳转向油门为0.25
     * 即 在油门值为0.25的情况下,剩余的双向油门空间最大
     * 正向:1~0.25 区间为0.75
     * 反向:-0.5~0.25 区间为-0.75
     */
	// Maximum steering is half way between upper and lower limits
    const float best_steering_throttle = (1.0 + lower_throttle_limit) * 0.5;   

    float steering_range;

    if (throttle_scaled < best_steering_throttle) {
        /**
         *  当油门低于最佳转向油门
         *  1. 控制油门量为正(前进),转向区间受限于反向后退能力,但不固定(向下可用空间)
         *  2. 控制油门量为负(后退),转向区间受限于反向后退能力,且固定(向下可用空间)
		 *		“固定”的因素为”倒车“是一种特殊情况,一般情况下利用正向来控制,而不是倒退
         *  
         *  为什么取决于反向能力? 
         *  是因为油门量在后续油门转向混合中不会增加,即不会超过他现在的值,
         *  最大转弯能力(throttle_range)取决于lower_throttle_limit
         */ 
        // steering range is reduced as throttle will never be increased by mixer

        steering_range = MAX(throttle_scaled,0.0) - lower_throttle_limit;

    } else {
        /**
         *  当油门高于最佳转向油门
         *  转向区间为最大转弯能力(1-best_steering_throttle)
         *  为什么是最大转弯能力,是因为油门量总是可以降低到最佳转弯油门
         */ 
		// full range available, throttle can always be lowered down to best_steering_throttle
        steering_range = 1 - best_steering_throttle;

    }

    // 约束转向
    if (steering_scaled > steering_range) {
        limit.steer_right = true;
        steering_scaled = steering_range;
    } else if (steering_scaled < -steering_range) {
        limit.steer_left = true;
        steering_scaled = -steering_range;
    }
	// 约束油门
    if (throttle_scaled > 1.0) {
        limit.throttle_upper = true;
        throttle_scaled = 1.0;
    } else if (throttle_scaled < lower_throttle_limit) {
        limit.throttle_lower = true;
        throttle_scaled = lower_throttle_limit;
    }

    // 现在thrott与steering分别都满足要求, 以下检查他们组合在一起后是否也满足要求
	const float max_output = throttle_scaled + fabsf(steering_scaled);
    const float min_output = throttle_scaled - fabsf(steering_scaled);

    // 计算饱和度,并据此按比例缩放throttle和steering
	const float max_output = throttle_scaled + fabsf(steering_scaled);
    const float min_output = throttle_scaled - fabsf(steering_scaled);

    // 计算饱和度,据此按比例缩放throttle和steering
    const float saturation_value = MAX(max_output, min_output / lower_throttle_limit);
    if (saturation_value > 1.0f) {
        // 备份,方便后面判断是否操作了throttle和steering
        const float steering_scaled_orig = steering_scaled;
        const float throttle_scaled_orig = throttle_scaled;
		
		// str_thr_mix用于界定输出饱和后,throttle和steering修改优先比例
		/**
		 *	str_thr_mix = 0.5	throttle和steering按相同比例缩放
		 *	str_thr_mix > 0.5	steering将具有更大的缩放优先度(str_thr_mix-0.5)
		 *	str_thr_mix < 0.5	throttle将具有更大的缩放优先度(str_thr_mix-0.5)
		 *	P.S. 具有优先度者,将缩小更少的值,而另一者将缩小更大的值
		 */
        const float str_thr_mix = constrain_float(_steering_throttle_mix, 0.0f, 1.0f);
        const float fair_scaler = 1.0f / saturation_value;
        if (str_thr_mix >= 0.5f) {
            // prioritise steering over throttle
            steering_scaled *= linear_interpolate(fair_scaler, 1.0f, str_thr_mix, 0.5f, 1.0f);
            if (throttle_scaled >= best_steering_throttle) {
                // throttle大于最佳值时,输出上限决定其大小
                throttle_scaled = 1.0 - fabsf(steering_scaled);
            } else {
                // throttle小于最佳转弯值时,输出下限决定其大小,这种情况用于throttle<0的情况
				/**
		 		 *	throttle小于最佳转弯值时,输出又饱和
		 		 *	若throttle>0,不可能发生throttle+steering>1的情况
		 		 *	其情况只有throttle<0时
		 		 */
                throttle_scaled = fabsf(steering_scaled) + lower_throttle_limit;
            }

        } else {
            // 同上理
            throttle_scaled *= linear_interpolate(fair_scaler, 1.0f, 0.5f - str_thr_mix, 0.0f, 0.5f);
            const float steering_sign = is_positive(steering_scaled) ? 1.0 : -1.0;
            if (throttle_scaled >= best_steering_throttle) {
                // constrained by upper limit
                steering_scaled = (1.0 - throttle_scaled) * steering_sign;
            } else {
                // constrained by lower limit
                steering_scaled = (throttle_scaled - lower_throttle_limit) * steering_sign;
            }
        }

        // 判断是否发生了缩放,若发生缩放,说明已经到达极限
        if (fabsf(steering_scaled) < fabsf(steering_scaled_orig)) {
            limit.steer_left |= is_negative(steering_scaled_orig);
            limit.steer_right |= is_positive(steering_scaled_orig);
        }
        if (fabsf(throttle_scaled) < fabsf(throttle_scaled_orig)) {
            limit.throttle_lower |= is_negative(throttle_scaled_orig);
            limit.throttle_upper |= is_positive(throttle_scaled_orig);
        }
    }

    // 合并
    float motor_left = throttle_scaled + steering_scaled;
    float motor_right = throttle_scaled - steering_scaled;

    // 反转不对称修正
    if (is_negative(motor_right)) {
        motor_right *= thrust_asymmetry;
    }
    if (is_negative(motor_left)) {
        motor_left *= thrust_asymmetry;
    }

    // 转化为pwm
    output_throttle(SRV_Channel::k_throttleLeft, 100.0f * motor_left, dt);
    output_throttle(SRV_Channel::k_throttleRight, 100.0f * motor_right, dt);
}

油门缩放

参数分析:

throttle:输入油门量大小(范围-100~100)

_throttle_min:油门死区

_thrust_curve_expo:低转低扭补偿(拉高低速油门时的转速,使其油门与推力关系更线性)

源码分析:

float AP_MotorsUGV::get_scaled_throttle(float throttle) const
{
    // 油门为0则返回
    if (is_zero(throttle)) {
        return throttle;
    }

    // 补偿油门死区 把 0-100 映射到 throttle_min~100
    if (_throttle_min > 0) {
        if (is_negative(throttle)) {
            throttle = -_throttle_min + (throttle * ((100.0f - _throttle_min) * 0.01f));
        } else {
            throttle = _throttle_min + (throttle * ((100.0f - _throttle_min) * 0.01f));
        }
    }

    // 判断是否进行油门低位曲线补偿
    if (is_zero(_thrust_curve_expo) || (_thrust_curve_expo > 1.0f) || (_thrust_curve_expo < -1.0f)) {
        return throttle;
    }

    // calculate scaler
    const float sign = (throttle < 0.0f) ? -1.0f : 1.0f;
    const float throttle_pct = constrain_float(throttle, -100.0f, 100.0f) * 0.01f;
	/**
	*	补偿模型为过(0,0) (1,1)的开口向右的抛物线,参数为_thrust_curve_expo
	*	曲线图像如下所示
	*/
    return 100.0f * sign * ((_thrust_curve_expo - 1.0f) + safe_sqrt((1.0f - _thrust_curve_expo) * (1.0f - _thrust_curve_expo) + 4.0f * _thrust_curve_expo * fabsf(throttle_pct))) / (2.0f * _thrust_curve_expo);
}

对于油门死区缩放:

对于油门推力补偿:

红色线段为 绿色线段为

可见在低油门下,可以通过调整参数,来提高低油门转速,进而线形推力。

待研究代码

ModeCircle

void ModeCircle::update()
{
    // get current position
    Vector2f curr_pos;
    const bool position_ok = AP::ahrs().get_relative_position_NE_origin(curr_pos);

    // if no position estimate stop vehicle
    if (!position_ok) {
        stop_vehicle();
        return;
    }

    // Update distance to destination and distance to edge
    const Vector2f center_to_veh = curr_pos - config.center_pos;
    _distance_to_destination = (target.pos.tofloat() - curr_pos).length();
    dist_to_edge_m = fabsf(center_to_veh.length() - config.radius);

    // Update depending on stage
    if (!reached_edge) {
        update_drive_to_radius();
    } else if (dist_to_edge_m > 2 * MAX(g2.turn_radius, AR_CIRCLE_REACHED_EDGE_DIST) && !tracking_back) {
        // if more than 2* turn_radius outside circle radius, slow vehicle by 20%
        config.speed = 0.8 * config.speed;
        GCS_SEND_TEXT(MAV_SEVERITY_WARNING, "Circle: cannot keep up, slowing to %.1fm/s", config.speed);
        tracking_back = true;
    } else if (dist_to_edge_m < MAX(g2.turn_radius, AR_CIRCLE_REACHED_EDGE_DIST) && tracking_back) {
        // if within turn_radius, call the vehicle back on track
        tracking_back = false;
    } else {
        update_circling();
    }

    g2.pos_control.update(rover.G_Dt);

    // get desired speed and turn rate from pos_control
    const float desired_speed = g2.pos_control.get_desired_speed();
    const float desired_turn_rate = g2.pos_control.get_desired_turn_rate_rads();

    // run steering and throttle controllers
    calc_steering_from_turn_rate(desired_turn_rate);
    calc_throttle(desired_speed, true);
}

山和山不相遇,人与人要相逢