Line data Source code
1 : #include "AP_TECS.h"
2 :
3 : #include <AP_HAL/AP_HAL.h>
4 : #include <AP_Baro/AP_Baro.h>
5 : #include <AP_Logger/AP_Logger.h>
6 : #include <AP_Landing/AP_Landing.h>
7 :
8 : extern const AP_HAL::HAL& hal;
9 :
10 : #if CONFIG_HAL_BOARD == HAL_BOARD_SITL
11 : #include <stdio.h>
12 : # define Debug(fmt, args ...) do {printf("%s:%d: " fmt "\n", __FUNCTION__, __LINE__, ## args); hal.scheduler->delay(1); } while(0)
13 : #else
14 : # define Debug(fmt, args ...)
15 : #endif
16 : //Debug("%.2f %.2f %.2f %.2f \n", var1, var2, var3, var4);
17 :
18 : // table of user settable parameters
19 : const AP_Param::GroupInfo AP_TECS::var_info[] = {
20 :
21 : // @Param: CLMB_MAX
22 : // @DisplayName: Maximum Climb Rate (metres/sec)
23 : // @Description: Maximum demanded climb rate. Do not set higher than the climb speed at THR_MAX at AIRSPEED_CRUISE when the battery is at low voltage. Reduce value if airspeed cannot be maintained on ascent. Increase value if throttle does not increase significantly to ascend.
24 : // @Increment: 0.1
25 : // @Range: 0.1 20.0
26 : // @User: Standard
27 : AP_GROUPINFO("CLMB_MAX", 0, AP_TECS, _maxClimbRate, 5.0f),
28 :
29 : // @Param: SINK_MIN
30 : // @DisplayName: Minimum Sink Rate (metres/sec)
31 : // @Description: Minimum sink rate when at THR_MIN and AIRSPEED_CRUISE.
32 : // @Increment: 0.1
33 : // @Range: 0.1 10.0
34 : // @User: Standard
35 : AP_GROUPINFO("SINK_MIN", 1, AP_TECS, _minSinkRate, 2.0f),
36 :
37 : // @Param: TIME_CONST
38 : // @DisplayName: Controller time constant (sec)
39 : // @Description: Time constant of the TECS control algorithm. Small values make faster altitude corrections but can cause overshoot and aggressive behavior.
40 : // @Range: 3.0 10.0
41 : // @Increment: 0.2
42 : // @User: Advanced
43 : AP_GROUPINFO("TIME_CONST", 2, AP_TECS, _timeConst, 5.0f),
44 :
45 : // @Param: THR_DAMP
46 : // @DisplayName: Controller throttle damping
47 : // @Description: Damping gain for throttle demand loop. Increase to add throttle activity to dampen oscillations in speed and height.
48 : // @Range: 0.1 1.0
49 : // @Increment: 0.1
50 : // @User: Advanced
51 : AP_GROUPINFO("THR_DAMP", 3, AP_TECS, _thrDamp, 0.5f),
52 :
53 : // @Param: INTEG_GAIN
54 : // @DisplayName: Controller integrator
55 : // @Description: Integrator gain to trim out long-term speed and height errors.
56 : // @Range: 0.0 0.5
57 : // @Increment: 0.02
58 : // @User: Advanced
59 : AP_GROUPINFO("INTEG_GAIN", 4, AP_TECS, _integGain, 0.3f),
60 :
61 : // @Param: VERT_ACC
62 : // @DisplayName: Vertical Acceleration Limit (metres/sec^2)
63 : // @Description: Maximum vertical acceleration used to correct speed or height errors.
64 : // @Range: 1.0 10.0
65 : // @Increment: 0.5
66 : // @User: Advanced
67 : AP_GROUPINFO("VERT_ACC", 5, AP_TECS, _vertAccLim, 7.0f),
68 :
69 : // @Param: HGT_OMEGA
70 : // @DisplayName: Height complementary filter frequency (radians/sec)
71 : // @Description: This is the cross-over frequency of the complementary filter used to fuse vertical acceleration and baro alt to obtain an estimate of height rate and height.
72 : // @Range: 1.0 5.0
73 : // @Increment: 0.05
74 : // @User: Advanced
75 : AP_GROUPINFO("HGT_OMEGA", 6, AP_TECS, _hgtCompFiltOmega, 3.0f),
76 :
77 : // @Param: SPD_OMEGA
78 : // @DisplayName: Speed complementary filter frequency (radians/sec)
79 : // @Description: This is the cross-over frequency of the complementary filter used to fuse longitudinal acceleration and airspeed to obtain a lower noise and lag estimate of airspeed.
80 : // @Range: 0.5 2.0
81 : // @Increment: 0.05
82 : // @User: Advanced
83 : AP_GROUPINFO("SPD_OMEGA", 7, AP_TECS, _spdCompFiltOmega, 2.0f),
84 :
85 : // @Param: RLL2THR
86 : // @DisplayName: Bank angle compensation gain
87 : // @Description: Gain from bank angle to throttle to compensate for loss of airspeed from drag in turns. Set to approximately 10x the sink rate in m/s caused by a 45-degree turn. High efficiency models may need less while less efficient aircraft may need more. Should be tuned in an automatic mission with waypoints and turns greater than 90 degrees. Tune with PTCH2SRV_RLL and KFF_RDDRMIX to achieve constant airspeed, constant altitude turns.
88 : // @Range: 5.0 30.0
89 : // @Increment: 1.0
90 : // @User: Advanced
91 : AP_GROUPINFO("RLL2THR", 8, AP_TECS, _rollComp, 10.0f),
92 :
93 : // @Param: SPDWEIGHT
94 : // @DisplayName: Weighting applied to speed control
95 : // @Description: Mixing of pitch and throttle correction for height and airspeed errors. Pitch controls altitude and throttle controls airspeed if set to 0. Pitch controls airspeed and throttle controls altitude if set to 2 (good for gliders). Blended if set to 1.
96 : // @Range: 0.0 2.0
97 : // @Increment: 0.1
98 : // @User: Advanced
99 : AP_GROUPINFO("SPDWEIGHT", 9, AP_TECS, _spdWeight, 1.0f),
100 :
101 : // @Param: PTCH_DAMP
102 : // @DisplayName: Controller pitch damping
103 : // @Description: Damping gain for pitch control from TECS control. Increasing may correct for oscillations in speed and height, but too much may cause additional oscillation and degraded control.
104 : // @Range: 0.1 1.0
105 : // @Increment: 0.1
106 : // @User: Advanced
107 : AP_GROUPINFO("PTCH_DAMP", 10, AP_TECS, _ptchDamp, 0.3f),
108 :
109 : // @Param: SINK_MAX
110 : // @DisplayName: Maximum Descent Rate (metres/sec)
111 : // @Description: Maximum demanded descent rate. Do not set higher than the vertical speed the aircraft can maintain at THR_MIN, TECS_PITCH_MIN, and AIRSPEED_MAX.
112 : // @Increment: 0.1
113 : // @Range: 0.0 20.0
114 : // @User: Standard
115 : AP_GROUPINFO("SINK_MAX", 11, AP_TECS, _maxSinkRate, 5.0f),
116 :
117 : // @Param: LAND_ARSPD
118 : // @DisplayName: Airspeed during landing approach (m/s)
119 : // @Description: When performing an autonomous landing, this value is used as the goal airspeed during approach. Max airspeed allowed is Trim Airspeed or AIRSPEED_MAX as defined by LAND_OPTIONS bitmask. Note that this parameter is not useful if your platform does not have an airspeed sensor (use TECS_LAND_THR instead). If negative then this value is halfway between AIRSPEED_MIN and AIRSPEED_CRUISE speed for fixed wing autolandings.
120 : // @Range: -1 127
121 : // @Increment: 1
122 : // @User: Standard
123 : AP_GROUPINFO("LAND_ARSPD", 12, AP_TECS, _landAirspeed, -1),
124 :
125 : // @Param: LAND_THR
126 : // @DisplayName: Cruise throttle during landing approach (percentage)
127 : // @Description: Use this parameter instead of LAND_ARSPD if your platform does not have an airspeed sensor. It is the cruise throttle during landing approach. If this value is negative then it is disabled and TECS_LAND_ARSPD is used instead.
128 : // @Range: -1 100
129 : // @Increment: 0.1
130 : // @User: Standard
131 : AP_GROUPINFO("LAND_THR", 13, AP_TECS, _landThrottle, -1),
132 :
133 : // @Param: LAND_SPDWGT
134 : // @DisplayName: Weighting applied to speed control during landing.
135 : // @Description: Same as SPDWEIGHT parameter, with the exception that this parameter is applied during landing flight stages. A value closer to 2 will result in the plane ignoring height error during landing and our experience has been that the plane will therefore keep the nose up -- sometimes good for a glider landing (with the side effect that you will likely glide a ways past the landing point). A value closer to 0 results in the plane ignoring speed error -- use caution when lowering the value below 1 -- ignoring speed could result in a stall. Values between 0 and 2 are valid values for a fixed landing weight. When using -1 the weight will be scaled during the landing. At the start of the landing approach it starts with TECS_SPDWEIGHT and scales down to 0 by the time you reach the land point. Example: Halfway down the landing approach you'll effectively have a weight of TECS_SPDWEIGHT/2.
136 : // @Range: -1.0 2.0
137 : // @Increment: 0.1
138 : // @User: Advanced
139 : AP_GROUPINFO("LAND_SPDWGT", 14, AP_TECS, _spdWeightLand, -1.0f),
140 :
141 : // @Param: PITCH_MAX
142 : // @DisplayName: Maximum pitch in auto flight
143 : // @Description: Overrides PTCH_LIM_MAX_DEG in automatic throttle modes to reduce climb rates. Uses PTCH_LIM_MAX_DEG if set to 0. For proper TECS tuning, set to the angle that the aircraft can climb at AIRSPEED_CRUISE and THR_MAX.
144 : // @Range: 0 45
145 : // @Increment: 1
146 : // @User: Advanced
147 : AP_GROUPINFO("PITCH_MAX", 15, AP_TECS, _pitch_max, 15),
148 :
149 : // @Param: PITCH_MIN
150 : // @DisplayName: Minimum pitch in auto flight
151 : // @Description: Overrides PTCH_LIM_MIN_DEG in automatic throttle modes to reduce descent rates. Uses PTCH_LIM_MIN_DEG if set to 0. For proper TECS tuning, set to the angle that the aircraft can descend at without overspeeding.
152 : // @Range: -45 0
153 : // @Increment: 1
154 : // @User: Advanced
155 : AP_GROUPINFO("PITCH_MIN", 16, AP_TECS, _pitch_min, 0),
156 :
157 : // @Param: LAND_SINK
158 : // @DisplayName: Sink rate for final landing stage
159 : // @Description: The sink rate in meters/second for the final stage of landing.
160 : // @Range: 0.0 2.0
161 : // @Increment: 0.1
162 : // @User: Advanced
163 : AP_GROUPINFO("LAND_SINK", 17, AP_TECS, _land_sink, 0.25f),
164 :
165 : // @Param: LAND_TCONST
166 : // @DisplayName: Land controller time constant (sec)
167 : // @Description: This is the time constant of the TECS control algorithm when in final landing stage of flight. It should be smaller than TECS_TIME_CONST to allow for faster flare
168 : // @Range: 1.0 5.0
169 : // @Increment: 0.2
170 : // @User: Advanced
171 : AP_GROUPINFO("LAND_TCONST", 18, AP_TECS, _landTimeConst, 2.0f),
172 :
173 : // @Param: LAND_DAMP
174 : // @DisplayName: Controller sink rate to pitch gain during flare
175 : // @Description: This is the sink rate gain for the pitch demand loop when in final landing stage of flight. It should be larger than TECS_PTCH_DAMP to allow for better sink rate control during flare.
176 : // @Range: 0.1 1.0
177 : // @Increment: 0.1
178 : // @User: Advanced
179 : AP_GROUPINFO("LAND_DAMP", 19, AP_TECS, _landDamp, 0.5f),
180 :
181 : // @Param: LAND_PMAX
182 : // @DisplayName: Maximum pitch during final stage of landing
183 : // @Description: This limits the pitch used during the final stage of automatic landing. During the final landing stage most planes need to keep their pitch small to avoid stalling. A maximum of 10 degrees is usually good. A value of zero means to use the normal pitch limits.
184 : // @Range: -5 40
185 : // @Increment: 1
186 : // @User: Advanced
187 : AP_GROUPINFO("LAND_PMAX", 20, AP_TECS, _land_pitch_max, 10),
188 :
189 : // @Param: APPR_SMAX
190 : // @DisplayName: Sink rate max for landing approach stage
191 : // @Description: The sink rate max for the landing approach stage of landing. This will need to be large for steep landing approaches especially when using reverse thrust. If 0, then use TECS_SINK_MAX.
192 : // @Range: 0.0 20.0
193 : // @Units: m/s
194 : // @Increment: 0.1
195 : // @User: Advanced
196 : AP_GROUPINFO("APPR_SMAX", 21, AP_TECS, _maxSinkRate_approach, 0),
197 :
198 : // @Param: LAND_SRC
199 : // @DisplayName: Land sink rate change
200 : // @Description: When zero, the flare sink rate (TECS_LAND_SINK) is a fixed sink demand. With this enabled the flare sink rate will increase/decrease the flare sink demand as you get further beyond the LAND waypoint. Has no effect before the waypoint. This value is added to TECS_LAND_SINK proportional to distance traveled after wp. With an increasing sink rate you can still land in a given distance if you're traveling too fast and cruise passed the land point. A positive value will force the plane to land sooner proportional to distance passed land point. A negative number will tell the plane to slowly climb allowing for a pitched-up stall landing. Recommend 0.2 as initial value.
201 : // @Range: -2.0 2.0
202 : // @Units: m/s/m
203 : // @Increment: 0.1
204 : // @User: Advanced
205 : AP_GROUPINFO("LAND_SRC", 22, AP_TECS, _land_sink_rate_change, 0),
206 :
207 : // @Param: LAND_TDAMP
208 : // @DisplayName: Controller throttle damping when landing
209 : // @Description: Damping gain for the throttle demand loop during an auto-landing. Same as TECS_THR_DAMP but only in effect during an auto-land. Increase to add throttle activity to dampen oscillations in speed and height. When set to 0 landing throttle damping is controlled by TECS_THR_DAMP.
210 : // @Range: 0.1 1.0
211 : // @Increment: 0.1
212 : // @User: Advanced
213 : AP_GROUPINFO("LAND_TDAMP", 23, AP_TECS, _land_throttle_damp, 0),
214 :
215 : // @Param: LAND_IGAIN
216 : // @DisplayName: Controller integrator during landing
217 : // @Description: This is the integrator gain on the control loop during landing. When set to 0 then TECS_INTEG_GAIN is used. Increase to increase the rate at which speed and height offsets are trimmed out. Typically values lower than TECS_INTEG_GAIN work best
218 : // @Range: 0.0 0.5
219 : // @Increment: 0.02
220 : // @User: Advanced
221 : AP_GROUPINFO("LAND_IGAIN", 24, AP_TECS, _integGain_land, 0),
222 :
223 : // @Param: TKOFF_IGAIN
224 : // @DisplayName: Controller integrator during takeoff
225 : // @Description: This is the integrator gain on the control loop during takeoff. Increase to increase the rate at which speed and height offsets are trimmed out.
226 : // @Range: 0.0 0.5
227 : // @Increment: 0.02
228 : // @User: Advanced
229 : AP_GROUPINFO("TKOFF_IGAIN", 25, AP_TECS, _integGain_takeoff, 0),
230 :
231 : // @Param: LAND_PDAMP
232 : // @DisplayName: Pitch damping gain when landing
233 : // @Description: This is the damping gain for the pitch demand loop during landing. Increase to add damping to correct for oscillations in speed and height. If set to 0 then TECS_PTCH_DAMP will be used instead.
234 : // @Range: 0.1 1.0
235 : // @Increment: 0.1
236 : // @User: Advanced
237 : AP_GROUPINFO("LAND_PDAMP", 26, AP_TECS, _land_pitch_damp, 0),
238 :
239 : // @Param: SYNAIRSPEED
240 : // @DisplayName: Enable the use of synthetic airspeed
241 : // @Description: This enables the use of synthetic airspeed in TECS for aircraft that don't have a real airspeed sensor. This is useful for development testing where the user is aware of the considerable limitations of the synthetic airspeed system, such as very poor estimates when a wind estimate is not accurate. Do not enable this option unless you fully understand the limitations of a synthetic airspeed estimate. This option has no effect if a healthy airspeed sensor is being used for airspeed measurements.
242 : // @Values: 0:Disable,1:Enable
243 : // @User: Advanced
244 : AP_GROUPINFO("SYNAIRSPEED", 27, AP_TECS, _use_synthetic_airspeed, 0),
245 :
246 : // @Param: OPTIONS
247 : // @DisplayName: Extra TECS options
248 : // @Description: This allows the enabling of special features in the speed/height controller.
249 : // @Bitmask: 0:GliderOnly,1:AllowDescentSpeedup
250 : // @User: Advanced
251 : AP_GROUPINFO("OPTIONS", 28, AP_TECS, _options, 0),
252 :
253 : // @Param: PTCH_FF_V0
254 : // @DisplayName: Baseline airspeed for pitch feed-forward.
255 : // @Description: This parameter sets the airspeed at which no feed-forward is applied between demanded airspeed and pitch. It should correspond to the airspeed in metres per second at which the plane glides at neutral pitch including STAB_PITCH_DOWN.
256 : // @Range: 5.0 50.0
257 : // @User: Advanced
258 : AP_GROUPINFO("PTCH_FF_V0", 29, AP_TECS, _pitch_ff_v0, 12.0),
259 :
260 : // @Param: PTCH_FF_K
261 : // @DisplayName: Gain for pitch feed-forward.
262 : // @Description: This parameter sets the gain between demanded airspeed and pitch. It has units of radians per metre per second and should generally be negative. A good starting value is -0.04 for gliders and -0.08 for draggy airframes. The default (0.0) disables this feed-forward.
263 : // @Range: -5.0 0.0
264 : // @User: Advanced
265 : AP_GROUPINFO("PTCH_FF_K", 30, AP_TECS, _pitch_ff_k, 0.0),
266 :
267 : // 31 previously used by AP_Int8 TECS_LAND_PTRIM which was removed in November 2022
268 :
269 : // @Param: THR_ERATE
270 : // @DisplayName: Forward throttle external limit slew rate
271 : // @Description: The externally set forward throttle lower limit applied within TECS will be reduced by this many percentage points per second after being set. Set to a non positive value to hold the lower limit for one frame only.
272 : // @Units: %/s
273 : // @Range: 0 100
274 : // @User: Advanced
275 : AP_GROUPINFO("THR_ERATE", 31, AP_TECS, _thr_min_pct_ext_rate_lim, 20),
276 :
277 : // @Param: FLARE_HGT
278 : // @DisplayName: Flare holdoff height
279 : // @Description: When height above ground is below this, the sink rate will be held at TECS_LAND_SINK. Use this to perform a hold-off manoeuvre when combined with small values for TECS_LAND_SINK.
280 : // @Range: 0 15
281 : // @Units: m
282 : // @User: Advanced
283 : AP_GROUPINFO("FLARE_HGT", 32, AP_TECS, _flare_holdoff_hgt, 1.0f),
284 :
285 : // @Param: HDEM_TCONST
286 : // @DisplayName: Height Demand Time Constant
287 : // @Description: This sets the time constant of the low pass filter that is applied to the height demand input when bit 0 of TECS_OPTIONS is not selected.
288 : // @Range: 1.0 5.0
289 : // @Units: s
290 : // @Increment: 0.2
291 : // @User: Advanced
292 : AP_GROUPINFO("HDEM_TCONST", 33, AP_TECS, _hgt_dem_tconst, 3.0f),
293 :
294 : AP_GROUPEND
295 : };
296 :
297 : /*
298 : * Written by Paul Riseborough 2013 to provide:
299 : * - Combined control of speed and height using throttle to control
300 : * total energy and pitch angle to control exchange of energy between
301 : * potential and kinetic.
302 : * Selectable speed or height priority modes when calculating pitch angle
303 : * - Fallback mode when no airspeed measurement is available that
304 : * sets throttle based on height rate demand and switches pitch angle control to
305 : * height priority
306 : * - Underspeed protection that demands maximum throttle and switches pitch angle control
307 : * to speed priority mode
308 : * - Relative ease of tuning through use of intuitive time constant, integrator and damping gains and the use
309 : * of easy to measure aircraft performance data
310 : *
311 : */
312 :
313 1657292 : void AP_TECS::update_50hz(void)
314 : {
315 : // Implement third order complementary filter for height and height rate
316 : // estimated height rate = _climb_rate
317 : // estimated height above field elevation = _height
318 : // Reference Paper :
319 : // Optimizing the Gains of the Baro-Inertial Vertical Channel
320 : // Widnall W.S, Sinha P.K,
321 : // AIAA Journal of Guidance and Control, 78-1307R
322 :
323 : /*
324 : if we have a vertical position estimate from the EKF then use
325 : it, otherwise use barometric altitude
326 : */
327 1657292 : _ahrs.get_relative_position_D_home(_height);
328 1657292 : _height *= -1.0f;
329 :
330 : // Calculate time in seconds since last update
331 1657292 : uint64_t now = AP_HAL::micros64();
332 1657292 : float DT = (now - _update_50hz_last_usec) * 1.0e-6f;
333 1657292 : _flags.reset = DT > 1.0f;
334 1657292 : if (_flags.reset) {
335 368 : _climb_rate = 0.0f;
336 368 : _height_filter.dd_height = 0.0f;
337 368 : DT = 0.02f; // when first starting TECS, use most likely time constant
338 368 : _vdot_filter.reset();
339 : }
340 1657292 : _update_50hz_last_usec = now;
341 :
342 : // Use inertial nav vertical velocity and height if available
343 1657292 : Vector3f velned;
344 1657292 : if (_ahrs.get_velocity_NED(velned)) {
345 : // if possible use the EKF vertical velocity
346 1648957 : _climb_rate = -velned.z;
347 : } else {
348 : /*
349 : use a complimentary filter to calculate climb_rate. This is
350 : designed to minimise lag
351 : */
352 8335 : const float baro_alt = AP::baro().get_altitude();
353 : // Get height acceleration
354 8335 : float hgt_ddot_mea = -(_ahrs.get_accel_ef().z + GRAVITY_MSS);
355 : // Perform filter calculation using backwards Euler integration
356 : // Coefficients selected to place all three filter poles at omega
357 8335 : float omega2 = _hgtCompFiltOmega*_hgtCompFiltOmega;
358 8335 : float hgt_err = baro_alt - _height_filter.height;
359 8335 : float integ1_input = hgt_err * omega2 * _hgtCompFiltOmega;
360 :
361 8335 : _height_filter.dd_height += integ1_input * DT;
362 :
363 8335 : float integ2_input = _height_filter.dd_height + hgt_ddot_mea + hgt_err * omega2 * 3.0f;
364 :
365 8335 : _climb_rate += integ2_input * DT;
366 :
367 8335 : float integ3_input = _climb_rate + hgt_err * _hgtCompFiltOmega * 3.0f;
368 : // If more than 1 second has elapsed since last update then reset the integrator state
369 : // to the measured height
370 8335 : if (_flags.reset) {
371 38 : _height_filter.height = _height;
372 : } else {
373 8297 : _height_filter.height += integ3_input*DT;
374 : }
375 : }
376 :
377 : // Update the speed estimate using a 2nd order complementary filter
378 1657292 : _update_speed(DT);
379 1657292 : }
380 :
381 1657292 : void AP_TECS::_update_speed(float DT)
382 : {
383 : // Update and average speed rate of change
384 :
385 : // calculate a low pass filtered _vel_dot
386 1657292 : if (_flags.reset) {
387 368 : _vdot_filter.reset();
388 368 : _vel_dot_lpf = _vel_dot;
389 : } else {
390 : // Get DCM
391 1656924 : const Matrix3f &rotMat = _ahrs.get_rotation_body_to_ned();
392 : // Calculate speed rate of change
393 1656924 : float temp = rotMat.c.x * GRAVITY_MSS + AP::ins().get_accel().x;
394 : // take 5 point moving average
395 1656924 : _vel_dot = _vdot_filter.apply(temp);
396 1656924 : const float alpha = DT / (DT + timeConstant());
397 1656924 : _vel_dot_lpf = _vel_dot_lpf * (1.0f - alpha) + _vel_dot * alpha;
398 : }
399 :
400 1657292 : const bool should_use_airspeed = use_airspeed();
401 :
402 : // Convert equivalent airspeeds to true airspeeds and harmonise limits
403 :
404 1657292 : float EAS2TAS = _ahrs.get_EAS2TAS();
405 1657292 : _TAS_dem = _EAS_dem * EAS2TAS;
406 1657292 : if (_flags.reset || !should_use_airspeed) {
407 77821 : _TASmax = aparm.airspeed_max * EAS2TAS;
408 1579471 : } else if (_thr_clip_status == clipStatus::MAX) {
409 : // wind down airspeed upper limit to prevent a situation where the aircraft can't climb
410 : // at the maximum speed
411 177500 : const float velRateMin = 0.5f * _STEdot_min / MAX(_TAS_state, aparm.airspeed_min * EAS2TAS);
412 177500 : _TASmax += _DT * velRateMin;
413 177500 : _TASmax = MAX(_TASmax, aparm.airspeed_cruise * EAS2TAS);
414 : } else {
415 : // wind airspeed upper limit back to parameter defined value
416 1401971 : const float velRateMax = 0.5f * _STEdot_max / MAX(_TAS_state, aparm.airspeed_min * EAS2TAS);
417 1401971 : _TASmax += _DT * velRateMax;
418 : }
419 1657292 : _TASmax = MIN(_TASmax, aparm.airspeed_max * EAS2TAS);
420 1657292 : _TASmin = aparm.airspeed_min * EAS2TAS;
421 :
422 1657292 : if (_landing.is_on_final() && is_positive(aparm.airspeed_stall)) {
423 0 : _TASmin = aparm.airspeed_stall * EAS2TAS;
424 : }
425 :
426 1657292 : if (aparm.stall_prevention) {
427 : // when stall prevention is active we raise the minimum
428 : // airspeed based on aerodynamic load factor
429 1629071 : if (is_positive(aparm.airspeed_stall)) {
430 0 : _TASmin = MAX(_TASmin, aparm.airspeed_stall*EAS2TAS*sqrtf(_load_factor));
431 : } else {
432 1629071 : _TASmin *= sqrtf(_load_factor);
433 : }
434 : }
435 :
436 1657292 : if (_TASmax < _TASmin) {
437 339 : _TASmax = _TASmin;
438 : }
439 :
440 : // Get measured airspeed or default to trim speed and constrain to range between min and max if
441 : // airspeed sensor data cannot be used
442 :
443 : // Equivalent airspeed
444 : float _EAS;
445 1657292 : if (!should_use_airspeed || !_ahrs.airspeed_EAS(_EAS)) {
446 : // If no airspeed available, assume we are at cruise speed.
447 77471 : _EAS = constrain_float(aparm.airspeed_cruise.get(), (float)aparm.airspeed_min.get(), (float)aparm.airspeed_max.get());
448 : }
449 :
450 : // limit the airspeed to a minimum of 3 m/s
451 1657292 : const float min_airspeed = 3.0;
452 :
453 : // Reset states of time since last update is too large
454 1657292 : if (_flags.reset) {
455 368 : _TAS_state = (_EAS * EAS2TAS);
456 368 : _TAS_state = MAX(_TAS_state, min_airspeed);
457 368 : _integDTAS_state = 0.0f;
458 368 : return;
459 : }
460 :
461 : // Implement a second order complementary filter to obtain a
462 : // smoothed airspeed estimate
463 : // airspeed estimate is held in _TAS_state
464 1656924 : float aspdErr = (_EAS * EAS2TAS) - _TAS_state;
465 1656924 : float integDTAS_input = aspdErr * _spdCompFiltOmega * _spdCompFiltOmega;
466 : // Prevent state from winding up
467 1656924 : if (_TAS_state < 3.1f) {
468 163166 : integDTAS_input = MAX(integDTAS_input, 0.0f);
469 : }
470 1656924 : _integDTAS_state = _integDTAS_state + integDTAS_input * DT;
471 1656924 : float TAS_input = _integDTAS_state + _vel_dot + aspdErr * _spdCompFiltOmega * 1.4142f;
472 1656924 : _TAS_state = _TAS_state + TAS_input * DT;
473 1656924 : _TAS_state = MAX(_TAS_state, min_airspeed);
474 :
475 : }
476 :
477 313892 : void AP_TECS::_update_speed_demand(void)
478 : {
479 313892 : if (option_is_set(Option::DESCENT_SPEEDUP)) {
480 : // Allow demanded speed to go to maximum when descending at maximum descent rate
481 4799 : _TAS_dem = _TAS_dem + (_TASmax - _TAS_dem) * _sink_fraction;
482 : }
483 :
484 : // Set the airspeed demand to the minimum value if an underspeed condition exists
485 : // or a bad descent condition exists
486 : // This will minimise the rate of descent resulting from an engine failure,
487 : // enable the maximum climb rate to be achieved and prevent continued full power descent
488 : // into the ground due to an unachievable airspeed value
489 313892 : if ((_flags.badDescent) || (_flags.underspeed)) {
490 6291 : _TAS_dem = _TASmin;
491 : }
492 :
493 : // Constrain speed demand, taking into account the load factor
494 313892 : _TAS_dem = constrain_float(_TAS_dem, _TASmin, _TASmax);
495 :
496 : // Determine the true cruising airspeed (m/s)
497 313892 : const float TAScruise = aparm.airspeed_cruise * _ahrs.get_EAS2TAS();
498 :
499 : // calculate velocity rate limits based on physical performance limits
500 : // provision to use a different rate limit if bad descent or underspeed condition exists
501 : // Use 50% of maximum energy rate on gain, 90% on dissipation to allow margin for total energy controller
502 313892 : const float velRateMax = 0.5f * _STEdot_max / _TAS_state;
503 : // Maximum permissible rate of deceleration value at max airspeed
504 313892 : const float velRateNegMax = 0.9f * _STEdot_neg_max / _TASmax;
505 : // Maximum permissible rate of deceleration value at cruise speed
506 313892 : const float velRateNegCruise = 0.9f * _STEdot_min / TAScruise;
507 : // Linear interpolation between velocity rate at cruise and max speeds, capped at those speeds
508 313892 : const float velRateMin = linear_interpolate(velRateNegMax, velRateNegCruise, _TAS_state, _TASmax, TAScruise);
509 313892 : const float TAS_dem_previous = _TAS_dem_adj;
510 :
511 : // Apply rate limit
512 313892 : if ((_TAS_dem - TAS_dem_previous) > (velRateMax * _DT)) {
513 11020 : _TAS_dem_adj = TAS_dem_previous + velRateMax * _DT;
514 11020 : _TAS_rate_dem = velRateMax;
515 302872 : } else if ((_TAS_dem - TAS_dem_previous) < (velRateMin * _DT)) {
516 20530 : _TAS_dem_adj = TAS_dem_previous + velRateMin * _DT;
517 20530 : _TAS_rate_dem = velRateMin;
518 : } else {
519 282342 : _TAS_rate_dem = (_TAS_dem - TAS_dem_previous) / _DT;
520 282342 : _TAS_dem_adj = _TAS_dem;
521 : }
522 :
523 : // calculate a low pass filtered _TAS_rate_dem
524 313892 : if (_flags.reset) {
525 516 : _TAS_dem_adj = _TAS_state;
526 516 : _TAS_rate_dem_lpf = _TAS_rate_dem;
527 : } else {
528 313376 : const float alpha = _DT / (_DT + timeConstant());
529 313376 : _TAS_rate_dem_lpf = _TAS_rate_dem_lpf * (1.0f - alpha) + _TAS_rate_dem * alpha;
530 : }
531 :
532 : // Constrain speed demand again to protect against bad values on initialisation.
533 313892 : _TAS_dem_adj = constrain_float(_TAS_dem_adj, _TASmin, _TASmax);
534 313892 : }
535 :
536 313892 : void AP_TECS::_update_height_demand(void)
537 : {
538 313892 : _climb_rate_limit = _maxClimbRate * _max_climb_scaler;
539 313892 : _sink_rate_limit = _maxSinkRate * _max_sink_scaler;
540 313892 : if (_maxSinkRate_approach > 0 && _flags.is_doing_auto_land) {
541 : // special sink rate for approach to accommodate steep slopes and reverse thrust.
542 : // A special check must be done to see if we're LANDing on approach but also if
543 : // we're in that tiny window just starting NAV_LAND but still in NORMAL mode. If
544 : // we have a steep slope with a short approach we'll want to allow acquiring the
545 : // glide slope right away.
546 0 : _sink_rate_limit = _maxSinkRate_approach;
547 : }
548 :
549 :
550 313892 : if (!_landing.is_flaring()) {
551 : // Apply 2 point moving average to demanded height
552 290636 : const float hgt_dem = 0.5f * (_hgt_dem_in + _hgt_dem_in_prev);
553 290636 : _hgt_dem_in_prev = _hgt_dem_in;
554 :
555 : // Limit height rate of change
556 290636 : if ((hgt_dem - _hgt_dem_rate_ltd) > (_climb_rate_limit * _DT)) {
557 35958 : _hgt_dem_rate_ltd = _hgt_dem_rate_ltd + _climb_rate_limit * _DT;
558 35958 : _sink_fraction = 0.0f;
559 254678 : } else if ((hgt_dem - _hgt_dem_rate_ltd) < (-_sink_rate_limit * _DT)) {
560 24717 : _hgt_dem_rate_ltd = _hgt_dem_rate_ltd - _sink_rate_limit * _DT;
561 24717 : _sink_fraction = 1.0f;
562 : } else {
563 229961 : const float numerator = hgt_dem - _hgt_dem_rate_ltd;
564 229961 : const float denominator = - _sink_rate_limit * _DT;
565 229961 : if (is_negative(numerator) && is_negative(denominator)) {
566 67109 : _sink_fraction = numerator / denominator;
567 : } else {
568 162852 : _sink_fraction = 0.0f;
569 : }
570 229961 : _hgt_dem_rate_ltd = hgt_dem;
571 : }
572 :
573 : // Apply a first order lag to height demand and compensate for lag when commencing height
574 : // control after takeoff to prevent plane pushing nose to level before climbing again. Post takeoff
575 : // compensation offset is decayed using the same time constant as the height demand filter.
576 290636 : const float coef = MIN(_DT / (_DT + MAX(_hgt_dem_tconst, _DT)), 1.0f);
577 290636 : _hgt_rate_dem = (_hgt_dem_rate_ltd - _hgt_dem_lpf) / _hgt_dem_tconst;
578 290636 : _hgt_dem_lpf = _hgt_dem_rate_ltd * coef + (1.0f - coef) * _hgt_dem_lpf;
579 290636 : _post_TO_hgt_offset *= (1.0f - coef);
580 290636 : _hgt_dem = _hgt_dem_lpf + _post_TO_hgt_offset;
581 :
582 : // during approach compensate for height filter lag
583 290636 : if (_flags.is_doing_auto_land) {
584 12850 : _hgt_dem += _hgt_dem_tconst * _hgt_rate_dem;
585 : } else {
586 : // Don't allow height demand to get too far ahead of the vehicles current height
587 : // if vehicle is unable to follow the demanded climb or descent
588 545060 : bool max_climb_condition = (_pitch_dem_unc > _PITCHmaxf) ||
589 267274 : (_SEBdot_dem_clip == clipStatus::MAX);
590 545899 : bool max_descent_condition = (_pitch_dem_unc < _PITCHminf) ||
591 268113 : (_SEBdot_dem_clip == clipStatus::MIN);
592 277786 : if (_using_airspeed_for_throttle) {
593 : // large height errors will result in the throttle saturating
594 276507 : max_climb_condition |= (_thr_clip_status == clipStatus::MAX) &&
595 11736 : !((_flight_stage == AP_FixedWing::FlightStage::TAKEOFF) || (_flight_stage == AP_FixedWing::FlightStage::ABORT_LANDING));
596 264771 : max_descent_condition |= (_thr_clip_status == clipStatus::MIN) && !_landing.is_flaring();
597 : }
598 277786 : const float hgt_dem_alpha = _DT / MAX(_DT + _hgt_dem_tconst, _DT);
599 277786 : if (max_climb_condition && _hgt_dem > _hgt_dem_prev) {
600 18641 : _max_climb_scaler *= (1.0f - hgt_dem_alpha);
601 259145 : } else if (max_descent_condition && _hgt_dem < _hgt_dem_prev) {
602 6123 : _max_sink_scaler *= (1.0f - hgt_dem_alpha);
603 : } else {
604 253022 : _max_climb_scaler = _max_climb_scaler * (1.0f - hgt_dem_alpha) + hgt_dem_alpha;
605 253022 : _max_sink_scaler = _max_sink_scaler * (1.0f - hgt_dem_alpha) + hgt_dem_alpha;
606 : }
607 : }
608 290636 : _hgt_dem_prev = _hgt_dem;
609 : } else {
610 : // when flaring force height rate demand to the
611 : // configured sink rate and adjust the demanded height to
612 : // be kinematically consistent with the height rate.
613 :
614 : // set all height filter states to current height to prevent large pitch transients if flare is aborted
615 23256 : _hgt_dem_lpf = _height;
616 23256 : _hgt_dem_rate_ltd = _height;
617 23256 : _hgt_dem_in_prev = _height;
618 :
619 23256 : if (!_flare_initialised) {
620 110 : _flare_hgt_dem_adj = _hgt_dem;
621 110 : _flare_hgt_dem_ideal = _height;
622 110 : _hgt_at_start_of_flare = _hgt_afe;
623 110 : _hgt_rate_dem_at_flare_entry = _hgt_rate_dem;
624 110 : _flare_initialised = true;
625 : }
626 :
627 : // adjust the flare sink rate to increase/decrease as your travel further beyond the land wp
628 23256 : float land_sink_rate_adj = _land_sink + _land_sink_rate_change*_distance_beyond_land_wp;
629 :
630 : // bring it in linearly with height
631 : float p;
632 23256 : if (_hgt_at_start_of_flare > _flare_holdoff_hgt) {
633 22723 : p = constrain_float((_hgt_at_start_of_flare - _hgt_afe) / (_hgt_at_start_of_flare - _flare_holdoff_hgt), 0.0f, 1.0f);
634 : } else {
635 533 : p = 1.0f;
636 : }
637 23256 : _hgt_rate_dem = _hgt_rate_dem_at_flare_entry * (1.0f - p) - land_sink_rate_adj * p;
638 :
639 23256 : _flare_hgt_dem_ideal += _DT * _hgt_rate_dem; // the ideal height profile to follow
640 23256 : _flare_hgt_dem_adj += _DT * _hgt_rate_dem; // the demanded height profile that includes the pre-flare height tracking offset
641 :
642 : // fade across to the ideal height profile
643 23256 : _hgt_dem = _flare_hgt_dem_adj * (1.0f - p) + _flare_hgt_dem_ideal * p;
644 : }
645 313892 : }
646 :
647 313892 : void AP_TECS::_detect_underspeed(void)
648 : {
649 : // see if we can clear a previous underspeed condition. We clear
650 : // it if we are now more than 15% above min speed, and haven't
651 : // been below min speed for at least 3 seconds.
652 630382 : if (_flags.underspeed &&
653 314121 : _TAS_state >= _TASmin * 1.15f &&
654 229 : AP_HAL::millis() - _underspeed_start_ms > 3000U) {
655 1 : _flags.underspeed = false;
656 : }
657 :
658 313892 : if (_flight_stage == AP_FixedWing::FlightStage::VTOL) {
659 18726 : _flags.underspeed = false;
660 610613 : } else if (((_TAS_state < _TASmin * 0.9f) &&
661 20281 : (_throttle_dem >= _THRmaxf * 0.95f) &&
662 604873 : !_landing.is_flaring()) ||
663 291364 : ((_height < _hgt_dem) && _flags.underspeed))
664 : {
665 4229 : _flags.underspeed = true;
666 4229 : if (_TAS_state < _TASmin * 0.9f) {
667 : // reset start time as we are still underspeed
668 3802 : _underspeed_start_ms = AP_HAL::millis();
669 : }
670 : } else {
671 : // this clears underspeed if we reach our demanded height and
672 : // we are either below 95% throttle or we above 90% of min
673 : // airspeed
674 290937 : _flags.underspeed = false;
675 : }
676 313892 : }
677 :
678 313892 : void AP_TECS::_update_energies(void)
679 : {
680 : // Calculate specific energy demands
681 313892 : _SPE_dem = _hgt_dem * GRAVITY_MSS;
682 313892 : _SKE_dem = 0.5f * _TAS_dem_adj * _TAS_dem_adj;
683 :
684 : // Calculate specific energy rate demands and high pass filter demanded airspeed
685 : // rate of change to match the filtering applied to the measurement
686 313892 : _SKEdot_dem = _TAS_state * (_TAS_rate_dem - _TAS_rate_dem_lpf);
687 :
688 : // Calculate specific energy
689 313892 : _SPE_est = _height * GRAVITY_MSS;
690 313892 : _SKE_est = 0.5f * _TAS_state * _TAS_state;
691 :
692 : // Calculate specific energy rate and high pass filter airspeed rate of change
693 : // to remove effect of complementary filter induced bias errors
694 313892 : _SPEdot = _climb_rate * GRAVITY_MSS;
695 313892 : _SKEdot = _TAS_state * (_vel_dot - _vel_dot_lpf);
696 :
697 313892 : }
698 :
699 : /*
700 : current time constant. It is lower in landing to try to give a precise approach
701 : */
702 3177779 : float AP_TECS::timeConstant(void) const
703 : {
704 3177779 : if (_flags.is_doing_auto_land) {
705 377445 : if (_landTimeConst < 0.1f) {
706 0 : return 0.1f;
707 : }
708 377445 : return _landTimeConst;
709 : }
710 2800334 : if (_timeConst < 0.1f) {
711 0 : return 0.1f;
712 : }
713 2800334 : return _timeConst;
714 : }
715 :
716 : /*
717 : calculate throttle demand - airspeed enabled case
718 : */
719 299136 : void AP_TECS::_update_throttle_with_airspeed(void)
720 : {
721 : // Calculate limits to be applied to potential energy error to prevent over or underspeed occurring due to large height errors
722 299136 : float SPE_err_max = MAX(_SKE_est - 0.5f * _TASmin * _TASmin, 0.0f);
723 299136 : float SPE_err_min = MIN(_SKE_est - 0.5f * _TASmax * _TASmax, 0.0f);
724 :
725 299136 : if (_flight_stage == AP_FixedWing::FlightStage::VTOL) {
726 : /*
727 : when we are in a VTOL state then we ignore potential energy
728 : errors as we have vertical motors that interfere with the
729 : total energy calculation.
730 : */
731 18726 : SPE_err_max = SPE_err_min = 0;
732 : }
733 :
734 : // rate of change of potential energy is proportional to height error
735 299136 : _SPEdot_dem = (_SPE_dem - _SPE_est) / timeConstant();
736 :
737 : // Calculate total energy error
738 299136 : _STE_error = constrain_float((_SPE_dem - _SPE_est), SPE_err_min, SPE_err_max) + _SKE_dem - _SKE_est;
739 299136 : float STEdot_dem = constrain_float((_SPEdot_dem + _SKEdot_dem), _STEdot_min, _STEdot_max);
740 299136 : float STEdot_error = STEdot_dem - _SPEdot - _SKEdot;
741 :
742 : // Apply 0.5 second first order filter to STEdot_error
743 : // This is required to remove accelerometer noise from the measurement
744 299136 : const float filt_coef = 2.0f * _DT;
745 299136 : STEdot_error = filt_coef * STEdot_error + (1.0f - filt_coef) * _STEdotErrLast;
746 299136 : _STEdotErrLast = STEdot_error;
747 :
748 : // Calculate throttle demand
749 : // If underspeed condition is set, then demand full throttle
750 299136 : if (_flags.underspeed) {
751 4228 : _throttle_dem = 1.0f;
752 294908 : } else if (_flags.is_gliding) {
753 14349 : _throttle_dem = 0.0f;
754 : } else {
755 : // Calculate gain scaler from specific energy error to throttle
756 280559 : const float K_thr2STE = (_STEdot_max - _STEdot_min) / (_THRmaxf - _THRminf); // This is the derivative of STEdot wrt throttle measured across the max allowed throttle range.
757 280559 : const float K_STE2Thr = 1 / (timeConstant() * K_thr2STE);
758 :
759 : // Calculate feed-forward throttle
760 280559 : const float nomThr = aparm.throttle_cruise * 0.01f;
761 : // Use the demanded rate of change of total energy as the feed-forward demand, but add
762 : // additional component which scales with (1/(cos(bank angle)**2) - 1) to compensate for induced
763 : // drag increase during turns.
764 280559 : STEdot_dem = STEdot_dem + _rollComp * (1.0f/constrain_float(sq(_ahrs.cos_roll()), 0.1f, 1.0f) - 1.0f);
765 280559 : const float ff_throttle = nomThr + STEdot_dem / K_thr2STE;
766 :
767 : // Calculate PD + FF throttle
768 280559 : float throttle_damp = _thrDamp;
769 280559 : if (_flags.is_doing_auto_land && !is_zero(_land_throttle_damp)) {
770 0 : throttle_damp = _land_throttle_damp;
771 : }
772 280559 : _throttle_dem = (_STE_error + STEdot_error * throttle_damp) * K_STE2Thr + ff_throttle;
773 :
774 280559 : float THRminf_clipped_to_zero = constrain_float(_THRminf, 0, _THRmaxf);
775 :
776 : // Calculate integrator state upper and lower limits
777 : // Set to a value that will allow 0.1 (10%) throttle saturation to allow for noise on the demand
778 : // Additionally constrain the integrator state amplitude so that the integrator comes off limits faster.
779 280559 : const float maxAmp = 0.5f*(_THRmaxf - THRminf_clipped_to_zero);
780 280559 : const float integ_max = constrain_float((_THRmaxf - _throttle_dem + 0.1f),-maxAmp,maxAmp);
781 280559 : const float integ_min = constrain_float((_THRminf - _throttle_dem - 0.1f),-maxAmp,maxAmp);
782 :
783 : // Calculate integrator state, constraining state
784 : // Set integrator to a max throttle value during climbout
785 280559 : _integTHR_state = _integTHR_state + (_STE_error * _get_i_gain()) * _DT * K_STE2Thr;
786 280559 : if (_flight_stage == AP_FixedWing::FlightStage::TAKEOFF || _flight_stage == AP_FixedWing::FlightStage::ABORT_LANDING) {
787 5840 : if (!_flags.reached_speed_takeoff) {
788 : // ensure we run at full throttle until we reach the target airspeed
789 222 : _throttle_dem = MAX(_throttle_dem, _THRmaxf - _integTHR_state);
790 : }
791 : } else {
792 274719 : _integTHR_state = constrain_float(_integTHR_state, integ_min, integ_max);
793 : }
794 :
795 : // Rate limit PD + FF throttle
796 : // Calculate the throttle increment from the specified slew time
797 280559 : int16_t throttle_slewrate = aparm.throttle_slewrate;
798 280559 : if (_landing.is_on_approach()) {
799 5188 : const int16_t land_slewrate = _landing.get_throttle_slewrate();
800 5188 : if (land_slewrate > 0) {
801 0 : throttle_slewrate = land_slewrate;
802 : }
803 : }
804 :
805 280559 : if (throttle_slewrate != 0) {
806 280230 : const float thrRateIncr = _DT * (_THRmaxf - THRminf_clipped_to_zero) * throttle_slewrate * 0.01f;
807 :
808 280230 : _throttle_dem = constrain_float(_throttle_dem,
809 : _last_throttle_dem - thrRateIncr,
810 : _last_throttle_dem + thrRateIncr);
811 280230 : _last_throttle_dem = _throttle_dem;
812 : }
813 :
814 : // Sum the components.
815 280559 : _throttle_dem = _throttle_dem + _integTHR_state;
816 :
817 : #if HAL_LOGGING_ENABLED
818 280559 : if (AP::logger().should_log(_log_bitmask)){
819 : // @LoggerMessage: TEC3
820 : // @Vehicles: Plane
821 : // @Description: Additional additional information about the Total Energy Control System
822 : // @URL: http://ardupilot.club/plane/docs/tecs-total-energy-control-system-for-speed-height-tuning-guide.html
823 : // @Field: TimeUS: Time since system startup
824 : // @Field: KED: Kinetic Energy Dot (1st derivative of KE)
825 : // @Field: PED: Potential Energy Dot (1st derivative of PE)
826 : // @Field: KEDD: Kinetic Energy Dot Demand
827 : // @Field: PEDD: Potential Energy Dot Demand
828 : // @Field: TEE: Total energy error
829 : // @Field: TEDE: Total energy dot error (1st derivative of total energy error)
830 : // @Field: FFT: feed-forward throttle
831 : // @Field: Imin: integrator limit based on throttle values
832 : // @Field: Imax: integrator limit based on throttle values
833 : // @Field: I: integrator state for throttle
834 : // @Field: Emin: lower limit for potential energy error
835 : // @Field: Emax: upper limit for potential energy error
836 561118 : AP::logger().WriteStreaming(
837 : "TEC3",
838 : "TimeUS," "KED," "PED," "KEDD," "PEDD," "TEE," "TEDE," "FFT," "Imin," "Imax," "I," "Emin," "Emax",
839 : "s" "?" "?" "?" "?" "?" "?" "-" "-" "-" "-" "?" "?",
840 : "F" "0" "0" "0" "0" "0" "0" "0" "0" "0" "0" "0" "0",
841 : "Q" "f" "f" "f" "f" "f" "f" "f" "f" "f" "f" "f" "f",
842 : AP_HAL::micros64(),
843 280559 : _SKEdot,
844 280559 : _SPEdot,
845 280559 : _SKEdot_dem,
846 280559 : _SPEdot_dem,
847 280559 : _STE_error,
848 : STEdot_error,
849 : ff_throttle,
850 : integ_min,
851 : integ_max,
852 280559 : _integTHR_state,
853 : SPE_err_min,
854 : SPE_err_max
855 : );
856 :
857 : // @LoggerMessage: TEC4
858 : // @Vehicles: Plane
859 : // @Description: Additional additional additional information about the Total Energy Control System
860 : // @URL: http://ardupilot.club/plane/docs/tecs-total-energy-control-system-for-speed-height-tuning-guide.html
861 : // @Field: TimeUS: Time since system startup
862 : // @Field: P: estimate of potential energy
863 : // @Field: K: estimate of kinetic energy
864 : // @Field: Pdem: demanded potential energy
865 : // @Field: Kdem: demanded kinetic energy
866 : // @Field: ClimbMax: Climb rate upper limit
867 : // @Field: ClimbMin: Climb rate lower limit
868 561118 : AP::logger().WriteStreaming(
869 : "TEC4",
870 : "TimeUS," "P," "K," "Pdem," "Kdem," "ClimbMax," "ClimbMin",
871 : "s" "?" "?" "?" "?" "n" "n",
872 : "F" "0" "0" "0" "0" "0" "0",
873 : "Q" "f" "f" "f" "f" "f" "f",
874 : AP_HAL::micros64(),
875 280559 : _SPE_est,
876 280559 : _SKE_est,
877 280559 : _SPE_dem,
878 280559 : _SKE_dem,
879 280559 : _climb_rate_limit,
880 280559 : -_sink_rate_limit
881 : );
882 : }
883 : #endif
884 : }
885 :
886 299136 : constrain_throttle();
887 :
888 299136 : }
889 :
890 : // Constrain throttle demand and record clipping
891 313892 : void AP_TECS::constrain_throttle() {
892 313892 : if (_throttle_dem > _THRmaxf) {
893 33805 : _thr_clip_status = clipStatus::MAX;
894 33805 : _throttle_dem = _THRmaxf;
895 280087 : } else if (_throttle_dem < _THRminf) {
896 12214 : _thr_clip_status = clipStatus::MIN;
897 12214 : _throttle_dem = _THRminf;
898 : } else {
899 267873 : _thr_clip_status = clipStatus::NONE;
900 : }
901 313892 : }
902 :
903 594451 : float AP_TECS::_get_i_gain(void)
904 : {
905 594451 : float i_gain = _integGain;
906 594451 : if (_flight_stage == AP_FixedWing::FlightStage::TAKEOFF || _flight_stage == AP_FixedWing::FlightStage::ABORT_LANDING) {
907 13836 : i_gain = _integGain_takeoff;
908 580615 : } else if (_flags.is_doing_auto_land) {
909 69075 : if (!is_zero(_integGain_land)) {
910 0 : i_gain = _integGain_land;
911 : }
912 : }
913 594451 : return i_gain;
914 : }
915 :
916 : /*
917 : calculate throttle, non-airspeed case
918 : */
919 14756 : void AP_TECS::_update_throttle_without_airspeed(int16_t throttle_nudge, float pitch_trim_deg)
920 : {
921 : // reset clip status after possible use of synthetic airspeed
922 14756 : _thr_clip_status = clipStatus::NONE;
923 :
924 : // Calculate throttle demand by interpolating between pitch and throttle limits
925 : float nomThr;
926 : //If landing and we don't have an airspeed sensor and we have a non-zero
927 : //TECS_LAND_THR param then use it
928 14756 : if (_flags.is_doing_auto_land && _landThrottle >= 0) {
929 0 : nomThr = (_landThrottle + throttle_nudge) * 0.01f;
930 : } else { //not landing or not using TECS_LAND_THR parameter
931 14756 : nomThr = (aparm.throttle_cruise + throttle_nudge)* 0.01f;
932 : }
933 :
934 : // Use a pitch angle that is the sum of a highpassed _pitch_dem and a lowpassed ahrs pitch
935 : // so that the throttle mapping adjusts for the effect of pitch control errors
936 14756 : _pitch_demand_lpf.apply(_pitch_dem, _DT);
937 14756 : const float pitch_demand_hpf = _pitch_dem - _pitch_demand_lpf.get();
938 14756 : _pitch_measured_lpf.apply(_ahrs.get_pitch_rad(), _DT);
939 14756 : const float pitch_corrected_lpf = _pitch_measured_lpf.get() - radians(pitch_trim_deg);
940 14756 : const float pitch_blended = pitch_demand_hpf + pitch_corrected_lpf;
941 :
942 14756 : if (pitch_blended > 0.0f && _PITCHmaxf > 0.0f)
943 : {
944 1952 : _throttle_dem = nomThr + (_THRmaxf - nomThr) * pitch_blended / _PITCHmaxf;
945 : }
946 12804 : else if (pitch_blended < 0.0f && _PITCHminf < 0.0f)
947 : {
948 12703 : _throttle_dem = nomThr + (_THRminf - nomThr) * pitch_blended / _PITCHminf;
949 : }
950 : else
951 : {
952 101 : _throttle_dem = nomThr;
953 : }
954 :
955 14756 : if (_flags.is_gliding) {
956 0 : _throttle_dem = 0.0f;
957 0 : return;
958 : }
959 :
960 : // Calculate additional throttle for turn drag compensation including throttle nudging
961 : // Use the demanded rate of change of total energy as the feed-forward demand, but add
962 : // additional component which scales with (1/(cos(bank angle)**2) - 1) to compensate for induced
963 : // drag increase during turns.
964 14756 : float STEdot_dem = _rollComp * (1.0f/constrain_float(sq(_ahrs.cos_roll()), 0.1f, 1.0f) - 1.0f);
965 14756 : _throttle_dem = _throttle_dem + STEdot_dem / (_STEdot_max - _STEdot_min) * (_THRmaxf - _THRminf);
966 :
967 14756 : constrain_throttle();
968 : }
969 :
970 313892 : void AP_TECS::_detect_bad_descent(void)
971 : {
972 : // Don't detect bad descents when gliding, transitioning, or when underspeed.
973 313892 : if (_flags.is_gliding || _flight_stage == AP_FixedWing::FlightStage::VTOL || _flags.underspeed) {
974 37304 : _flags.badDescent = false;
975 37304 : return;
976 : }
977 :
978 : // Detect a demanded airspeed too high for the aircraft to achieve. This will be
979 : // evident by the following conditions:
980 : // 1) Specific total energy error > 200 (greater than ~20m height error)
981 : // 2) Specific total energy reducing
982 : // 3) throttle demand > 90%
983 : // If these conditions exist simultaneously, then the protection mode will be activated.
984 : // Once active, it will remain active until the specific total energy error drops below 0.
985 : // This mode will produce an undulating speed and height response as it cuts in and out, but it
986 : // will prevent the aircraft from descending into the ground if an unachievable speed demand is set.
987 276588 : float STEdot = _SPEdot + _SKEdot;
988 276588 : if (((_STE_error > 200.0f) && (STEdot < 0.0f) && (_throttle_dem >= _THRmaxf * 0.9f)) || (_flags.badDescent && (_STE_error > 0.0f))) {
989 3719 : _flags.badDescent = true;
990 : } else {
991 272869 : _flags.badDescent = false;
992 : }
993 : }
994 :
995 313892 : void AP_TECS::_update_pitch(void)
996 : {
997 : // Calculate Speed/Height Control Weighting
998 : // This is used to determine how the pitch control prioritises speed and height control
999 : // A weighting of 1 provides equal priority (this is the normal mode of operation)
1000 : // A SKE_weighting of 0 provides 100% priority to height control. This is used when no airspeed measurement is available
1001 : // A SKE_weighting of 2 provides 100% priority to speed control. This is used when an underspeed condition is detected. In this instance, if airspeed
1002 : // rises above the demanded value, the pitch angle will be increased by the TECS controller.
1003 313892 : _SKE_weighting = constrain_float(_spdWeight, 0.0f, 2.0f);
1004 313892 : if (!use_airspeed()) {
1005 14756 : _SKE_weighting = 0.0f;
1006 299136 : } else if (_flight_stage == AP_FixedWing::FlightStage::VTOL) {
1007 : // if we are in VTOL mode then control pitch without regard to
1008 : // speed. Speed is also taken care of independently of
1009 : // height. This is needed as the usual relationship of speed
1010 : // and height is broken by the VTOL motors
1011 18726 : _SKE_weighting = 0.0f;
1012 280410 : } else if ( _flags.underspeed || _flight_stage == AP_FixedWing::FlightStage::TAKEOFF || _flight_stage == AP_FixedWing::FlightStage::ABORT_LANDING || _flags.is_gliding) {
1013 24417 : _SKE_weighting = 2.0f;
1014 255993 : } else if (_flags.is_doing_auto_land) {
1015 32969 : if (_spdWeightLand < 0) {
1016 : // use sliding scale from normal weight down to zero at landing
1017 32969 : float scaled_weight = _spdWeight * (1.0f - constrain_float(_path_proportion,0,1));
1018 32969 : _SKE_weighting = constrain_float(scaled_weight, 0.0f, 2.0f);
1019 : } else {
1020 0 : _SKE_weighting = constrain_float(_spdWeightLand, 0.0f, 2.0f);
1021 : }
1022 : }
1023 :
1024 313892 : float SPE_weighting = 2.0f - _SKE_weighting;
1025 :
1026 : // either weight can fade to 0, but don't go above 1 to prevent instability if tuned at a speed weight of 1 and weighting is varied to end points in flight.
1027 313892 : SPE_weighting = MIN(SPE_weighting, 1.0f);
1028 313892 : _SKE_weighting = MIN(_SKE_weighting, 1.0f);
1029 :
1030 : // Calculate demanded specific energy balance and error
1031 313892 : float SEB_dem = _SPE_dem * SPE_weighting - _SKE_dem * _SKE_weighting;
1032 313892 : float SEB_est = _SPE_est * SPE_weighting - _SKE_est * _SKE_weighting;
1033 313892 : float SEB_error = SEB_dem - SEB_est;
1034 :
1035 : // track demanded height using the specified time constant
1036 313892 : float SEBdot_dem = _hgt_rate_dem * GRAVITY_MSS * SPE_weighting + SEB_error / timeConstant();
1037 313892 : const float SEBdot_dem_min = - _maxSinkRate * GRAVITY_MSS;
1038 313892 : const float SEBdot_dem_max = _maxClimbRate * GRAVITY_MSS;
1039 313892 : if (SEBdot_dem < SEBdot_dem_min) {
1040 9161 : SEBdot_dem = SEBdot_dem_min;
1041 9161 : _SEBdot_dem_clip = clipStatus::MIN;
1042 304731 : } else if (SEBdot_dem > SEBdot_dem_max) {
1043 5334 : SEBdot_dem = SEBdot_dem_max;
1044 5334 : _SEBdot_dem_clip = clipStatus::MAX;
1045 : } else {
1046 299397 : _SEBdot_dem_clip = clipStatus::NONE;
1047 : }
1048 :
1049 : // calculate specific energy balance rate error
1050 313892 : const float SEBdot_est = _SPEdot * SPE_weighting - _SKEdot * _SKE_weighting;
1051 313892 : float SEBdot_error = SEBdot_dem - SEBdot_est;
1052 :
1053 : // sum predicted plus damping correction
1054 : // integral correction is added later
1055 : // During flare a different damping gain is used
1056 313892 : float pitch_damp = _ptchDamp;
1057 313892 : if (_landing.is_flaring()) {
1058 23256 : pitch_damp = _landDamp;
1059 290636 : } else if (!is_zero(_land_pitch_damp) && _flags.is_doing_auto_land) {
1060 0 : pitch_damp = _land_pitch_damp;
1061 : }
1062 313892 : float SEBdot_dem_total = SEBdot_dem + SEBdot_error * pitch_damp;
1063 :
1064 : // inverse of gain from SEB to pitch angle
1065 313892 : float gainInv = (_TAS_state * GRAVITY_MSS);
1066 :
1067 : // During climbout/takeoff, bias the demanded pitch angle so that zero speed error produces a pitch angle
1068 : // demand equal to the minimum value (which is )set by the mission plan during this mode). Otherwise the
1069 : // integrator has to catch up before the nose can be raised to reduce speed during climbout.
1070 313892 : if (_flight_stage == AP_FixedWing::FlightStage::TAKEOFF || _flight_stage == AP_FixedWing::FlightStage::ABORT_LANDING) {
1071 7996 : SEBdot_dem_total += _PITCHminf * gainInv;
1072 : }
1073 :
1074 : // don't allow the integrator to rise by more than 20% of its full
1075 : // Calculate max and min values for integrator state that will allow for no more than
1076 : // 5deg of saturation. This allows for some pitch variation due to gusts before the
1077 : // integrator is clipped. Otherwise the effectiveness of the integrator will be reduced in turbulence
1078 313892 : float integSEBdot_min = (gainInv * (_PITCHminf - radians(5.0f))) - SEBdot_dem_total;
1079 313892 : float integSEBdot_max = (gainInv * (_PITCHmaxf + radians(5.0f))) - SEBdot_dem_total;
1080 :
1081 : // Calculate integrator state, constraining input if pitch limits are exceeded
1082 : // don't allow the integrator to rise by more than 10% of its full
1083 : // range in one step. This prevents single value glitches from
1084 : // causing massive integrator changes. See Issue#4066
1085 313892 : float integSEB_range = integSEBdot_max - integSEBdot_min;
1086 313892 : float integSEB_delta = constrain_float(SEBdot_error * _get_i_gain() * _DT, -integSEB_range*0.1f, integSEB_range*0.1f);
1087 :
1088 : // predict what pitch will be with uncontrained integration
1089 313892 : _pitch_dem_unc = (SEBdot_dem_total + _integSEBdot + integSEB_delta + _integKE) / gainInv;
1090 :
1091 : // integrate SEB rate error and apply integrator state limits
1092 614820 : const bool inhibit_integrator = ((_pitch_dem_unc > _PITCHmaxf) && integSEB_delta > 0.0f) ||
1093 300928 : ((_pitch_dem_unc < _PITCHminf) && integSEB_delta < 0.0f);
1094 313892 : if (!inhibit_integrator) {
1095 288436 : _integSEBdot += integSEB_delta;
1096 288436 : _integKE += (_SKE_est - _SKE_dem) * _SKE_weighting * _DT / timeConstant();
1097 : } else {
1098 : // fade out integrator if saturating
1099 25456 : const float coef = 1.0f - _DT / (_DT + timeConstant());
1100 25456 : _integSEBdot *= coef;
1101 25456 : _integKE *= coef;
1102 : }
1103 313892 : _integSEBdot = constrain_float(_integSEBdot, integSEBdot_min, integSEBdot_max);
1104 313892 : const float KE_integ_limit = 0.25f * (_PITCHmaxf - _PITCHminf) * gainInv; // allow speed trim integrator to access 505 of pitch range
1105 313892 : _integKE = constrain_float(_integKE, - KE_integ_limit, KE_integ_limit);
1106 :
1107 : // Calculate pitch demand from specific energy balance signals
1108 313892 : _pitch_dem_unc = (SEBdot_dem_total + _integSEBdot + _integKE) / gainInv;
1109 :
1110 : // Add a feedforward term from demanded airspeed to pitch
1111 313892 : if (_flags.is_gliding) {
1112 14349 : _pitch_dem_unc += (_TAS_dem_adj - _pitch_ff_v0) * _pitch_ff_k;
1113 : }
1114 :
1115 : // Constrain pitch demand
1116 313892 : _pitch_dem = constrain_float(_pitch_dem_unc, _PITCHminf, _PITCHmaxf);
1117 :
1118 : // Rate limit the pitch demand to comply with specified vertical
1119 : // acceleration limit
1120 313892 : float ptchRateIncr = _DT * _vertAccLim / _TAS_state;
1121 :
1122 313892 : if ((_pitch_dem - _last_pitch_dem) > ptchRateIncr) {
1123 4594 : _pitch_dem = _last_pitch_dem + ptchRateIncr;
1124 309298 : } else if ((_pitch_dem - _last_pitch_dem) < -ptchRateIncr) {
1125 5826 : _pitch_dem = _last_pitch_dem - ptchRateIncr;
1126 : }
1127 :
1128 313892 : _last_pitch_dem = _pitch_dem;
1129 :
1130 : #if HAL_LOGGING_ENABLED
1131 313892 : if (AP::logger().should_log(_log_bitmask)){
1132 : // log to AP_Logger
1133 : // @LoggerMessage: TEC2
1134 : // @Vehicles: Plane
1135 : // @Description: Additional information about the Total Energy Control System
1136 : // @URL: http://ardupilot.club/plane/docs/tecs-total-energy-control-system-for-speed-height-tuning-guide.html
1137 : // @Field: TimeUS: Time since system startup
1138 : // @Field: PEW: Potential energy weighting
1139 : // @Field: KEW: Kinetic energy weighting
1140 : // @Field: EBD: Energy balance demand
1141 : // @Field: EBE: Energy balance estimate
1142 : // @Field: EBDD: Energy balance rate demand
1143 : // @Field: EBDE: Energy balance rate estimate
1144 : // @Field: EBDDT: Energy balance rate demand + Energy balance rate error*pitch_damping
1145 : // @Field: Imin: Minimum integrator value
1146 : // @Field: Imax: Maximum integrator value
1147 : // @Field: I: Energy balance error integral
1148 : // @Field: KI: Pitch demand kinetic energy integral
1149 : // @Field: tmin: Throttle min
1150 : // @Field: tmax: Throttle max
1151 627784 : AP::logger().WriteStreaming(
1152 : "TEC2",
1153 : "TimeUS," "PEW," "KEW," "EBD," "EBE," "EBDD," "EBDE," "EBDDT," "Imin," "Imax," "I," "KI," "tmin," "tmax",
1154 : "s" "-" "-" "?" "?" "?" "?" "-" "-" "-" "-" "-" "-" "-",
1155 : "F" "0" "0" "0" "0" "0" "0" "0" "0" "0" "0" "0" "0" "0",
1156 : "Q" "f" "f" "f" "f" "f" "f" "f" "f" "f" "f" "f" "f" "f",
1157 : AP_HAL::micros64(),
1158 : SPE_weighting,
1159 313892 : _SKE_weighting,
1160 : SEB_dem,
1161 : SEB_est,
1162 : SEBdot_dem,
1163 : SEBdot_est,
1164 : SEBdot_dem_total,
1165 : integSEBdot_min,
1166 : integSEBdot_max,
1167 313892 : _integSEBdot,
1168 313892 : _integKE,
1169 313892 : _THRminf,
1170 313892 : _THRmaxf
1171 : );
1172 : }
1173 : #endif
1174 313892 : }
1175 :
1176 313892 : void AP_TECS::_initialise_states(float hgt_afe)
1177 : {
1178 : // Initialise states and variables if DT > 0.2 second or TECS is getting overridden or in climbout.
1179 313892 : _flags.reset = false;
1180 :
1181 313892 : if (_DT > 0.2f || _need_reset) {
1182 402 : _SKE_weighting = 1.0f;
1183 402 : _integTHR_state = 0.0f;
1184 402 : _integSEBdot = 0.0f;
1185 402 : _integKE = 0.0f;
1186 402 : _last_throttle_dem = aparm.throttle_cruise * 0.01f;
1187 402 : _last_pitch_dem = _ahrs.get_pitch_rad();
1188 402 : _hgt_dem_in_prev = hgt_afe;
1189 402 : _hgt_dem_lpf = hgt_afe;
1190 402 : _hgt_dem_rate_ltd = hgt_afe;
1191 402 : _hgt_dem_prev = hgt_afe;
1192 402 : _TAS_dem_adj = _TAS_dem;
1193 402 : _flags.reset = true;
1194 402 : _DT = 0.02f; // when first starting TECS, use the most likely time constant
1195 402 : _lag_comp_hgt_offset = 0.0f;
1196 402 : _post_TO_hgt_offset = 0.0f;
1197 402 : _use_synthetic_airspeed_once = false;
1198 :
1199 402 : _flags.underspeed = false;
1200 402 : _flags.badDescent = false;
1201 402 : _flags.reached_speed_takeoff = false;
1202 402 : _need_reset = false;
1203 :
1204 : // misc variables used for alternative precision landing pitch control
1205 402 : _hgt_at_start_of_flare = 0.0f;
1206 402 : _hgt_rate_dem_at_flare_entry = 0.0f;
1207 402 : _hgt_afe = 0.0f;
1208 402 : _pitch_min_at_flare_entry = 0.0f;
1209 :
1210 402 : _max_climb_scaler = 1.0f;
1211 402 : _max_sink_scaler = 1.0f;
1212 :
1213 402 : const float fc = 1.0f / (M_2PI * _timeConst);
1214 402 : _pitch_demand_lpf.set_cutoff_frequency(fc);
1215 402 : _pitch_measured_lpf.set_cutoff_frequency(fc);
1216 402 : _pitch_demand_lpf.reset(_ahrs.get_pitch_rad());
1217 402 : _pitch_measured_lpf.reset(_ahrs.get_pitch_rad());
1218 :
1219 313892 : } else if (_flight_stage == AP_FixedWing::FlightStage::TAKEOFF || _flight_stage == AP_FixedWing::FlightStage::ABORT_LANDING) {
1220 :
1221 7891 : if (!_flag_throttle_forced) {
1222 : // Calculate the takeoff target height offset before _hgt_dem_in_raw gets reset below.
1223 : // Prevent the offset from becoming negative.
1224 121 : _post_TO_hgt_offset = MAX(MIN(_climb_rate_limit * _hgt_dem_tconst, _hgt_dem_in_raw - hgt_afe), 0);
1225 : } else {
1226 : // If throttle is externally forced, this mechanism of adding energy is unnecessary.
1227 7770 : _post_TO_hgt_offset = 0;
1228 : }
1229 :
1230 7891 : _hgt_afe = hgt_afe;
1231 7891 : _hgt_dem_lpf = hgt_afe;
1232 7891 : _hgt_dem_rate_ltd = hgt_afe;
1233 7891 : _hgt_dem_prev = hgt_afe;
1234 7891 : _hgt_dem = hgt_afe;
1235 7891 : _hgt_dem_in_prev = hgt_afe;
1236 7891 : _hgt_dem_in_raw = hgt_afe;
1237 7891 : _flags.underspeed = false;
1238 7891 : _flags.badDescent = false;
1239 7891 : _TAS_dem_adj = _TAS_dem;
1240 7891 : _max_climb_scaler = 1.0f;
1241 7891 : _max_sink_scaler = 1.0f;
1242 7891 : _pitch_demand_lpf.reset(_ahrs.get_pitch_rad());
1243 7891 : _pitch_measured_lpf.reset(_ahrs.get_pitch_rad());
1244 :
1245 :
1246 7891 : if (!_flag_have_reset_after_takeoff) {
1247 114 : _flags.reset = true;
1248 114 : _flag_have_reset_after_takeoff = true;
1249 : }
1250 : }
1251 :
1252 313892 : if (_flight_stage != AP_FixedWing::FlightStage::TAKEOFF && _flight_stage != AP_FixedWing::FlightStage::ABORT_LANDING) {
1253 : // reset takeoff speed flag when not in takeoff
1254 305896 : _flags.reached_speed_takeoff = false;
1255 305896 : _flag_have_reset_after_takeoff = false;
1256 : }
1257 313892 : }
1258 :
1259 313892 : void AP_TECS::_update_STE_rate_lim(void)
1260 : {
1261 : // Calculate Specific Total Energy Rate Limits & deceleration rate bounds
1262 : // Keep the 50% energy margin from the original velRate calculation for now
1263 : // This is a trivial calculation at the moment but will get bigger once we start adding altitude effects
1264 313892 : _STEdot_max = _climb_rate_limit * GRAVITY_MSS;
1265 313892 : _STEdot_min = - _minSinkRate * GRAVITY_MSS;
1266 313892 : _STEdot_neg_max = - _maxSinkRate * GRAVITY_MSS;
1267 313892 : }
1268 :
1269 :
1270 313893 : void AP_TECS::update_pitch_throttle(int32_t hgt_dem_cm,
1271 : int32_t EAS_dem_cm,
1272 : enum AP_FixedWing::FlightStage flight_stage,
1273 : float distance_beyond_land_wp,
1274 : int32_t ptchMinCO_cd,
1275 : int16_t throttle_nudge,
1276 : float hgt_afe,
1277 : float load_factor,
1278 : float pitch_trim_deg)
1279 : {
1280 313893 : uint64_t now = AP_HAL::micros64();
1281 : // check how long since we last did the 50Hz update; do nothing in
1282 : // this loop if that hasn't run for some significant period of
1283 : // time. Notably, it may never have run, leaving _TAS_state as
1284 : // zero and subsequently division-by-zero errors.
1285 313893 : const float _DT_for_update_50hz = (now - _update_50hz_last_usec) * 1.0e-6f;
1286 313893 : if (_update_50hz_last_usec == 0 || _DT_for_update_50hz > 1.0) {
1287 : // more than 1 second since it was run, don't do anything yet:
1288 1 : return;
1289 : }
1290 :
1291 : // Calculate time in seconds since last update
1292 313892 : _DT = (now - _update_pitch_throttle_last_usec) * 1.0e-6f;
1293 313892 : _DT = MAX(_DT, 0.001f);
1294 313892 : _update_pitch_throttle_last_usec = now;
1295 :
1296 313892 : _flags.is_gliding = _flags.gliding_requested || _flags.propulsion_failed || aparm.throttle_max==0;
1297 313892 : _flags.is_doing_auto_land = (flight_stage == AP_FixedWing::FlightStage::LAND);
1298 313892 : _distance_beyond_land_wp = distance_beyond_land_wp;
1299 313892 : _flight_stage = flight_stage;
1300 :
1301 : // Convert inputs
1302 313892 : _hgt_dem_in_raw = hgt_dem_cm * 0.01f;
1303 313892 : _EAS_dem = EAS_dem_cm * 0.01f;
1304 313892 : _hgt_afe = hgt_afe;
1305 313892 : _load_factor = load_factor;
1306 :
1307 : // Don't allow height deamnd to continue changing in a direction that saturates vehicle manoeuvre limits
1308 : // if vehicle is unable to follow the demanded climb or descent.
1309 356724 : const bool max_climb_condition = (_pitch_dem_unc > _PITCHmaxf || _thr_clip_status == clipStatus::MAX) &&
1310 42832 : !(_flight_stage == AP_FixedWing::FlightStage::TAKEOFF || _flight_stage == AP_FixedWing::FlightStage::ABORT_LANDING);
1311 313892 : const bool max_descent_condition = _pitch_dem_unc < _PITCHminf || _thr_clip_status == clipStatus::MIN;
1312 313892 : if (max_climb_condition && _hgt_dem_in_raw > _hgt_dem_in_prev) {
1313 10550 : _hgt_dem_in = _hgt_dem_in_prev;
1314 303342 : } else if (max_descent_condition && _hgt_dem_in_raw < _hgt_dem_in_prev) {
1315 5683 : _hgt_dem_in = _hgt_dem_in_prev;
1316 : } else {
1317 297659 : _hgt_dem_in = _hgt_dem_in_raw;
1318 : }
1319 :
1320 : // Update the throttle limits.
1321 313892 : _update_throttle_limits();
1322 :
1323 313892 : _update_pitch_limits(ptchMinCO_cd);
1324 :
1325 313892 : if (flight_stage == AP_FixedWing::FlightStage::TAKEOFF || flight_stage == AP_FixedWing::FlightStage::ABORT_LANDING) {
1326 7996 : if (!_flags.reached_speed_takeoff && _TAS_state >= _TASmin && _TASmin > 0) {
1327 : // we have reached our target speed in takeoff, allow for
1328 : // normal throttle control
1329 118 : _flags.reached_speed_takeoff = true;
1330 : }
1331 : }
1332 :
1333 : // initialise selected states and variables if DT > 1 second or in climbout
1334 313892 : _initialise_states(hgt_afe);
1335 :
1336 : // Calculate Specific Total Energy Rate Limits
1337 313892 : _update_STE_rate_lim();
1338 :
1339 : // Calculate the speed demand
1340 313892 : _update_speed_demand();
1341 :
1342 : // Calculate the height demand
1343 313892 : _update_height_demand();
1344 :
1345 : // Detect underspeed condition
1346 313892 : _detect_underspeed();
1347 :
1348 : // Calculate specific energy quantitiues
1349 313892 : _update_energies();
1350 :
1351 : // Calculate pitch demand
1352 313892 : _update_pitch();
1353 :
1354 : // Calculate throttle demand - use simple pitch to throttle if no airspeed estimate.
1355 313892 : if (use_airspeed()) {
1356 299136 : _update_throttle_with_airspeed();
1357 299136 : _use_synthetic_airspeed_once = false;
1358 299136 : _using_airspeed_for_throttle = true;
1359 : } else {
1360 14756 : _update_throttle_without_airspeed(throttle_nudge, pitch_trim_deg);
1361 14756 : _using_airspeed_for_throttle = false;
1362 : }
1363 :
1364 : // Detect bad descent due to demanded airspeed being too high
1365 313892 : _detect_bad_descent();
1366 :
1367 313892 : if (option_is_set(Option::GLIDER_ONLY)) {
1368 4799 : _flags.badDescent = false;
1369 : }
1370 :
1371 : #if HAL_LOGGING_ENABLED
1372 313892 : if (AP::logger().should_log(_log_bitmask)){
1373 : // log to AP_Logger
1374 : // @LoggerMessage: TECS
1375 : // @Vehicles: Plane
1376 : // @Description: Information about the Total Energy Control System
1377 : // @URL: http://ardupilot.club/plane/docs/tecs-total-energy-control-system-for-speed-height-tuning-guide.html
1378 : // @Field: TimeUS: Time since system startup
1379 : // @Field: h: height estimate (UP) currently in use by TECS
1380 : // @Field: dh: current climb rate ("delta-height")
1381 : // @Field: hin: height demand received by TECS
1382 : // @Field: hdem: height demand after rate limiting and filtering that TECS is currently trying to achieve
1383 : // @Field: dhdem: climb rate TECS is currently trying to achieve
1384 : // @Field: spdem: True AirSpeed TECS is currently trying to achieve
1385 : // @Field: sp: current estimated True AirSpeed
1386 : // @Field: dsp: x-axis acceleration estimate ("delta-speed")
1387 : // @Field: th: throttle output
1388 : // @Field: ph: pitch output
1389 : // @Field: pmin: pitch lower limit
1390 : // @Field: pmax: pitch upper limit
1391 : // @Field: dspdem: demanded acceleration output ("delta-speed demand")
1392 : // @Field: f: flags
1393 : // @FieldBits: f: Underspeed,UnachievableDescent,AutoLanding,ReachedTakeoffSpd,GlidingRequested,isGliding,PropulsionFailed,Reset
1394 313892 : AP::logger().WriteStreaming(
1395 : "TECS",
1396 : "TimeUS," "h," "dh," "hin," "hdem," "dhdem," "spdem," "sp," "dsp," "th," "ph," "pmin," "pmax," "dspdem," "f",
1397 : "s" "m" "n" "m" "m" "n" "n" "n" "n" "-" "-" "-" "-" "-" "-",
1398 : "F" "0" "0" "0" "0" "0" "0" "0" "0" "-" "-" "-" "-" "-" "-",
1399 : "Q" "f" "f" "f" "f" "f" "f" "f" "f" "f" "f" "f" "f" "f" "B",
1400 : now,
1401 313892 : _height,
1402 313892 : _climb_rate,
1403 313892 : _hgt_dem_in_raw,
1404 313892 : _hgt_dem,
1405 313892 : _hgt_rate_dem,
1406 313892 : _TAS_dem_adj,
1407 313892 : _TAS_state,
1408 313892 : _vel_dot,
1409 313892 : _throttle_dem,
1410 313892 : _pitch_dem,
1411 313892 : _PITCHminf,
1412 313892 : _PITCHmaxf,
1413 313892 : _TAS_rate_dem,
1414 313892 : _flags_byte
1415 : );
1416 : }
1417 : #endif
1418 : }
1419 :
1420 : // set minimum throttle override, [-1, -1] range
1421 : // if reset_output is true the output slew limiter will also be reset to respect the lower limit
1422 : // its decay is controlled by TECS_THR_ERATE
1423 7338766 : void AP_TECS::set_throttle_min(const float thr_min, bool reset_output) {
1424 : // Don't change the limit if it is already covered.
1425 7338766 : if (thr_min > _THRminf_ext) {
1426 306693 : _THRminf_ext = thr_min;
1427 306693 : if (reset_output) {
1428 6 : _last_throttle_dem = MAX(_last_throttle_dem, _THRminf_ext);
1429 6 : _throttle_dem = _last_throttle_dem;
1430 : }
1431 : }
1432 7338766 : }
1433 :
1434 : // set minimum throttle override, [0, -1] range
1435 : // it is applicable for one control cycle only
1436 7338760 : void AP_TECS::set_throttle_max(const float thr_max) {
1437 : // Don't change the limit if it is already covered.
1438 7338760 : if (thr_max < _THRmaxf_ext) {
1439 9931 : _THRmaxf_ext = thr_max;
1440 : }
1441 7338760 : }
1442 :
1443 313892 : void AP_TECS::_update_throttle_limits() {
1444 :
1445 : // Configure max throttle; constrain to the external safety limits.
1446 313892 : _THRmaxf = MIN(1.0f, _THRmaxf_ext);
1447 : // Configure min throttle; constrain to the external safety limits.
1448 313892 : _THRminf = MAX(-1.0f, _THRminf_ext);
1449 :
1450 : // Allow a minimum of 1% throttle range, primarily to prevent TECS numerical errors.
1451 313892 : const float thr_eps = 0.01;
1452 313892 : if (fabsf(_THRminf-_THRmaxf) < thr_eps) {
1453 12724 : _flag_throttle_forced = true;
1454 12724 : if (_THRmaxf < 1) {
1455 5252 : _THRmaxf = MAX(_THRmaxf, _THRminf + 0.01f);
1456 : } else {
1457 7472 : _THRminf = MIN(_THRminf, _THRmaxf - 0.01f);
1458 : }
1459 : } else {
1460 301168 : _flag_throttle_forced = false;
1461 : }
1462 :
1463 : // Reset the external throttle limits.
1464 : // Caller will have to reset them in the next iteration.
1465 313892 : if (_thr_min_pct_ext_rate_lim > 0) {
1466 313892 : _THRminf_ext -= 0.01f * float(_thr_min_pct_ext_rate_lim) * _DT;
1467 313892 : _THRminf_ext = MAX(_THRminf_ext, -1.0f);
1468 : } else {
1469 0 : _THRminf_ext = -1.0f;
1470 : }
1471 313892 : _THRmaxf_ext = 1.0f;
1472 313892 : }
1473 :
1474 392752 : void AP_TECS::set_pitch_min(const float pitch_min) {
1475 : // Don't change the limit if it is already covered.
1476 392752 : if (pitch_min > _PITCHminf_ext) {
1477 19694 : _PITCHminf_ext = pitch_min;
1478 : }
1479 392752 : }
1480 :
1481 329054 : void AP_TECS::set_pitch_max(const float pitch_max) {
1482 : // Don't change the limit if it is already covered.
1483 329054 : if (pitch_max < _PITCHmaxf_ext) {
1484 12129 : _PITCHmaxf_ext = pitch_max;
1485 : }
1486 329054 : }
1487 :
1488 313892 : void AP_TECS::_update_pitch_limits(const int32_t ptchMinCO_cd) {
1489 : // If TECS_PITCH_{MAX,MIN} isn't set then use LIM_PITCH_{MAX,MIN}.
1490 : // Don't allow TECS_PITCH_{MAX,MIN} to be larger than LIM_PITCH_{MAX,MIN}.
1491 313892 : if (_pitch_max == 0) {
1492 0 : _PITCHmaxf = aparm.pitch_limit_max;
1493 : } else {
1494 313892 : _PITCHmaxf = _pitch_max;
1495 : }
1496 :
1497 313892 : if (_pitch_min == 0) {
1498 309093 : _PITCHminf = aparm.pitch_limit_min;
1499 : } else {
1500 4799 : _PITCHminf = _pitch_min;
1501 : }
1502 :
1503 313892 : if (!_landing.is_on_approach()) {
1504 : // reset land pitch min when not landing
1505 307639 : _land_pitch_min = _PITCHminf;
1506 : }
1507 :
1508 : // calculate the expected pitch angle from the demanded climb rate and airspeed for use during approach and flare
1509 313892 : if (_landing.is_flaring()) {
1510 : // smoothly move the min pitch to the required minimum at touchdown
1511 : float p; // 0 at start of flare, 1 at finish
1512 23256 : if (!_flare_initialised) {
1513 110 : p = 0.0f;
1514 23146 : } else if (_hgt_at_start_of_flare > _flare_holdoff_hgt) {
1515 22616 : p = constrain_float((_hgt_at_start_of_flare - _hgt_afe) / _hgt_at_start_of_flare, 0.0f, 1.0f);
1516 : } else {
1517 530 : p = 1.0f;
1518 : }
1519 23256 : const float pitch_limit_deg = (1.0f - p) * _pitch_min_at_flare_entry + p * 0.01f * _landing.get_pitch_cd();
1520 :
1521 : // in flare use min pitch from LAND_PITCH_DEG
1522 23256 : _PITCHminf = MAX(_PITCHminf, pitch_limit_deg);
1523 :
1524 : // and use max pitch from TECS_LAND_PMAX
1525 23256 : if (_land_pitch_max != 0) {
1526 : // note that this allows a flare pitch outside the normal TECS auto limits
1527 23256 : _PITCHmaxf = _land_pitch_max;
1528 : }
1529 290636 : } else if (_landing.is_on_approach()) {
1530 6253 : _PITCHminf = MAX(_PITCHminf, aparm.pitch_limit_min);
1531 6253 : _pitch_min_at_flare_entry = _PITCHminf;
1532 6253 : _flare_initialised = false;
1533 : } else {
1534 284383 : _flare_initialised = false;
1535 : }
1536 :
1537 313892 : if (_landing.is_on_approach()) {
1538 : // don't allow the lower bound of pitch to decrease, nor allow
1539 : // it to increase rapidly. This prevents oscillation of pitch
1540 : // demand while in landing approach based on rapidly changing
1541 : // time to flare estimate
1542 6253 : if (_land_pitch_min <= -90) {
1543 0 : _land_pitch_min = _PITCHminf;
1544 : }
1545 6253 : const float flare_pitch_range = 20;
1546 6253 : const float delta_per_loop = (flare_pitch_range/_landTimeConst) * _DT;
1547 6253 : _PITCHminf = MIN(_PITCHminf, _land_pitch_min+delta_per_loop);
1548 6253 : _land_pitch_min = MAX(_land_pitch_min, _PITCHminf);
1549 6253 : _PITCHminf = MAX(_land_pitch_min, _PITCHminf);
1550 : }
1551 :
1552 : // Apply TAKEOFF minimum pitch
1553 313892 : if (_flight_stage == AP_FixedWing::FlightStage::TAKEOFF
1554 306143 : || _flight_stage == AP_FixedWing::FlightStage::ABORT_LANDING)
1555 : {
1556 7996 : _PITCHminf = cd_to_rad(ptchMinCO_cd);
1557 : }
1558 :
1559 : // Apply external limits.
1560 313892 : _PITCHmaxf = MIN(_PITCHmaxf, _PITCHmaxf_ext);
1561 313892 : _PITCHminf = MAX(_PITCHminf, _PITCHminf_ext);
1562 :
1563 : // Reset the external pitch limits.
1564 313892 : _PITCHminf_ext = -90.0f;
1565 313892 : _PITCHmaxf_ext = 90.0f;
1566 :
1567 : // convert to radians
1568 313892 : _PITCHmaxf = radians(_PITCHmaxf);
1569 313892 : _PITCHminf = radians(_PITCHminf);
1570 :
1571 : // don't allow max pitch to go below min pitch
1572 313892 : _PITCHmaxf = MAX(_PITCHmaxf, _PITCHminf);
1573 313892 : }
1574 :
1575 291 : void AP_TECS::offset_altitude(const float alt_offset)
1576 : {
1577 : // Convention: When alt_offset is positive it means that the altitude of
1578 : // home has increased. Thus, the relative altitude of the vehicle has
1579 : // decreased.
1580 : //
1581 : // Assumption: This method is called more often and before
1582 : // `update_pitch_throttle()`. This is necessary to ensure that new height
1583 : // demands which incorporate the home change are compatible with the
1584 : // (now updated) internal height state.
1585 :
1586 291 : _flare_hgt_dem_ideal -= alt_offset;
1587 291 : _flare_hgt_dem_adj -= alt_offset;
1588 291 : _hgt_at_start_of_flare -= alt_offset;
1589 291 : _hgt_dem_in_prev -= alt_offset;
1590 291 : _hgt_dem_lpf -= alt_offset;
1591 291 : _hgt_dem_rate_ltd -= alt_offset;
1592 291 : _hgt_dem_prev -= alt_offset;
1593 291 : _height_filter.height -= alt_offset;
1594 :
1595 : // The following variables are updated anew in every call of
1596 : // `update_pitch_throttle()`. There's no need to update those.
1597 : // _hgt_dem
1598 : // _hgt_dem_in_raw
1599 : // _hgt_dem_in
1600 : // Energies
1601 291 : }
1602 :
1603 : // Return true if airspeed should be used (either from a sensor or synthetic)
1604 2658142 : bool AP_TECS::use_airspeed() const
1605 : {
1606 : // Note that caller can demand the use of
1607 : // synthetic airspeed for one loop if needed. This is required
1608 : // during QuadPlane transition when pitch is constrained
1609 2658142 : return _ahrs.using_airspeed_sensor() || _use_synthetic_airspeed || _use_synthetic_airspeed_once;
1610 : }
|