本节主要学习ardupilot地效补偿代码,欢迎批评指正!
Copter3.4版本(或者更高版本)包括地面效应补偿,当无人机可能处于起飞或着陆时,地面效应补偿可降低气压计的权重(以加速度计为准)。与降低螺旋桨的长度相比,这减少了无人机因脚架比较短导致着陆时的反弹现象。
如果您的无人机在着陆时没有受到弹跳的影响,最好关闭此功能,因为它会稍微增加高振动水平扰乱高度估计的风险。
需要注意的是:在Copter3.4版本已经引入了地效补偿
使用地面站(即任务规划器)连接到飞行控制器,并将GND_effect_comp参数设置为“1”。
具体可以看下图:
SCHED_TASK(throttle_loop, 50, 75), //更新油门函数
void Copter::throttle_loop()
{
//更新油门低补偿值(优先控制油门与姿态)--- update throttle_low_comp value (controls priority of throttle vs attitude control)
update_throttle_thr_mix();
//检查无人机的自动上锁状态-----check auto_armed status
update_auto_armed();
//直升机代码
#if FRAME_CONFIG == HELI_FRAME
// update rotor speed
heli_update_rotor_speed_targets();
// update trad heli swash plate movement
heli_update_landing_swash();
#endif
// 弥补为地效影响----compensate for ground effect (if enabled)
update_ground_effect_detector();
}
void Copter::update_ground_effect_detector(void)
{
if(!g2.gndeffect_comp_enabled || !motors->armed()) //如果地效补偿没有使能,或者电机没有解锁
{
// disarmed - disable ground effect and return
gndeffect_state.takeoff_expected = false; //期望起飞状态
gndeffect_state.touchdown_expected = false;//期望着地状态
ahrs.setTakeoffExpected(gndeffect_state.takeoff_expected); //都设置成0
ahrs.setTouchdownExpected(gndeffect_state.touchdown_expected); //都设置成0
return;
}
//变量初始化----variable initialization
uint32_t tnow_ms = millis();
float xy_des_speed_cms = 0.0f;
float xy_speed_cms = 0.0f;
float des_climb_rate_cms = pos_control->get_desired_velocity().z;
if (pos_control->is_active_xy()) {
Vector3f vel_target = pos_control->get_vel_target();
vel_target.z = 0.0f;
xy_des_speed_cms = vel_target.length();
}
if (position_ok() || optflow_position_ok()) {
Vector3f vel = inertial_nav.get_velocity();
vel.z = 0.0f;
xy_speed_cms = vel.length();
}
// takeoff logic
// if we are armed and haven't yet taken off
if (motors->armed() && ap.land_complete && !gndeffect_state.takeoff_expected) {
gndeffect_state.takeoff_expected = true;
}
// if we aren't taking off yet, reset the takeoff timer, altitude and complete flag
const bool throttle_up = flightmode->has_manual_throttle() && channel_throttle->get_control_in() > 0;
if (!throttle_up && ap.land_complete) {
gndeffect_state.takeoff_time_ms = tnow_ms;
gndeffect_state.takeoff_alt_cm = inertial_nav.get_altitude();
}
// if we are in takeoff_expected and we meet the conditions for having taken off
// end the takeoff_expected state
if (gndeffect_state.takeoff_expected && (tnow_ms-gndeffect_state.takeoff_time_ms > 5000 || inertial_nav.get_altitude()-gndeffect_state.takeoff_alt_cm > 50.0f)) {
gndeffect_state.takeoff_expected = false;
}
// landing logic
Vector3f angle_target_rad = attitude_control->get_att_target_euler_cd() * radians(0.01f);
bool small_angle_request = cosf(angle_target_rad.x)*cosf(angle_target_rad.y) > cosf(radians(7.5f));
bool xy_speed_low = (position_ok() || optflow_position_ok()) && xy_speed_cms <= 125.0f;
bool xy_speed_demand_low = pos_control->is_active_xy() && xy_des_speed_cms <= 125.0f;
bool slow_horizontal = xy_speed_demand_low || (xy_speed_low && !pos_control->is_active_xy()) || (control_mode == ALT_HOLD && small_angle_request);
bool descent_demanded = pos_control->is_active_z() && des_climb_rate_cms < 0.0f;
bool slow_descent_demanded = descent_demanded && des_climb_rate_cms >= -100.0f;
bool z_speed_low = fabsf(inertial_nav.get_velocity_z()) <= 60.0f;
bool slow_descent = (slow_descent_demanded || (z_speed_low && descent_demanded));
gndeffect_state.touchdown_expected = slow_horizontal && slow_descent;
// Prepare the EKF for ground effect if either takeoff or touchdown is expected.
ahrs.setTakeoffExpected(gndeffect_state.takeoff_expected);
ahrs.setTouchdownExpected(gndeffect_state.touchdown_expected);
}
分析:
ahrs.setTakeoffExpected(gndeffect_state.takeoff_expected); //都设置成0
ahrs.setTouchdownExpected(gndeffect_state.touchdown_expected); //都设置成0
上面两个函数主要被EKF2和EKF3调用
1.先看起飞:
ahrs.setTakeoffExpected(gndeffect_state.takeoff_expected); //都设置成0
void NavEKF2::setTakeoffExpected(bool val)
{
if (core)
{
for (uint8_t i=0; i
void NavEKF3::setTakeoffExpected(bool val)
{
if (core) {
for (uint8_t i=0; i
主要干嘛呢?
// called by vehicle code to specify that a takeoff is happening
// causes the EKF to compensate for expected barometer errors due to ground effect
被无人机发生起飞事件调用,使用ekf算法来补偿由于地面效应引起的预期气压计误差
2.着陆:
ahrs.setTakeoffExpected(gndeffect_state.takeoff_expected); //都设置成0
void NavEKF2::setTouchdownExpected(bool val)
{
if (core) {
for (uint8_t i=0; i
void NavEKF3::setTouchdownExpected(bool val)
{
if (core) {
for (uint8_t i=0; i
上面函数作用:
// called by vehicle code to specify that a touchdown is expected to happen
// causes the EKF to compensate for expected barometer errors due to ground effect
被无人机发生着陆事件调用,使用ekf算法来补偿由于地面效应引起的预期气压计误差
从上面两个函数,我们重点要分析下面两组变量:
core[i].setTakeoffExpected(val);
core[i].setTouchdownExpected(val);
起飞函数分析
void NavEKF2_core::setTakeoffExpected(bool val)
{
takeoffExpectedSet_ms = imuSampleTime_ms;
expectGndEffectTakeoff = val; //最终设置的起飞地效标志位
}
降落函数分析
void NavEKF2_core::setTouchdownExpected(bool val)
{
touchdownExpectedSet_ms = imuSampleTime_ms;
expectGndEffectTouchdown = val;//最终设置的着陆地效标志位
}
下面主要针对EKF2来研究,EKF3基本一样
expectGndEffectTakeoff = val; //最终设置的起飞地效标志位
expectGndEffectTouchdown = val;//最终设置的着陆地效标志位
bool NavEKF2_core::getTakeoffExpected()
{
if (expectGndEffectTakeoff && imuSampleTime_ms - takeoffExpectedSet_ms > frontend->gndEffectTimeout_ms)
{
expectGndEffectTakeoff = false;
}
return expectGndEffectTakeoff;
}
第一处使用
如果我们处于起飞模式,高度测量限制为不小于起飞开始时的测量值。
这可以防止由于无人机下降产生的下降气流产生的不良的气压的干扰,从而在初始上升过程中破坏EKF高度。
// If we are in takeoff mode, the height measurement is limited to be no less than the measurement at start of takeoff
// This prevents negative baro disturbances due to copter downwash corrupting the EKF altitude during initial ascent
if (getTakeoffExpected())
{
baroDataNew.hgt = MAX(baroDataNew.hgt, meaHgtAtTakeOff);
}
第二处使用
如果没有使能地效补偿
if (!getTakeoffExpected())
{
const float gndHgtFiltTC = 0.5f;
const float dtBaro = frontend->hgtAvg_ms*1.0e-3f;
float alpha = constrain_float(dtBaro / (dtBaro+gndHgtFiltTC),0.0f,1.0f);
meaHgtAtTakeOff += (baroDataDelayed.hgt-meaHgtAtTakeOff)*alpha;
}
第三,四处使用
// reduce weighting (increase observation noise) on baro if we are likely to be in ground effect
if (getTakeoffExpected() || getTouchdownExpected())
{
posDownObsNoise *= frontend->gndEffectBaroScaler;
}
// If we are in takeoff mode, the height measurement is limited to be no less than the measurement at start of takeoff
// This prevents negative baro disturbances due to copter downwash corrupting the EKF altitude during initial ascent
if (motorsArmed && getTakeoffExpected())
{
hgtMea = MAX(hgtMea, meaHgtAtTakeOff);
}
核心代码处理
void NavEKF2_core::selectHeightForFusion()
{
// Read range finder data and check for new data in the buffer
// This data is used by both height and optical flow fusion processing
readRangeFinder();
rangeDataToFuse = storedRange.recall(rangeDataDelayed,imuDataDelayed.time_ms);
// correct range data for the body frame position offset relative to the IMU
// the corrected reading is the reading that would have been taken if the sensor was
// co-located with the IMU
if (rangeDataToFuse) {
AP_RangeFinder_Backend *sensor = frontend->_rng.get_backend(rangeDataDelayed.sensor_idx);
if (sensor != nullptr) {
Vector3f posOffsetBody = sensor->get_pos_offset() - accelPosOffset;
if (!posOffsetBody.is_zero()) {
Vector3f posOffsetEarth = prevTnb.mul_transpose(posOffsetBody);
rangeDataDelayed.rng += posOffsetEarth.z / prevTnb.c.z;
}
}
}
// read baro height data from the sensor and check for new data in the buffer
readBaroData();
baroDataToFuse = storedBaro.recall(baroDataDelayed, imuDataDelayed.time_ms);
// select height source
if (extNavUsedForPos) {
// always use external vision as the hight source if using for position.
activeHgtSource = HGT_SOURCE_EV;
} else if (((frontend->_useRngSwHgt > 0) || (frontend->_altSource == 1)) && (imuSampleTime_ms - rngValidMeaTime_ms < 500)) {
if (frontend->_altSource == 1) {
// always use range finder
activeHgtSource = HGT_SOURCE_RNG;
} else {
// determine if we are above or below the height switch region
float rangeMaxUse = 1e-4f * (float)frontend->_rng.max_distance_cm_orient(ROTATION_PITCH_270) * (float)frontend->_useRngSwHgt;
bool aboveUpperSwHgt = (terrainState - stateStruct.position.z) > rangeMaxUse;
bool belowLowerSwHgt = (terrainState - stateStruct.position.z) < 0.7f * rangeMaxUse;
// If the terrain height is consistent and we are moving slowly, then it can be
// used as a height reference in combination with a range finder
// apply a hysteresis to the speed check to prevent rapid switching
float horizSpeed = norm(stateStruct.velocity.x, stateStruct.velocity.y);
bool dontTrustTerrain = ((horizSpeed > frontend->_useRngSwSpd) && filterStatus.flags.horiz_vel) || !terrainHgtStable;
float trust_spd_trigger = MAX((frontend->_useRngSwSpd - 1.0f),(frontend->_useRngSwSpd * 0.5f));
bool trustTerrain = (horizSpeed < trust_spd_trigger) && terrainHgtStable;
/*
* Switch between range finder and primary height source using height above ground and speed thresholds with
* hysteresis to avoid rapid switching. Using range finder for height requires a consistent terrain height
* which cannot be assumed if the vehicle is moving horizontally.
*/
if ((aboveUpperSwHgt || dontTrustTerrain) && (activeHgtSource == HGT_SOURCE_RNG)) {
// cannot trust terrain or range finder so stop using range finder height
if (frontend->_altSource == 0) {
activeHgtSource = HGT_SOURCE_BARO;
} else if (frontend->_altSource == 2) {
activeHgtSource = HGT_SOURCE_GPS;
}
} else if (belowLowerSwHgt && trustTerrain && (activeHgtSource != HGT_SOURCE_RNG)) {
// reliable terrain and range finder so start using range finder height
activeHgtSource = HGT_SOURCE_RNG;
}
}
} else if ((frontend->_altSource == 2) && ((imuSampleTime_ms - lastTimeGpsReceived_ms) < 500) && validOrigin && gpsAccuracyGood) {
activeHgtSource = HGT_SOURCE_GPS;
} else if ((frontend->_altSource == 3) && validOrigin && rngBcnGoodToAlign) {
activeHgtSource = HGT_SOURCE_BCN;
} else {
activeHgtSource = HGT_SOURCE_BARO;
}
// Use Baro alt as a fallback if we lose range finder, GPS or external nav
bool lostRngHgt = ((activeHgtSource == HGT_SOURCE_RNG) && ((imuSampleTime_ms - rngValidMeaTime_ms) > 500));
bool lostGpsHgt = ((activeHgtSource == HGT_SOURCE_GPS) && ((imuSampleTime_ms - lastTimeGpsReceived_ms) > 2000));
bool lostExtNavHgt = ((activeHgtSource == HGT_SOURCE_EV) && ((imuSampleTime_ms - extNavMeasTime_ms) > 2000));
if (lostRngHgt || lostGpsHgt || lostExtNavHgt) {
activeHgtSource = HGT_SOURCE_BARO;
}
// if there is new baro data to fuse, calculate filtered baro data required by other processes
if (baroDataToFuse)
{
// calculate offset to baro data that enables us to switch to Baro height use during operation
if (activeHgtSource != HGT_SOURCE_BARO) {
calcFiltBaroOffset();
}
// filtered baro data used to provide a reference for takeoff
// it is is reset to last height measurement on disarming in performArmingChecks()
if (!getTakeoffExpected())
{
const float gndHgtFiltTC = 0.5f;
const float dtBaro = frontend->hgtAvg_ms*1.0e-3f;
float alpha = constrain_float(dtBaro / (dtBaro+gndHgtFiltTC),0.0f,1.0f);
meaHgtAtTakeOff += (baroDataDelayed.hgt-meaHgtAtTakeOff)*alpha;
}
}
// If we are not using GPS as the primary height sensor, correct EKF origin height so that
// combined local NED position height and origin height remains consistent with the GPS altitude
// This also enables the GPS height to be used as a backup height source
if (gpsDataToFuse &&
(((frontend->_originHgtMode & (1 << 0)) && (activeHgtSource == HGT_SOURCE_BARO)) ||
((frontend->_originHgtMode & (1 << 1)) && (activeHgtSource == HGT_SOURCE_RNG)))
) {
correctEkfOriginHeight();
}
// Select the height measurement source
if (extNavDataToFuse && (activeHgtSource == HGT_SOURCE_EV)) {
hgtMea = -extNavDataDelayed.pos.z;
posDownObsNoise = sq(constrain_float(extNavDataDelayed.posErr, 0.1f, 10.0f));
} else if (rangeDataToFuse && (activeHgtSource == HGT_SOURCE_RNG)) {
// using range finder data
// correct for tilt using a flat earth model
if (prevTnb.c.z >= 0.7) {
// calculate height above ground
hgtMea = MAX(rangeDataDelayed.rng * prevTnb.c.z, rngOnGnd);
// correct for terrain position relative to datum
hgtMea -= terrainState;
// enable fusion
fuseHgtData = true;
velPosObs[5] = -hgtMea;
// set the observation noise
posDownObsNoise = sq(constrain_float(frontend->_rngNoise, 0.1f, 10.0f));
// add uncertainty created by terrain gradient and vehicle tilt
posDownObsNoise += sq(rangeDataDelayed.rng * frontend->_terrGradMax) * MAX(0.0f , (1.0f - sq(prevTnb.c.z)));
} else {
// disable fusion if tilted too far
fuseHgtData = false;
}
} else if (gpsDataToFuse && (activeHgtSource == HGT_SOURCE_GPS)) {
// using GPS data
hgtMea = gpsDataDelayed.hgt;
// enable fusion
velPosObs[5] = -hgtMea;
fuseHgtData = true;
// set the observation noise using receiver reported accuracy or the horizontal noise scaled for typical VDOP/HDOP ratio
if (gpsHgtAccuracy > 0.0f) {
posDownObsNoise = sq(constrain_float(gpsHgtAccuracy, 1.5f * frontend->_gpsHorizPosNoise, 100.0f));
} else {
posDownObsNoise = sq(constrain_float(1.5f * frontend->_gpsHorizPosNoise, 0.1f, 10.0f));
}
} else if (baroDataToFuse && (activeHgtSource == HGT_SOURCE_BARO)) {
// using Baro data
hgtMea = baroDataDelayed.hgt - baroHgtOffset;
// enable fusion
velPosObs[5] = -hgtMea;
fuseHgtData = true;
// set the observation noise
posDownObsNoise = sq(constrain_float(frontend->_baroAltNoise, 0.1f, 10.0f));
// reduce weighting (increase observation noise) on baro if we are likely to be in ground effect
if (getTakeoffExpected() || getTouchdownExpected())
{
posDownObsNoise *= frontend->gndEffectBaroScaler;
}
// If we are in takeoff mode, the height measurement is limited to be no less than the measurement at start of takeoff
// This prevents negative baro disturbances due to copter downwash corrupting the EKF altitude during initial ascent
if (motorsArmed && getTakeoffExpected())
{
hgtMea = MAX(hgtMea, meaHgtAtTakeOff);
}
} else
{
fuseHgtData = false;
}
// If we haven't fused height data for a while, then declare the height data as being timed out
// set timeout period based on whether we have vertical GPS velocity available to constrain drift
hgtRetryTime_ms = (useGpsVertVel && !velTimeout) ? frontend->hgtRetryTimeMode0_ms : frontend->hgtRetryTimeMode12_ms;
if (imuSampleTime_ms - lastHgtPassTime_ms > hgtRetryTime_ms) {
hgtTimeout = true;
} else {
hgtTimeout = false;
}
}
else if (obsIndex == 5)
{
innovVelPos[obsIndex] = stateStruct.position[obsIndex-3] - velPosObs[obsIndex];
const float gndMaxBaroErr = 4.0f;
const float gndBaroInnovFloor = -0.5f;
if(getTouchdownExpected() && activeHgtSource == HGT_SOURCE_BARO)
{
// when a touchdown is expected, floor the barometer innovation at gndBaroInnovFloor
// constrain the correction between 0 and gndBaroInnovFloor+gndMaxBaroErr
// this function looks like this:
// |/
//---------|---------
// ____/|
// / |
// / |
innovVelPos[5] += constrain_float(-innovVelPos[5]+gndBaroInnovFloor, 0.0f, gndBaroInnovFloor+gndMaxBaroErr);
}
}
if (getTakeoffExpected() || getTouchdownExpected())
{
posDownObsNoise *= frontend->gndEffectBaroScaler;
}
总结:地效补偿影响,主要更合理的处理无人机在起飞或者着陆过程中气流对气压计的影响。