之前在分析RCOutput的源码时候,其中的output_ch的成员函数实现过程中,对RC通道的Function进行了分类输出,具体的分成了4类:
Function manual 下RC通道为通道直通;
Function rc1~rc16 下RC通道则是映射到实际的ch0~ch15;
Function motor1~motor8 则是通过AP_Motors::rc_write来实现控制;
其他,则是直接输出到当前SRV_Channel绑定的物理通道;
所以这里就对AP_Motors进行分析:
// offsets for motors in motor_out and _motor_filtered arrays
#define AP_MOTORS_MOT_1 0U
#define AP_MOTORS_MOT_2 1U
#define AP_MOTORS_MOT_3 2U
#define AP_MOTORS_MOT_4 3U
#define AP_MOTORS_MOT_5 4U
#define AP_MOTORS_MOT_6 5U
#define AP_MOTORS_MOT_7 6U
#define AP_MOTORS_MOT_8 7U
#define AP_MOTORS_MAX_NUM_MOTORS 8
// motor update rate
#define AP_MOTORS_SPEED_DEFAULT 490 // default output rate to the motors
/// @class AP_Motors
class AP_Motors {
public:
enum motor_frame_class {
MOTOR_FRAME_UNDEFINED = 0,
MOTOR_FRAME_QUAD = 1,
MOTOR_FRAME_HEXA = 2,
MOTOR_FRAME_OCTA = 3,
MOTOR_FRAME_OCTAQUAD = 4,
MOTOR_FRAME_Y6 = 5,
MOTOR_FRAME_HELI = 6,
MOTOR_FRAME_TRI = 7,
MOTOR_FRAME_SINGLE = 8,
MOTOR_FRAME_COAX = 9,
MOTOR_FRAME_TAILSITTER = 10,
};
enum motor_frame_type {
MOTOR_FRAME_TYPE_PLUS = 0,
MOTOR_FRAME_TYPE_X = 1,
MOTOR_FRAME_TYPE_V = 2,
MOTOR_FRAME_TYPE_H = 3,
MOTOR_FRAME_TYPE_VTAIL = 4,
MOTOR_FRAME_TYPE_ATAIL = 5,
MOTOR_FRAME_TYPE_Y6B = 10,
MOTOR_FRAME_TYPE_Y6F = 11 // for FireFlyY6
};
// Constructor
AP_Motors(uint16_t loop_rate, uint16_t speed_hz = AP_MOTORS_SPEED_DEFAULT);
// check initialisation succeeded
bool initialised_ok() const { return _flags.initialised_ok; }
// arm, disarm or check status status of motors,通过AP_Notify传递解锁状态
bool armed() const { return _flags.armed; }
void armed(bool arm);
// set motor interlock status
void set_interlock(bool set) { _flags.interlock = set;}
// get motor interlock status. true means motors run, false motors don't run
bool get_interlock() const { return _flags.interlock; }
// set_roll, set_pitch, set_yaw, set_throttle,设置各个通道输入值范围
void set_roll(float roll_in) { _roll_in = roll_in; }; // range -1 ~ +1
void set_pitch(float pitch_in) { _pitch_in = pitch_in; }; // range -1 ~ +1
void set_yaw(float yaw_in) { _yaw_in = yaw_in; }; // range -1 ~ +1
void set_throttle(float throttle_in) { _throttle_in = throttle_in; }; // range 0 ~ 1
void set_throttle_avg_max(float throttle_avg_max) { _throttle_avg_max = constrain_float(throttle_avg_max,0.0f,1.0f); }; // range 0 ~ 1
void set_throttle_filter_cutoff(float filt_hz) { _throttle_filter.set_cutoff_frequency(filt_hz); }
void set_forward(float forward_in) { _forward_in = forward_in; }; // range -1 ~ +1
void set_lateral(float lateral_in) { _lateral_in = lateral_in; }; // range -1 ~ +1
// accessors for roll, pitch, yaw and throttle inputs to motors,获取当前各个通道的输入值
float get_roll() const { return _roll_in; }
float get_pitch() const { return _pitch_in; }
float get_yaw() const { return _yaw_in; }
float get_throttle() const { return constrain_float(_throttle_filter.get(),0.0f,1.0f); }
float get_throttle_bidirectional() const { return constrain_float(2*(_throttle_filter.get()-0.5f),-1.0f,1.0f); }
float get_forward() const { return _forward_in; }
float get_lateral() const { return _lateral_in; }
virtual float get_throttle_hover() const = 0;
// spool up states
enum spool_up_down_desired {
DESIRED_SHUT_DOWN = 0, // all motors stop
DESIRED_SPIN_WHEN_ARMED = 1, // all motors at spin when armed
DESIRED_THROTTLE_UNLIMITED = 2, // motors are no longer constrained by start up procedure
};
virtual void set_desired_spool_state(enum spool_up_down_desired spool) { _spool_desired = spool; };
enum spool_up_down_desired get_desired_spool_state(void) const { return _spool_desired; }
//
// voltage, current and air pressure compensation or limiting features - multicopters only
//
// set_voltage - set voltage to be used for output scaling
void set_voltage(float volts){ _batt_voltage = volts; }
// set_current - set current to be used for output scaling
void set_current(float current){ _batt_current = current; }
// set_density_ratio - sets air density as a proportion of sea level density
void set_air_density_ratio(float ratio) { _air_density_ratio = ratio; }
// structure for holding motor limit flags
struct AP_Motors_limit {
uint8_t roll_pitch : 1; // we have reached roll or pitch limit
uint8_t yaw : 1; // we have reached yaw limit
uint8_t throttle_lower : 1; // we have reached throttle's lower limit
uint8_t throttle_upper : 1; // we have reached throttle's upper limit
} limit;
//
// virtual functions that should be implemented by child classes
//
// set update rate to motors - a value in hertz,设置Motors的更新频率
virtual void set_update_rate( uint16_t speed_hz ) { _speed_hz = speed_hz; }
// init,设置机身类型[6轴、8轴等轴数]和机身布局类型[V、H等布局类型]
virtual void init(motor_frame_class frame_class, motor_frame_type frame_type) = 0;
// set frame class (i.e. quad, hexa, heli) and type (i.e. x, plus),设置机身类型和机身布局类型
virtual void set_frame_class_and_type(motor_frame_class frame_class, motor_frame_type frame_type) = 0;
// enable - starts allowing signals to be sent to motors,使能hal对应通道
virtual void enable() = 0;
// output - sends commands to the motors
virtual void output() = 0;
// output_min - sends minimum values out to the motors
virtual void output_min() = 0;
// output_test - spin a motor at the pwm value specified
// motor_seq is the motor's sequence number from 1 to the number of motors on the frame
// pwm value is an actual pwm value that will be output, normally in the range of 1000 ~ 2000
// 指定电机以固定的频率旋转
virtual void output_test(uint8_t motor_seq, int16_t pwm) = 0;
// get_motor_mask - returns a bitmask of which outputs are being used for motors (1 means being used)
// this can be used to ensure other pwm outputs (i.e. for servos) do not conflict
// 返回当前正在运行的电机编号
virtual uint16_t get_motor_mask() = 0;
// pilot input in the -1 ~ +1 range for roll, pitch and yaw. 0~1 range for throttle
// RC通道直通为姿态输入,直接控制电机
void set_radio_passthrough(float roll_input, float pitch_input, float throttle_input, float yaw_input);
// set loop rate. Used to support loop rate as a parameter
void set_loop_rate(uint16_t loop_rate) { _loop_rate = loop_rate; }
enum pwm_type { PWM_TYPE_NORMAL=0, PWM_TYPE_ONESHOT=1, PWM_TYPE_ONESHOT125=2, PWM_TYPE_BRUSHED16kHz=3 };
pwm_type get_pwm_type(void) const { return (pwm_type)_pwm_type.get(); }
protected:
// output functions that should be overloaded by child classes
// 下面这些就是用于派生类 多态实现,具体分析我们参照实际的例子来
virtual void output_armed_stabilizing()=0;
virtual void rc_write(uint8_t chan, uint16_t pwm);
virtual void rc_set_freq(uint32_t mask, uint16_t freq_hz);
virtual void rc_enable_ch(uint8_t chan);
virtual uint32_t rc_map_mask(uint32_t mask) const;
// add a motor to the motor map,增加一个电机
void add_motor_num(int8_t motor_num);
// update the throttle input filter,更新油门的滤波器
virtual void update_throttle_filter() = 0;
// save parameters as part of disarming,在上锁的时候保存参数
virtual void save_params_on_disarm() {}
// convert input in -1 to +1 range to pwm output,转换输入pwm范围到[-1,1]
int16_t calc_pwm_output_1to1(float input, const SRV_Channel *servo);
// convert input in 0 to +1 range to pwm output,转换输入pwm到[0,1]
int16_t calc_pwm_output_0to1(float input, const SRV_Channel *servo);
// flag bitmask
struct AP_Motors_flags {
uint8_t armed : 1; // 0 if disarmed, 1 if armed
uint8_t interlock : 1; // 1 if the motor interlock is enabled (i.e. motors run), 0 if disabled (motors don't run)
uint8_t initialised_ok : 1; // 1 if initialisation was successful
} _flags;
// internal variables 内部变量
uint16_t _loop_rate; // rate in Hz at which output() function is called (normally 400hz)AP_Motor的执行频率
uint16_t _speed_hz; // speed in hz to send updates to motors pwm输出的更新频率
float _roll_in; // desired roll control from attitude controllers, -1 ~ +1
float _pitch_in; // desired pitch control from attitude controller, -1 ~ +1
float _yaw_in; // desired yaw control from attitude controller, -1 ~ +1
float _throttle_in; // last throttle input from set_throttle caller
float _forward_in; // last forward input from set_forward caller
float _lateral_in; // last lateral input from set_lateral caller
float _throttle_avg_max; // last throttle input from set_throttle_avg_max
LowPassFilterFloat _throttle_filter; // throttle input filter,油门低通滤波器
spool_up_down_desired _spool_desired; // desired spool state
// battery voltage, current and air pressure compensation variables,电池电压,电流和气压补偿变量
float _batt_voltage; // latest battery voltage reading
float _batt_current; // latest battery current reading
float _air_density_ratio; // air density / sea level density - decreases in altitude
// mapping to output channels,电机通道映射
uint8_t _motor_map[AP_MOTORS_MAX_NUM_MOTORS];
uint16_t _motor_map_mask;
uint16_t _motor_fast_mask;
// pass through variables,这个不明白意思
float _roll_radio_passthrough = 0.0f; // roll input from pilot in -1 ~ +1 range. used for setup and providing servo feedback while landed
float _pitch_radio_passthrough = 0.0f; // pitch input from pilot in -1 ~ +1 range. used for setup and providing servo feedback while landed
float _throttle_radio_passthrough = 0.0f; // throttle/collective input from pilot in 0 ~ 1 range. used for setup and providing servo feedback while landed
float _yaw_radio_passthrough = 0.0f; // yaw input from pilot in -1 ~ +1 range. used for setup and providing servo feedback while landed
AP_Int8 _pwm_type; // PWM output type,PWM输出模式
};
从上面的数据结构来看,整个AP_Motors是定义了一个面向电机组从输入到输出的框架,从数据结构上来看,分为两个部分:
1.方法,自身实现了的方法包括了对姿态期望自输入的归一化,以及电压、电流、气压计补偿值等参数的设置;虚函数则是主要对实际rc输出的实现,这里不同的机型和布局,是存在区别;
2.变量,变量上主要也是对姿态控制器出来的数值的读入、电流、电压等一些数据的读入、电机通道的映射…
简单来说AP_Motors就是读取并保存姿态控制器计算出来的姿态各个方向的值,并归一化,然后依据motor_map的映射关系对应通道输出;
下面来找一个实际的多旋翼的例子进行说明:
void Copter::init_ardupilot()下面调用Copter::allocate_motors(void),
/*
allocate the motors class
*/
void Copter::allocate_motors(void)
{
const struct AP_Param::GroupInfo *var_info;
switch ((AP_Motors::motor_frame_class)g2.frame_class.get()) {
/*这是针对非直升机的模型,对应的motor的类型都是AP_MotorsMatrix*/
#if FRAME_CONFIG != HELI_FRAME
case AP_Motors::MOTOR_FRAME_QUAD://四旋翼
case AP_Motors::MOTOR_FRAME_HEXA://六旋翼
case AP_Motors::MOTOR_FRAME_Y6: //三旋翼上下桨
case AP_Motors::MOTOR_FRAME_OCTA://八旋翼
case AP_Motors::MOTOR_FRAME_OCTAQUAD://四旋翼上下桨
default:
motors = new AP_MotorsMatrix(MAIN_LOOP_RATE);
var_info = AP_MotorsMatrix::var_info;
break;
/*这是针对三旋翼的模型,对应的motor的类型是AP_MotorsTri*/
case AP_Motors::MOTOR_FRAME_TRI:
motors = new AP_MotorsTri(MAIN_LOOP_RATE);
var_info = AP_MotorsTri::var_info;
AP_Param::set_frame_type_flags(AP_PARAM_FRAME_TRICOPTER);
break;
/*这是针对单轴的模型,对应AP_MotorsSingle*/
case AP_Motors::MOTOR_FRAME_SINGLE:
motors = new AP_MotorsSingle(MAIN_LOOP_RATE);
var_info = AP_MotorsSingle::var_info;
break;
/*这是针对单轴的模型,对应AP_MotorsCoax*/
case AP_Motors::MOTOR_FRAME_COAX:
motors = new AP_MotorsCoax(MAIN_LOOP_RATE);
var_info = AP_MotorsCoax::var_info;
break;
/*这是针对单轴的模型,对应AP_MotorsTailsitter*/
case AP_Motors::MOTOR_FRAME_TAILSITTER:
motors = new AP_MotorsTailsitter(MAIN_LOOP_RATE);
var_info = AP_MotorsTailsitter::var_info;
break;
#else // FRAME_CONFIG == HELI_FRAME直升机模型
case AP_Motors::MOTOR_FRAME_HELI:
default:
motors = new AP_MotorsHeli_Single(MAIN_LOOP_RATE);
var_info = AP_MotorsHeli::var_info;
AP_Param::set_frame_type_flags(AP_PARAM_FRAME_HELI);
break;
#endif
}
if (motors == nullptr) {
AP_HAL::panic("Unable to allocate FRAME_CLASS=%u", (unsigned)g2.frame_class.get());
}
AP_Param::load_object_from_eeprom(motors, var_info);
AP_AHRS_View *ahrs_view = ahrs.create_view(ROTATION_NONE);
if (ahrs_view == nullptr) {
AP_HAL::panic("Unable to allocate AP_AHRS_View");
}
/*下面是控制模型*/
/*非直升机的控制模型均为AC_AttitudeControl_Multi*/
#if FRAME_CONFIG != HELI_FRAME
attitude_control = new AC_AttitudeControl_Multi(*ahrs_view, aparm, *motors, MAIN_LOOP_SECONDS);
var_info = AC_AttitudeControl_Multi::var_info;
#else
/*直升机的控制模型为AC_AttitudeControl_Heli*/
attitude_control = new AC_AttitudeControl_Heli(*ahrs_view, aparm, *motors, MAIN_LOOP_SECONDS);
var_info = AC_AttitudeControl_Heli::var_info;
#endif
if (attitude_control == nullptr) {
AP_HAL::panic("Unable to allocate AttitudeControl");
}
AP_Param::load_object_from_eeprom(attitude_control, var_info);
/*位置控制模型均为AC_PosControl*/
pos_control = new AC_PosControl(*ahrs_view, inertial_nav, *motors, *attitude_control,
g.p_alt_hold, g.p_vel_z, g.pid_accel_z,
g.p_pos_xy, g.pi_vel_xy);
if (pos_control == nullptr) {
AP_HAL::panic("Unable to allocate PosControl");
}
AP_Param::load_object_from_eeprom(pos_control, pos_control->var_info);
/*航点导航均为AC_WPNav*/
wp_nav = new AC_WPNav(inertial_nav, *ahrs_view, *pos_control, *attitude_control);
if (wp_nav == nullptr) {
AP_HAL::panic("Unable to allocate WPNav");
}
AP_Param::load_object_from_eeprom(wp_nav, wp_nav->var_info);
/*绕圈AC_Circle*/
circle_nav = new AC_Circle(inertial_nav, *ahrs_view, *pos_control);
if (wp_nav == nullptr) {
AP_HAL::panic("Unable to allocate CircleNav");
}
AP_Param::load_object_from_eeprom(circle_nav, circle_nav->var_info);
// reload lines from the defaults file that may now be accessible
AP_Param::reload_defaults_file();
// now setup some frame-class specific defaults
根据不同的机架类型,设定不同的PID参数
switch ((AP_Motors::motor_frame_class)g2.frame_class.get()) {
case AP_Motors::MOTOR_FRAME_Y6:
attitude_control->get_rate_roll_pid().kP().set_default(0.1);
attitude_control->get_rate_roll_pid().kD().set_default(0.006);
attitude_control->get_rate_pitch_pid().kP().set_default(0.1);
attitude_control->get_rate_pitch_pid().kD().set_default(0.006);
attitude_control->get_rate_yaw_pid().kP().set_default(0.15);
attitude_control->get_rate_yaw_pid().kI().set_default(0.015);
break;
case AP_Motors::MOTOR_FRAME_TRI:
attitude_control->get_rate_yaw_pid().filt_hz().set_default(100);
break;
default:
break;
}
if (upgrading_frame_params) {
// do frame specific upgrade. This is only done the first time we run the new firmware
#if FRAME_CONFIG == HELI_FRAME
SRV_Channels::upgrade_motors_servo(Parameters::k_param_motors, 12, CH_1);
SRV_Channels::upgrade_motors_servo(Parameters::k_param_motors, 13, CH_2);
SRV_Channels::upgrade_motors_servo(Parameters::k_param_motors, 14, CH_3);
SRV_Channels::upgrade_motors_servo(Parameters::k_param_motors, 15, CH_4);
#else
if (g2.frame_class == AP_Motors::MOTOR_FRAME_TRI) {
const AP_Param::ConversionInfo tri_conversion_info[] = {
{ Parameters::k_param_motors, 32, AP_PARAM_INT16, "SERVO7_TRIM" },
{ Parameters::k_param_motors, 33, AP_PARAM_INT16, "SERVO7_MIN" },
{ Parameters::k_param_motors, 34, AP_PARAM_INT16, "SERVO7_MAX" },
{ Parameters::k_param_motors, 35, AP_PARAM_FLOAT, "MOT_YAW_SV_ANGLE" },
};
// we need to use CONVERT_FLAG_FORCE as the SERVO7_* parameters will already be set from RC7_*
AP_Param::convert_old_parameters(tri_conversion_info, ARRAY_SIZE(tri_conversion_info), AP_Param::CONVERT_FLAG_FORCE);
const AP_Param::ConversionInfo tri_conversion_info_rev { Parameters::k_param_motors, 31, AP_PARAM_INT8, "SERVO7_REVERSED" };
AP_Param::convert_old_parameter(&tri_conversion_info_rev, 1, AP_Param::CONVERT_FLAG_REVERSE | AP_Param::CONVERT_FLAG_FORCE);
// AP_MotorsTri was converted from having nested var_info to one level
AP_Param::convert_parent_class(Parameters::k_param_motors, motors, motors->var_info);
}
#endif
}
// upgrade parameters. This must be done after allocating the objects
convert_pid_parameters();
}
从void Copter::allocate_motors中就会首先依据机架类型设定电机控制模型和姿态控制模型;
class AP_MotorsMulticopter : public AP_Motors
void Copter::loop()
|
V
void Copter::fast_loop()
|
V
void Copter::motors_output()
而在Copter这个类中就定义了:
#if FRAME_CONFIG == HELI_FRAME
#define MOTOR_CLASS AP_MotorsHeli_Single
#else
#define MOTOR_CLASS AP_MotorsMulticopter
#endif
MOTOR_CLASS *motors;
所以可以明显看到整个AP_Motors的运行就开始了
void Copter::motors_output()
还是先从void Copter::motors_output()这个函数的开始,上层的调用逻辑暂时先不去管;
// motors_output - send output to motors library which will adjust and send to ESCs and servos
// 电机输出,其结果是发送给ESCs和伺服接口
void Copter::motors_output()
{
#if ADVANCED_FAILSAFE == ENABLED
// this is to allow the failsafe module to deliberately crash
// the vehicle. Only used in extreme circumstances to meet the
// OBC rules,我靠,这段是用来故意使机器坠毁的代码,感觉好恶意满满
if (g2.afs.should_crash_vehicle()) {
g2.afs.terminate_vehicle();
return;
}
#endif
// Update arming delay state,解锁延迟,也就是机器解锁后不立即启动。
if (ap.in_arming_delay && (!motors->armed() || millis()-arm_time_ms > ARMING_DELAY_SEC*1.0e3f || control_mode == THROW)) {
ap.in_arming_delay = false;
}
// check if we are performing the motor test,检测当前是否处于电机测试模式
if (ap.motor_test) {
motor_test_output();//执行电机测试输出
} else {
bool interlock = motors->armed() && !ap.in_arming_delay && (!ap.using_interlock || ap.motor_interlock_switch) && !ap.motor_emergency_stop;
if (!motors->get_interlock() && interlock) {
motors->set_interlock(true);
Log_Write_Event(DATA_MOTORS_INTERLOCK_ENABLED);
} else if (motors->get_interlock() && !interlock) {
motors->set_interlock(false);
Log_Write_Event(DATA_MOTORS_INTERLOCK_DISABLED);
}
// send output signals to motors,执行电机输出
motors->output();
}
}
void AP_MotorsMulticopter::output()
接下来就来分析output具体的实现过程了,这也是AP_Motors虚函数的第一个实现
// output - sends commands to the motors
void AP_MotorsMulticopter::output()
{
// update throttle filter,对油门进行一阶低通滤波
update_throttle_filter();
// update battery resistance,更新电池电阻(确保大机动运动的时候,电池电压稳定)
update_battery_resistance();
// calc filtered battery voltage and lift_max,计算电池电压的最大升力
update_lift_max_from_batt_voltage();
// run spool logic
//将电机输出设定为几个阶段:
//SHUT_DOWN :Motors停转无输出,Servos输出保持中位或测试条件值
//SPIN_WHEN_ARMED :Motors停转无输出或者开始输出旋转,Servos开始输出
//SPOOL_UP :Motors最大油门输出,Servos正常输出
//THROTTLE_UNLIMITED :Motors正常输出,Servos正常输出
//SPOOL_DOWN :Motors最小输出,Servos正常输出
output_logic();
// calculate thrust
//计算推力
output_armed_stabilizing();
// apply any thrust compensation for the frame
// 对机架进行推力补偿
thrust_compensation();
// convert rpy_thrust values to pwm
// 根据_spool_mode分别计算出motor_out[AP_MOTORS_MAX_NUM_MOTORS]的PWM值
// 然后通过hal.rcout->write(chan, pwm);进行输出
output_to_motors();
};
到这里面向电机的输入输出的代码梳理完成了,接下来分析这这个过程中用到的一些关键算法和逻辑处理代码
这里就从void AP_MotorsMulticopter::output()开始梳理
fl = 1/(2πRC),fl为截止频率;-------①
w0 = 1/(RC),w0为截止角频率;-------②
通过时域、频域的转换,最终得到采样系数 a = t/(RC),t为采样周期
a为与RC值有关的一个参数,称为滤波系数,其值决定新采样值在本次滤波结果中所占的权重,其值通常远小于1
而在最终得到a的过程中RC还需通过式①转换截止频率间接得到
以下:
{
float rc = 1.0f/(M_2PI*cutoff_freq);
alpha = constrain_float(dt/(dt+rc), 0.0f, 1.0f);
_output += (sample - _output) * alpha;
return _output;
}
电池负载更新(计算的电池内阻+外阻)
这里计算了PWM到电机升力的近似线性转换:
_lift_max = batt_voltage_filt*(1-_thrust_curve_expo) + _thrust_curve_expo*batt_voltage_filt*batt_voltage_filt;
做这一步的目的是对后面每个电机的补偿(这个没懂原理)
- 电机输出逻辑(分步实现电机运行)
//SHUT_DOWN :Motors停转无输出,Servos输出保持中位或测试条件值
//SPIN_WHEN_ARMED :Motors停转无输出或者开始输出旋转,Servos开始输出
//SPOOL_UP :Motors最大油门输出,Servos正常输出
//THROTTLE_UNLIMITED :Motors正常输出,Servos正常输出
//SPOOL_DOWN :Motors最小输出,Servos正常输出
这几个模式之间的切换的结果是改变了_spin_up_ratio和_throttle_thrust_max