Line data Source code
1 : #include "Plane.h"
2 :
3 : #if HAL_QUADPLANE_ENABLED
4 :
5 : #include "AC_AttitudeControl/AC_AttitudeControl_TS.h"
6 :
7 : const AP_Param::GroupInfo QuadPlane::var_info[] = {
8 :
9 : // @Param: ENABLE
10 : // @DisplayName: Enable QuadPlane
11 : // @Description: This enables QuadPlane functionality, assuming multicopter motors start on output 5. If this is set to 2 then when starting AUTO mode it will initially be in VTOL AUTO mode.
12 : // @Values: 0:Disable,1:Enable,2:Enable VTOL AUTO
13 : // @User: Standard
14 : // @RebootRequired: True
15 : AP_GROUPINFO_FLAGS("ENABLE", 1, QuadPlane, enable, 0, AP_PARAM_FLAG_ENABLE),
16 :
17 : // @Group: M_
18 : // @Path: ../libraries/AP_Motors/AP_MotorsMulticopter.cpp
19 : AP_SUBGROUPVARPTR(motors, "M_", 2, QuadPlane, plane.quadplane.motors_var_info),
20 :
21 : // 3 ~ 8 were used by quadplane attitude control PIDs
22 :
23 : // 10 was ANGLE_MAX
24 :
25 : // @Param: TRANSITION_MS
26 : // @DisplayName: Transition time
27 : // @Description: Transition time in milliseconds after minimum airspeed is reached
28 : // @Units: ms
29 : // @Range: 500 30000
30 : // @User: Advanced
31 : AP_GROUPINFO("TRANSITION_MS", 11, QuadPlane, transition_time_ms, 5000),
32 :
33 : // 12 ~ 16 were used by position, velocity and acceleration PIDs
34 :
35 : // @Group: P
36 : // @Path: ../libraries/AC_AttitudeControl/AC_PosControl.cpp
37 : AP_SUBGROUPPTR(pos_control, "P", 17, QuadPlane, AC_PosControl),
38 :
39 : // @Param: PILOT_SPD_UP
40 : // @DisplayName: Pilot maximum vertical speed up
41 : // @Description: The maximum ascending vertical velocity the pilot may request in m/s
42 : // @Units: m/s
43 : // @Range: 0.5 5
44 : // @Increment: 0.1
45 : // @User: Standard
46 : AP_GROUPINFO("PILOT_SPD_UP", 18, QuadPlane, pilot_speed_z_max_up_ms, 2.50),
47 :
48 : // @Param: PILOT_SPD_DN
49 : // @DisplayName: Pilot maximum vertical speed down
50 : // @Description: The maximum vertical velocity the pilot may request in m/s going down. If 0, uses Q_PILOT_SPD_UP value.
51 : // @Units: m/s
52 : // @Range: 0.5 5
53 : // @Increment: 0.1
54 : // @User: Standard
55 : AP_GROUPINFO("PILOT_SPD_DN", 60, QuadPlane, pilot_speed_z_max_dn_ms, 0),
56 :
57 : // @Param: PILOT_ACCEL_Z
58 : // @DisplayName: Pilot vertical acceleration
59 : // @Description: The vertical acceleration used when pilot is controlling the altitude
60 : // @Units: m/s/s
61 : // @Range: 0.5 5
62 : // @Increment: 0.1
63 : // @User: Standard
64 : AP_GROUPINFO("PILOT_ACCEL_Z", 19, QuadPlane, pilot_accel_z_mss, 2.5),
65 :
66 : // @Group: WP_
67 : // @Path: ../libraries/AC_WPNav/AC_WPNav.cpp
68 : AP_SUBGROUPPTR(wp_nav, "WP_", 20, QuadPlane, AC_WPNav),
69 :
70 : // @Param: RC_SPEED
71 : // @DisplayName: RC output speed in Hz
72 : // @Description: This is the PWM refresh rate in Hz for QuadPlane quad motors
73 : // @Units: Hz
74 : // @Range: 50 500
75 : // @Increment: 10
76 : // @User: Standard
77 : AP_GROUPINFO("RC_SPEED", 21, QuadPlane, rc_speed, 490),
78 :
79 : // @Param: THR_MIN_PWM
80 : // @DisplayName: Minimum PWM output
81 : // @Description: This is the minimum PWM output for the quad motors
82 : // @Units: PWM
83 : // @Range: 800 2200
84 : // @Increment: 1
85 : // @User: Standard
86 : // 22: THR_MIN_PWM
87 :
88 : // @Param: THR_MAX_PWM
89 : // @DisplayName: Maximum PWM output
90 : // @Description: This is the maximum PWM output for the quad motors
91 : // @Units: PWM
92 : // @Range: 800 2200
93 : // @Increment: 1
94 : // @User: Standard
95 : // 23: THR_MAX_PWM
96 :
97 : // @Param: ASSIST_SPEED
98 : // @DisplayName: Quadplane assistance speed
99 : // @Description: This is the speed below which the quad motors will provide stability and lift assistance in fixed wing modes. The default value of 0 disables assistance but will generate a pre-arm failure to encourage users to set this parameter to -1, or a positive, non-zero value. If this is set to -1 then all Q_ASSIST features are disabled except during transitions. A high non-zero,positive value will lead to more false positives which can waste battery. A lower value will result in less false positive, but will result in assistance taking longer to trigger. If unsure then set to 3 m/s below the minimum airspeed you will fly at. If you don't have an airspeed sensor then use 5 m/s below the minimum airspeed you fly at.
100 : // @Units: m/s
101 : // @Range: 0 100
102 : // @Increment: 0.1
103 : // @User: Standard
104 : AP_GROUPINFO("ASSIST_SPEED", 24, QuadPlane, assist.speed, 0),
105 :
106 : // @Param: YAW_RATE_MAX
107 : // @DisplayName: Maximum yaw rate
108 : // @Description: This is the maximum yaw rate for pilot input on rudder stick in degrees/second and weathervaning is limited to half of this value
109 : // @Units: deg/s
110 : // @Range: 50 500
111 : // @Increment: 1
112 : // @User: Standard
113 :
114 : // YAW_RATE_MAX index 25
115 :
116 : // @Param: LAND_FINAL_SPD
117 : // @DisplayName: Land final speed
118 : // @Description: The descent speed for the final stage of landing in m/s
119 : // @Units: m/s
120 : // @Range: 0.3 2
121 : // @Increment: 0.1
122 : // @User: Standard
123 : AP_GROUPINFO("LAND_FINAL_SPD", 26, QuadPlane, land_final_speed_ms, 0.5),
124 :
125 : // @Param: LAND_FINAL_ALT
126 : // @DisplayName: Land final altitude
127 : // @Description: The altitude at which we should switch to Q_LAND_FINAL_SPD descent rate
128 : // @Units: m
129 : // @Range: 0.5 50
130 : // @Increment: 0.1
131 : // @User: Standard
132 : AP_GROUPINFO("LAND_FINAL_ALT", 27, QuadPlane, land_final_alt_m, 6),
133 :
134 : // 28 was used by THR_MID
135 :
136 : // @Param: TRAN_PIT_MAX
137 : // @DisplayName: Transition max pitch
138 : // @Description: Maximum pitch during transition to auto fixed wing flight
139 : // @User: Standard
140 : // @Range: 0 30
141 : // @Units: deg
142 : // @Increment: 1
143 : AP_GROUPINFO("TRAN_PIT_MAX", 29, QuadPlane, transition_pitch_max, 3),
144 :
145 : // frame class was moved from 30 when consolidating AP_Motors classes
146 :
147 : // @Param: FRAME_CLASS
148 : // @DisplayName: Frame Class
149 : // @Description: Controls major frame class for multicopter component
150 : // @Values: 0:Undefined, 1:Quad, 2:Hexa, 3:Octa, 4:OctaQuad, 5:Y6, 7:Tri, 10: Single/Dual, 12:DodecaHexa, 14:Deca, 15:Scripting Matrix, 17:Dynamic Scripting Matrix
151 : // @User: Standard
152 : AP_GROUPINFO("FRAME_CLASS", 46, QuadPlane, frame_class, 1),
153 :
154 : // @Param: FRAME_TYPE
155 : // @DisplayName: Frame Type (+, X or V)
156 : // @Description: Controls motor mixing for multicopter component
157 : // @Values: 0:Plus, 1:X, 2:V, 3:H, 4:V-Tail, 5:A-Tail, 10:Y6B, 11:Y6F, 12:BetaFlightX, 13:DJIX, 14:ClockwiseX, 15:I, 16:MOTOR_FRAME_TYPE_NYT_PLUS, 17:MOTOR_FRAME_TYPE_NYT_X, 18: BetaFlightXReversed, 19: Y4
158 : // @User: Standard
159 : AP_GROUPINFO("FRAME_TYPE", 31, QuadPlane, frame_type, 1),
160 :
161 : // @Param: VFWD_GAIN
162 : // @DisplayName: Forward velocity hold gain
163 : // @Description: The use of this parameter is no longer recommended and has been superseded by a method that works in all VTOL modes with the exception of QAUTOTUNE which is controlled by the Q_FWD_THR_USE parameter. This Q_VFD_GAIN parameter controls use of the forward motor in VTOL modes that use the velocity controller. Set to 0 to disable this function. A value of 0.05 is a good place to start if you want to use the forward motor for position control. No forward motor will be used in QSTABILIZE or QHOVER modes. Use with QLOITER for position hold with the forward motor.
164 : // @Range: 0 0.5
165 : // @Increment: 0.01
166 : // @User: Standard
167 : AP_GROUPINFO("VFWD_GAIN", 32, QuadPlane, vel_forward.gain, 0),
168 :
169 : // 33 was used by WVANE_GAIN
170 :
171 : // 34 was used by WVANE_MINROLL
172 :
173 : // @Param: RTL_ALT
174 : // @DisplayName: QRTL return altitude
175 : // @Description: The altitude which QRTL mode heads to initially
176 : // @Units: m
177 : // @Range: 1 200
178 : // @Increment: 1
179 : // @User: Standard
180 : AP_GROUPINFO("RTL_ALT", 35, QuadPlane, qrtl_alt_m, 15),
181 :
182 : // @Param: RTL_MODE
183 : // @DisplayName: VTOL RTL mode
184 : // @Description: If this is set to 1 then an RTL will change to QRTL when within RTL_RADIUS meters of the RTL destination, VTOL approach: vehicle will RTL at RTL alt and circle with a radius of Q_FW_LND_APR_RAD down to Q_RTL_ALT and then transition into the wind and QRTL, see 'AUTO VTOL Landing', QRTL Always: do a QRTL instead of RTL
185 : // @Values: 0:Disabled,1:Enabled,2:VTOL approach,3:QRTL Always
186 : // @User: Standard
187 : AP_GROUPINFO("RTL_MODE", 36, QuadPlane, rtl_mode, 0),
188 :
189 : // 37: TILT_MASK
190 : // 38: TILT_RATE_UP
191 : // 39: TILT_MAX
192 :
193 : // @Param: GUIDED_MODE
194 : // @DisplayName: Enable VTOL in GUIDED mode
195 : // @Description: This enables use of VTOL in guided mode. When enabled the aircraft will switch to VTOL flight when the guided destination is reached and hover at the destination.
196 : // @Values: 0:Disabled,1:Enabled
197 : // @User: Standard
198 : AP_GROUPINFO("GUIDED_MODE", 40, QuadPlane, guided_mode, 0),
199 :
200 : // 41 was used by THR_MIN
201 :
202 : // @Param: ESC_CAL
203 : // @DisplayName: ESC Calibration
204 : // @Description: This is used to calibrate the throttle range of the VTOL motors. Please read https://ardupilot.club/plane/docs/quadplane-esc-calibration.html before using. This parameter is automatically set back to 0 on every boot. This parameter only takes effect in QSTABILIZE mode. When set to 1 the output of all motors will come directly from the throttle stick when armed, and will be zero when disarmed. When set to 2 the output of all motors will be maximum when armed and zero when disarmed. Make sure you remove all properllers before using.
205 : // @Values: 0:Disabled,1:ThrottleInput,2:FullInput
206 : // @User: Standard
207 : AP_GROUPINFO("ESC_CAL", 42, QuadPlane, esc_calibration, 0),
208 :
209 : // @Param: VFWD_ALT
210 : // @DisplayName: Forward velocity alt cutoff
211 : // @Description: Controls altitude to disable forward velocity assist when below this relative altitude. This is useful to keep the forward velocity propeller from hitting the ground. Rangefinder height data is incorporated when available.
212 : // @Units: m
213 : // @Range: 0 10
214 : // @Increment: 0.25
215 : // @User: Standard
216 : AP_GROUPINFO("VFWD_ALT", 43, QuadPlane, vel_forward_alt_cutoff_m, 0),
217 :
218 : // @Param: LAND_ICE_CUT
219 : // @DisplayName: Cut IC engine on landing
220 : // @Description: This controls stopping an internal combustion engine in the final landing stage of a VTOL. This is important for aircraft where the forward thrust engine may experience prop-strike if left running during landing. This requires the engine controls are enabled using the ICE_* parameters.
221 : // @Values: 0:Disabled,1:Enabled
222 : // @User: Standard
223 : AP_GROUPINFO("LAND_ICE_CUT", 44, QuadPlane, land_icengine_cut, 1),
224 :
225 : // @Param: ASSIST_ANGLE
226 : // @DisplayName: Quadplane assistance angle
227 : // @Description: This is the angular error in attitude beyond which the quadplane VTOL motors will provide stability assistance. This will only be used if Q_ASSIST_SPEED is also positive and non-zero. Assistance will be given if the attitude is outside the normal attitude limits by at least 5 degrees and the angular error in roll or pitch is greater than this angle for at least Q_ASSIST_DELAY seconds. Set to zero to disable angle assistance.
228 : // @Units: deg
229 : // @Range: 0 90
230 : // @Increment: 1
231 : // @User: Standard
232 : AP_GROUPINFO("ASSIST_ANGLE", 45, QuadPlane, assist.angle, 30),
233 :
234 : // @Param: ASSIST_OPTIONS
235 : // @DisplayName: Quadplane assistance options
236 : // @Description: Options for special QAssist features
237 : // @Bitmask: 0: Disable force fixed wing controller recovery
238 : // @Bitmask: 1: Disable quadplane spin recovery
239 : // @User: Standard
240 : AP_GROUPINFO("ASSIST_OPTIONS", 47, QuadPlane, assist.options, 0),
241 :
242 : // 47: TILT_TYPE // was AP_Int8, re-used by AP_Int16 ASSIST_OPTIONS
243 : // 48: TAILSIT_ANGLE
244 : // 61: TAILSIT_ANG_VT
245 : // 49: TILT_RATE_DN
246 : // 50: TAILSIT_INPUT
247 : // 51: TAILSIT_MASK
248 : // 52: TAILSIT_MASKCH
249 : // 53: TAILSIT_VFGAIN
250 : // 54: TAILSIT_VHGAIN
251 : // 56: TAILSIT_VHPOW
252 :
253 : // @Param: MAV_TYPE
254 : // @DisplayName: MAVLink type identifier
255 : // @Description: This controls the mavlink type given in HEARTBEAT messages. For some GCS types a particular setting will be needed for correct operation.
256 : // @Values: 0:AUTO,1:FIXED_WING,2:QUADROTOR,3:COAXIAL,4:HELICOPTER,7:AIRSHIP,8:FREE_BALLOON,9:ROCKET,10:GROUND_ROVER,11:SURFACE_BOAT,12:SUBMARINE,16:FLAPPING_WING,17:KITE,19:VTOL_DUOROTOR,20:VTOL_QUADROTOR,21:VTOL_TILTROTOR
257 : AP_GROUPINFO("MAV_TYPE", 57, QuadPlane, mav_type, 0),
258 :
259 : // @Param: OPTIONS
260 : // @DisplayName: quadplane options
261 : // @Description: See description for each bitmask bit description
262 : // @Bitmask: 0: Level Transition-keep wings within LEVEL_ROLL_LIMIT and only use forward motor(s) for climb during transition
263 : // @Bitmask: 1: Allow FW Takeoff-if bit is not set then NAV_TAKEOFF command on quadplanes will instead perform a NAV_VTOL takeoff
264 : // @Bitmask: 2: Allow FW Land-if bit is not set then NAV_LAND command on quadplanes will instead perform a NAV_VTOL_LAND
265 : // @Bitmask: 3: Vtol Takeoff Frame-command NAV_VTOL_TAKEOFF alt set by the command's reference frame not above current location
266 : // @Bitmask: 4: Always use FW spiral approach-always use Use a fixed wing spiral approach for VTOL landings
267 : // @Bitmask: 5: USE QRTL-instead of QLAND for rc failsafe when in VTOL modes
268 : // @Bitmask: 6: Use Governor-use ICE Idle Governor in MANUAL for forward motor
269 : // @Bitmask: 7: Force Qassist-on always
270 : // @Bitmask: 8: Mtrs_Only_Qassist-in tailsitters only uses VTOL motors and not flying surfaces for QASSIST
271 : // @Bitmask: 10: Disarmed Yaw Tilt-enable motor tilt for yaw when disarmed
272 : // @Bitmask: 11: Delay Spoolup-delay VTOL spoolup for 2 seconds after arming
273 : // @Bitmask: 12: Disable speed based Qassist when using synthetic airspeed estimates
274 : // @Bitmask: 13: Disable Ground Effect Compensation-on baro altitude reports
275 : // @Bitmask: 14: Ignore forward flight angle limits-in Qmodes and use Q_A_ANGLE_MAX exclusively
276 : // @Bitmask: 15: ThrLandControl-enable throttle stick control of landing rate
277 : // @Bitmask: 16: DisableApproach-disable use of approach and airbrake stages in VTOL landing
278 : // @Bitmask: 17: EnableLandResposition-enable pilot controlled repositioning in AUTO land.Descent will pause while repositioning
279 : // @Bitmask: 18: ARMVTOL-arm only in VTOL modes (or AUTO mode when current nav cmd is VTOL Takeoff)
280 : // @Bitmask: 19: CompleteTransition-to fixed wing if Q_TRANS_FAIL timer times out instead of QLAND
281 : // @Bitmask: 20: Force RTL mode-forces RTL mode on rc failsafe in VTOL modes overriding bit 5(USE_QRTL)
282 : // @Bitmask: 21: Tilt rotor-tilt motors up when disarmed in FW modes (except manual) to prevent ground strikes.
283 : // @Bitmask: 22: Scale FF by the ratio of VTOL to plane angle P gains in Position 1 phase of transition into VTOL flight as well as reducing VTOL angle P based on airspeed.
284 : AP_GROUPINFO("OPTIONS", 58, QuadPlane, options, 0),
285 :
286 : AP_SUBGROUPEXTENSION("",59, QuadPlane, var_info2),
287 :
288 : // 60 is used above for VELZ_MAX_DN
289 : // 61 was used above for TAILSIT_ANG_VT
290 :
291 : AP_GROUPEND
292 : };
293 :
294 : // second table of user settable parameters for quadplanes, this
295 : // allows us to go beyond the 64 parameter limit
296 : const AP_Param::GroupInfo QuadPlane::var_info2[] = {
297 : // @Param: TRANS_DECEL
298 : // @DisplayName: Transition deceleration
299 : // @Description: This is deceleration rate that will be used in calculating the stopping distance when transitioning from fixed wing flight to multicopter flight.
300 : // @Units: m/s/s
301 : // @Increment: 0.1
302 : // @Range: 0.2 5
303 : // @User: Standard
304 : AP_GROUPINFO("TRANS_DECEL", 1, QuadPlane, transition_decel_mss, 2.0),
305 :
306 : // @Group: LOIT_
307 : // @Path: ../libraries/AC_WPNav/AC_Loiter.cpp
308 : AP_SUBGROUPPTR(loiter_nav, "LOIT_", 2, QuadPlane, AC_Loiter),
309 :
310 : // 3: TAILSIT_GSCMAX
311 :
312 : // @Param: TRIM_PITCH
313 : // @DisplayName: Quadplane AHRS trim pitch
314 : // @Description: This sets the compensation for the pitch angle trim difference between calibrated AHRS level and vertical flight pitch. NOTE! this is relative to calibrated AHRS trim, not forward flight trim which includes PTCH_TRIM_DEG. For tailsitters, this is relative to a baseline of 90 degrees in AHRS.
315 : // @Units: deg
316 : // @Range: -10 +10
317 : // @Increment: 0.1
318 : // @User: Advanced
319 : // @RebootRequired: True
320 : AP_GROUPINFO("TRIM_PITCH", 4, QuadPlane, ahrs_trim_pitch, 0),
321 :
322 : // 5: TAILSIT_RLL_MX
323 :
324 : #if QAUTOTUNE_ENABLED
325 : // @Group: AUTOTUNE_
326 : // @Path: ../libraries/AC_AutoTune/AC_AutoTune_Multi.cpp
327 : AP_SUBGROUPINFO(qautotune, "AUTOTUNE_", 6, QuadPlane, QAutoTune),
328 : #endif
329 :
330 : // @Param: FW_LND_APR_RAD
331 : // @DisplayName: Quadplane fixed wing landing approach radius
332 : // @Description: This provides the radius used, when using a fixed wing landing approach. If set to 0 then the WP_LOITER_RAD will be selected.
333 : // @Units: m
334 : // @Range: 0 200
335 : // @Increment: 5
336 : // @User: Advanced
337 : AP_GROUPINFO("FW_LND_APR_RAD", 7, QuadPlane, fw_land_approach_radius_m, 0),
338 :
339 : // @Param: TRANS_FAIL
340 : // @DisplayName: Quadplane transition failure time
341 : // @Description: Maximum time allowed for forward transitions, exceeding this time will cancel the transition and the aircraft will immediately change to the mode set by Q_TRANS_FAIL_ACT or finish the transition depending on Q_OPTIONS bit 19. 0 for no limit.
342 : // @Units: s
343 : // @Range: 0 20
344 : // @Increment: 1
345 : // @User: Advanced
346 : AP_GROUPINFO("TRANS_FAIL", 8, QuadPlane, transition_failure.timeout, 0),
347 :
348 : // 9: TAILSIT_MOTMX
349 :
350 : // @Param: THROTTLE_EXPO
351 : // @DisplayName: Throttle expo strength
352 : // @Description: Amount of curvature in throttle curve: 0 is linear, 1 is cubic
353 : // @Range: 0 1
354 : // @Increment: 0.1
355 : // @User: Advanced
356 : AP_GROUPINFO("THROTTLE_EXPO", 10, QuadPlane, throttle_expo, 0.2),
357 :
358 : // @Param: ACRO_RLL_RATE
359 : // @DisplayName: QACRO mode roll rate
360 : // @Description: The maximum roll rate at full stick deflection in QACRO mode
361 : // @Units: deg/s
362 : // @Range: 10 500
363 : // @Increment: 1
364 : // @User: Standard
365 : AP_GROUPINFO("ACRO_RLL_RATE", 11, QuadPlane, acro_roll_rate, 360),
366 :
367 : // @Param: ACRO_PIT_RATE
368 : // @DisplayName: QACRO mode pitch rate
369 : // @Description: The maximum pitch rate at full stick deflection in QACRO mode
370 : // @Units: deg/s
371 : // @Range: 10 500
372 : // @Increment: 1
373 : // @User: Standard
374 : AP_GROUPINFO("ACRO_PIT_RATE", 12, QuadPlane, acro_pitch_rate, 180),
375 :
376 : // @Param: ACRO_YAW_RATE
377 : // @DisplayName: QACRO mode yaw rate
378 : // @Description: The maximum yaw rate at full stick deflection in QACRO mode
379 : // @Units: deg/s
380 : // @Range: 10 500
381 : // @Increment: 1
382 : // @User: Standard
383 : AP_GROUPINFO("ACRO_YAW_RATE", 13, QuadPlane, acro_yaw_rate, 90),
384 :
385 : // @Param: TKOFF_FAIL_SCL
386 : // @DisplayName: Takeoff time failure scalar
387 : // @Description: Scalar for how long past the expected takeoff time a takeoff should be considered as failed and the vehicle will switch to QLAND. If set to 0 there is no limit on takeoff time.
388 : // @Range: 1.1 5.0
389 : // @Increment: 5.1
390 : // @User: Advanced
391 : AP_GROUPINFO("TKOFF_FAIL_SCL", 14, QuadPlane, takeoff_failure_scalar, 0),
392 :
393 : // @Param: TKOFF_ARSP_LIM
394 : // @DisplayName: Takeoff airspeed limit
395 : // @Description: Airspeed limit during takeoff. If the airspeed exceeds this level the vehicle will switch to QLAND. This is useful for ensuring that you don't takeoff into excessively strong wind. If set to 0 there is no limit on airspeed during takeoff.
396 : // @Units: m/s
397 : // @Range: 0 20
398 : // @Increment: 1
399 : // @User: Advanced
400 : AP_GROUPINFO("TKOFF_ARSP_LIM", 15, QuadPlane, maximum_takeoff_airspeed_ms, 0),
401 :
402 : // @Param: ASSIST_ALT
403 : // @DisplayName: Quadplane assistance altitude
404 : // @Description: This is the altitude below which quadplane assistance will be triggered. This acts the same way as Q_ASSIST_ANGLE and Q_ASSIST_SPEED, but triggers if the aircraft drops below the given altitude while the VTOL motors are not running. A value of zero disables this feature. The altitude is calculated as being above ground level. The height above ground is given from a Lidar used if available and RNGFND_LANDING=1. Otherwise it comes from terrain data if TERRAIN_FOLLOW=1 and comes from height above home otherwise.
405 : // @Units: m
406 : // @Range: 0 120
407 : // @Increment: 1
408 : // @User: Standard
409 : AP_GROUPINFO("ASSIST_ALT", 16, QuadPlane, assist.alt, 0),
410 :
411 : // 17: TAILSIT_GSCMSK
412 : // 18: TAILSIT_GSCMIN
413 :
414 : // @Param: ASSIST_DELAY
415 : // @DisplayName: Quadplane assistance delay
416 : // @Description: This is delay between the assistance thresholds being met and the assistance starting.
417 : // @Units: s
418 : // @Range: 0 2
419 : // @Increment: 0.1
420 : // @User: Standard
421 : AP_GROUPINFO("ASSIST_DELAY", 19, QuadPlane, assist.delay, 0.5),
422 :
423 : // @Param: FWD_MANTHR_MAX
424 : // @DisplayName: VTOL manual forward throttle max percent
425 : // @Description: Maximum value for manual forward throttle; used with RC option FWD_THR (209)
426 : // @Range: 0 100
427 : AP_GROUPINFO("FWD_MANTHR_MAX", 20, QuadPlane, fwd_thr_max, 0),
428 :
429 : // 21: TAILSIT_DSKLD
430 : // 22: TILT_FIX_ANGLE
431 : // 23: TILT_FIX_GAIN
432 : // 24: TAILSIT_RAT_FW
433 : // 25: TAILSIT_RAT_VT
434 :
435 : // @Group: TAILSIT_
436 : // @Path: tailsitter.cpp
437 : AP_SUBGROUPINFO(tailsitter, "TAILSIT_", 26, QuadPlane, Tailsitter),
438 :
439 : // @Group: TILT_
440 : // @Path: tiltrotor.cpp
441 : AP_SUBGROUPINFO(tiltrotor, "TILT_", 27, QuadPlane, Tiltrotor),
442 :
443 : // @Param: BACKTRANS_MS
444 : // @DisplayName: SLT and Tiltrotor back transition pitch limit duration
445 : // @Description: Pitch angle will increase from 0 to angle max over this duration when switching into VTOL flight in a position control mode. 0 Disables.
446 : // @Units: ms
447 : // @Range: 0 10000
448 : AP_GROUPINFO("BACKTRANS_MS", 28, QuadPlane, back_trans_pitch_limit_ms, 3000),
449 :
450 : // @Param: TRANS_FAIL_ACT
451 : // @DisplayName: Quadplane transition failure action
452 : // @Description: This sets the mode that is changed to when Q_TRANS_FAIL time elapses, if set. See also Q_OPTIONS bit 19: CompleteTransition if Q_TRANS_FAIL
453 : // @Values: -1:Warn only, 0:QLand, 1:QRTL
454 : AP_GROUPINFO("TRANS_FAIL_ACT", 29, QuadPlane, transition_failure.action, 0),
455 :
456 : // @Group: WVANE_
457 : // @Path: ../libraries/AC_AttitudeControl/AC_WeatherVane.cpp
458 : AP_SUBGROUPPTR(weathervane, "WVANE_", 30, QuadPlane, AC_WeatherVane),
459 :
460 : // @Param: LAND_ALTCHG
461 : // @DisplayName: Land detection altitude change threshold
462 : // @Description: The maximum altitude change allowed during land detection. You can raise this value if you find that landing detection takes a long time to complete. It is the maximum change in altitude over a period of 4 seconds for landing to be detected
463 : // @Units: m
464 : // @Range: 0.1 0.6
465 : // @Increment: 0.05
466 : // @User: Standard
467 : AP_GROUPINFO("LAND_ALTCHG", 31, QuadPlane, landing_detect.detect_alt_change_m, 0.2),
468 :
469 : // @Param: NAVALT_MIN
470 : // @DisplayName: Minimum navigation altitude
471 : // @Description: This is the altitude in meters above which navigation begins in auto takeoff. Below this altitude the target roll and pitch will be zero. A value of zero disables the feature
472 : // @Units: m
473 : // @Range: 0 5
474 : // @User: Advanced
475 : AP_GROUPINFO("NAVALT_MIN", 32, QuadPlane, takeoff_navalt_min_m, 0),
476 :
477 : // @Param: PLT_Y_RATE
478 : // @DisplayName: Pilot controlled yaw rate
479 : // @Description: Pilot controlled yaw rate max. Used in all pilot controlled modes except QAcro
480 : // @Units: deg/s
481 : // @Range: 1 360
482 : // @User: Standard
483 :
484 : // @Param: PLT_Y_EXPO
485 : // @DisplayName: Pilot controlled yaw expo
486 : // @Description: Pilot controlled yaw expo to allow faster rotation when stick at edges
487 : // @Values: 0:Disabled,0.1:Very Low,0.2:Low,0.3:Medium,0.4:High,0.5:Very High
488 : // @Range: -0.5 1.0
489 : // @User: Advanced
490 :
491 : // @Param: PLT_Y_RATE_TC
492 : // @DisplayName: Pilot yaw rate control input time constant
493 : // @Description: Pilot yaw rate control input time constant. Low numbers lead to sharper response, higher numbers to softer response.
494 : // @Units: s
495 : // @Range: 0 1
496 : // @Increment: 0.01
497 : // @Values: 0.5:Very Soft, 0.2:Soft, 0.15:Medium, 0.1:Crisp, 0.05:Very Crisp
498 : // @User: Standard
499 : AP_SUBGROUPINFO(command_model_pilot, "PLT_Y_", 33, QuadPlane, AC_CommandModel),
500 :
501 : // @Param: RTL_ALT_MIN
502 : // @DisplayName: QRTL minimum altitude
503 : // @Description: If VTOL motors are active QRTL mode will VTOL climb to at least this altitude before returning home. If outside 150% the larger of WP_LOITER_RAD and RTL_RADIUS the vehicle will VTOL climb to Q_RTL_ALT. This parameter has no effect if the vehicle is in forward flight. Should be between Q_LAND_FINAL_ALT and Q_RTL_ALT
504 : // @Units: m
505 : // @Range: 1 200
506 : // @Increment: 1
507 : // @User: Standard
508 : AP_GROUPINFO("RTL_ALT_MIN", 34, QuadPlane, qrtl_alt_min_m, 10),
509 :
510 : // @Param: FWD_THR_GAIN
511 : // @DisplayName: Q mode fwd throttle gain
512 : // @Description: This parameter sets the gain from forward accel/tilt to forward throttle in certain Q modes. The Q modes this feature operates in is controlled by the Q_FWD_THR_USE parameter. Vehicles using separate forward thrust motors, eg quadplanes, should set this parameter to (all up weight) / (maximum combined thrust of forward motors) with a value of 2 being typical. Vehicles that tilt lifting rotors to provide forward thrust should set this parameter to (all up weight) / (weight lifted by tilting rotors) which for most aircraft can be approximated as (total number of lifting rotors) / (number of lifting rotors that tilt). When using this method of forward throttle control, the forward tilt angle limit is controlled by the Q_FWD_PIT_LIM parameter.
513 : // @Range: 0.0 5.0
514 : // @Increment: 0.1
515 : // @User: Standard
516 : AP_GROUPINFO("FWD_THR_GAIN", 35, QuadPlane, q_fwd_thr_gain, 2.0f),
517 :
518 : // @Param: FWD_PIT_LIM
519 : // @DisplayName: Q mode forward pitch limit
520 : // @Description: When forward throttle is being controlled by the Q_FWD_THR_GAIN parameter in Q modes, the vehicle forward (nose down) pitch rotation will be limited to the value specified by this parameter and the any additional forward acceleration required will be produced by use of the forward thrust motor(s) or tilting of moveable rotors. Larger values allow the vehicle to pitch more nose down. Set initially to the amount of nose down pitch required to remove wing lift.
521 : // @Units: deg
522 : // @Range: 0.0 5.0
523 : // @Increment: 0.1
524 : // @User: Standard
525 : AP_GROUPINFO("FWD_PIT_LIM", 36, QuadPlane, q_fwd_pitch_lim, 3.0f),
526 :
527 : // @Param: FWD_THR_USE
528 : // @DisplayName: Q mode forward throttle use
529 : // @Description: This parameter determines when the feature that uses forward throttle instead of forward tilt is used. The amount of forward throttle is controlled by the Q_FWD_THR_GAIN parameter. The maximum amount of forward pitch allowed is controlled by the Q_FWD_PIT_LIM parameter. Q_FWD_THR_USE = 0 disables the feature. Q_FWD_THR_USE = 1 enables the feature in all position controlled modes such as QLOITER, QLAND, QRTL and VTOL TAKEOFF. Q_FWD_THR_USE = 2 enables the feature in all Q modes except QAUTOTUNE and QACRO. When enabling the feature, the legacy method of controlling forward throttle use via velocity controller error should be disabled by setting Q_VFWD_GAIN to 0. Do not use this feature with tailsitters.
530 : // @Values: 0:Off,1:On in all position controlled Q modes,2:On in all Q modes except QAUTOTUNE and QACRO
531 : // @User: Standard
532 : AP_GROUPINFO("FWD_THR_USE", 37, QuadPlane, q_fwd_thr_use, uint8_t(FwdThrUse::OFF)),
533 :
534 : // @Param: BCK_PIT_LIM
535 : // @DisplayName: Q mode rearward pitch limit
536 : // @Description: This sets the maximum number of degrees of back or pitch up in Q modes when the airspeed is at AIRSPEED_MIN, and is used to prevent excessive sutructural loads when pitching up decelerate. If airspeed is above or below AIRSPEED_MIN, the pitch up/back will be adjusted according to the formula pitch_limit = Q_BCK_PIT_LIM * (AIRSPEED_MIN / IAS)^2. The backwards/up pitch limit controlled by this parameter is in addition to limiting applied by PTCH_LIM_MAX_DEG and Q_A_ANGLE_MAX. The BCK_PIT_LIM limit is only applied when Q_FWD_THR_USE is set to 1 or 2 and the vehicle is flying in a mode that uses forward throttle instead of forward tilt to generate forward speed. Set to a non positive value 0 to deactivate this limit.
537 : // @Units: deg
538 : // @Range: 0.0 15.0
539 : // @Increment: 0.1
540 : // @User: Standard
541 : AP_GROUPINFO("BCK_PIT_LIM", 38, QuadPlane, q_bck_pitch_lim, 10.0f),
542 :
543 : // @Param: APPROACH_DIST
544 : // @DisplayName: Q mode approach distance
545 : // @Description: The minimum distance from the destination to use the fixed wing airbrake and approach code for landing approach. This is useful if you don't want the fixed wing approach logic to be used when you are close to the destination. Set to zero to always use fixed wing approach.
546 : // @Units: m
547 : // @Range: 0.0 1000
548 : // @Increment: 1
549 : // @User: Standard
550 : AP_GROUPINFO("APPROACH_DIST", 39, QuadPlane, approach_distance_m, 0),
551 :
552 : #if HAL_WITH_ESC_TELEM
553 : // @Param: TKOFF_RPM_MIN
554 : // @DisplayName: Auto Takeoff Check RPM minimum
555 : // @Description: Auto takeoff (in Auto or Guided) is not permitted until motors report at least this RPM. Set to zero to disable check (which also disables Q_TKOFF_RPM_MAX).
556 : // @Range: 0 10000
557 : // @User: Standard
558 : AP_GROUPINFO("TKOFF_RPM_MIN", 40, QuadPlane, takeoff_rpm_min, 0),
559 :
560 : // @Param: TKOFF_RPM_MAX
561 : // @DisplayName: Auto Takeoff Check RPM maximum
562 : // @Description: Auto takeoff (in Auto or Guided) is not permitted until motors report no more than this RPM. Set to zero to disable check. Q_TKOFF_RPM_MIN must also be set to a non-zero value to enable this check.
563 : // @Range: 0 10000
564 : // @User: Standard
565 : AP_GROUPINFO("TKOFF_RPM_MAX", 41, QuadPlane, takeoff_rpm_max, 0),
566 : #endif
567 :
568 : // @Param: THRST_LOSS_OPT
569 : // @DisplayName: VTOL thrust loss detection options
570 : // @Description: Configuration for thrust loss detection detection, this results in "Potential VTOL Thrust Loss" warnings. This detection also allows the motor mixer to ignore the failed motor allowing better use of the remaining motors.
571 : // @Bitmask: 0: Disable thrust loss detection.
572 : // @Bitmask: 1: Disable thrust loss detection in transtions and fixed wing modes. Thrust loss detection will only run in VTOL modes.
573 : AP_GROUPINFO("THRST_LOSS_OPT", 42, QuadPlane, thrust_loss.options, 0),
574 :
575 : AP_GROUPEND
576 : };
577 :
578 : /*
579 : defaults for all quadplanes
580 : */
581 : static const struct AP_Param::defaults_table_struct defaults_table[] = {
582 : { "Q_A_RAT_RLL_P", 0.25 },
583 : { "Q_A_RAT_RLL_I", 0.25 },
584 : { "Q_A_RAT_RLL_FLTD", 10.0 },
585 : { "Q_A_RAT_RLL_SMAX", 50.0 },
586 : { "Q_A_RAT_PIT_P", 0.25 },
587 : { "Q_A_RAT_PIT_I", 0.25 },
588 : { "Q_A_RAT_PIT_FLTD", 10.0 },
589 : { "Q_A_RAT_PIT_SMAX", 50.0 },
590 : { "Q_A_RAT_YAW_SMAX", 50.0 },
591 : { "Q_A_RATE_R_MAX", 75.0 },
592 : { "Q_A_RATE_P_MAX", 75.0 },
593 : { "Q_A_RATE_Y_MAX", 75.0 },
594 : { "Q_M_SPOOL_TIME", 0.25 },
595 : { "Q_LOIT_ANG_MAX", 15.0 },
596 : { "Q_LOIT_ACC_MAX_M", 2.50 },
597 : { "Q_LOIT_BRK_ACC_M", 0.5 },
598 : { "Q_LOIT_BRK_JRK_M", 2.5 },
599 : { "Q_LOIT_SPEED_MS", 5.0 },
600 : { "Q_WP_SPD", 5.0 },
601 : { "Q_WP_ACC", 1.0 },
602 : { "Q_P_NE_JERK", 2 },
603 : // lower rotational accel limits
604 : { "Q_A_ACC_R_MAX", 400 },
605 : { "Q_A_ACC_P_MAX", 400 },
606 : { "Q_A_ACC_Y_MAX", 100 },
607 : };
608 :
609 692 : QuadPlane::QuadPlane(AP_AHRS &_ahrs) :
610 692 : ahrs(_ahrs)
611 : {
612 692 : AP_Param::setup_object_defaults(this, var_info);
613 692 : AP_Param::setup_object_defaults(this, var_info2);
614 :
615 692 : if (_singleton != nullptr) {
616 0 : AP_HAL::panic("Can only be one Quadplane");
617 : }
618 692 : _singleton = this;
619 692 : }
620 :
621 :
622 : // setup default motors for the frame class
623 191 : void QuadPlane::setup_default_channels(uint8_t num_motors)
624 : {
625 961 : for (uint8_t i=0; i<num_motors; i++) {
626 770 : SRV_Channels::set_aux_channel_default(SRV_Channels::get_motor_function(i), CH_5+i);
627 : }
628 191 : }
629 :
630 :
631 10606814 : bool QuadPlane::setup(void)
632 : {
633 10606814 : if (initialised) {
634 8732932 : return true;
635 : }
636 1873882 : if (!enable || hal.util->get_soft_armed()) {
637 1873681 : return false;
638 : }
639 :
640 201 : if (hal.util->available_memory() <
641 : 4096 + sizeof(*motors) + sizeof(*attitude_control) + sizeof(*pos_control) + sizeof(*wp_nav) + sizeof(*ahrs_view) + sizeof(*loiter_nav) + sizeof(*weathervane)) {
642 0 : AP_BoardConfig::config_error("Not enough memory for quadplane");
643 : }
644 :
645 : /*
646 : dynamically allocate the key objects for quadplane. This ensures
647 : that the objects don't affect the vehicle unless enabled and
648 : also saves memory when not in use
649 : */
650 201 : switch ((AP_Motors::motor_frame_class)frame_class) {
651 189 : case AP_Motors::MOTOR_FRAME_QUAD:
652 189 : setup_default_channels(4);
653 189 : break;
654 0 : case AP_Motors::MOTOR_FRAME_HEXA:
655 0 : setup_default_channels(6);
656 0 : break;
657 0 : case AP_Motors::MOTOR_FRAME_OCTA:
658 : case AP_Motors::MOTOR_FRAME_OCTAQUAD:
659 0 : setup_default_channels(8);
660 0 : break;
661 2 : case AP_Motors::MOTOR_FRAME_Y6:
662 2 : setup_default_channels(7);
663 2 : break;
664 0 : case AP_Motors::MOTOR_FRAME_DECA:
665 0 : setup_default_channels(10);
666 0 : break;
667 9 : case AP_Motors::MOTOR_FRAME_TRI:
668 9 : SRV_Channels::set_default_function(CH_5, SRV_Channel::k_motor1);
669 9 : SRV_Channels::set_default_function(CH_6, SRV_Channel::k_motor2);
670 9 : SRV_Channels::set_default_function(CH_8, SRV_Channel::k_motor4);
671 9 : SRV_Channels::set_default_function(CH_11, SRV_Channel::k_motor7);
672 9 : AP_Param::set_frame_type_flags(AP_PARAM_FRAME_TRICOPTER);
673 9 : break;
674 1 : case AP_Motors::MOTOR_FRAME_TAILSITTER:
675 : case AP_Motors::MOTOR_FRAME_SCRIPTING_MATRIX:
676 : case AP_Motors::MOTOR_FRAME_DYNAMIC_SCRIPTING_MATRIX:
677 1 : break;
678 0 : default:
679 0 : AP_BoardConfig::config_error("Unsupported Q_FRAME_CLASS %u", (unsigned int)(frame_class.get()));
680 : }
681 :
682 : // Make sure not both a tailsiter and tiltrotor
683 201 : if ((tailsitter.enable > 0) && (tiltrotor.enable > 0)) {
684 0 : AP_BoardConfig::config_error("set TAILSIT_ENABLE 0 or TILT_ENABLE 0");
685 : }
686 :
687 201 : switch ((AP_Motors::motor_frame_class)frame_class) {
688 : #if AP_MOTORS_TRI_ENABLED
689 9 : case AP_Motors::MOTOR_FRAME_TRI:
690 9 : motors = NEW_NOTHROW AP_MotorsTri(rc_speed);
691 9 : motors_var_info = AP_MotorsTri::var_info;
692 9 : break;
693 : #endif // AP_MOTORS_TRI_ENABLED
694 1 : case AP_Motors::MOTOR_FRAME_TAILSITTER:
695 : // this is a duo-motor tailsitter
696 1 : tailsitter.tailsitter_motors = NEW_NOTHROW AP_MotorsTailsitter(rc_speed);
697 1 : motors = tailsitter.tailsitter_motors;
698 1 : motors_var_info = AP_MotorsTailsitter::var_info;
699 1 : break;
700 0 : case AP_Motors::MOTOR_FRAME_DYNAMIC_SCRIPTING_MATRIX:
701 : #if AP_SCRIPTING_ENABLED
702 0 : motors = NEW_NOTHROW AP_MotorsMatrix_Scripting_Dynamic(plane.scheduler.get_loop_rate_hz());
703 0 : motors_var_info = AP_MotorsMatrix_Scripting_Dynamic::var_info;
704 : #endif // AP_SCRIPTING_ENABLED
705 0 : break;
706 191 : default:
707 191 : motors = NEW_NOTHROW AP_MotorsMatrix(rc_speed);
708 191 : motors_var_info = AP_MotorsMatrix::var_info;
709 191 : break;
710 : }
711 :
712 201 : if (!motors) {
713 0 : AP_BoardConfig::allocation_error("motors");
714 : }
715 :
716 201 : AP_Param::load_object_from_eeprom(motors, motors_var_info);
717 :
718 : // create the attitude view used by the VTOL code
719 201 : ahrs_view = ahrs.create_view((tailsitter.enable > 0) ? ROTATION_PITCH_90 : ROTATION_NONE, ahrs_trim_pitch);
720 201 : if (ahrs_view == nullptr) {
721 0 : AP_BoardConfig::allocation_error("ahrs_view");
722 : }
723 :
724 201 : attitude_control = NEW_NOTHROW AC_AttitudeControl_TS(*ahrs_view, *motors);
725 201 : if (!attitude_control) {
726 0 : AP_BoardConfig::allocation_error("attitude_control");
727 : }
728 :
729 201 : AP_Param::load_object_from_eeprom(attitude_control, attitude_control->var_info);
730 201 : pos_control = NEW_NOTHROW AC_PosControl(*ahrs_view, *motors, *attitude_control);
731 201 : if (!pos_control) {
732 0 : AP_BoardConfig::allocation_error("pos_control");
733 : }
734 201 : AP_Param::load_object_from_eeprom(pos_control, pos_control->var_info);
735 201 : wp_nav = NEW_NOTHROW AC_WPNav(*ahrs_view, *pos_control, *attitude_control);
736 201 : if (!wp_nav) {
737 0 : AP_BoardConfig::allocation_error("wp_nav");
738 : }
739 201 : AP_Param::load_object_from_eeprom(wp_nav, wp_nav->var_info);
740 :
741 201 : loiter_nav = NEW_NOTHROW AC_Loiter(*ahrs_view, *pos_control, *attitude_control);
742 201 : if (!loiter_nav) {
743 0 : AP_BoardConfig::allocation_error("loiter_nav");
744 : }
745 201 : AP_Param::load_object_from_eeprom(loiter_nav, loiter_nav->var_info);
746 :
747 201 : weathervane = NEW_NOTHROW AC_WeatherVane();
748 201 : if (!weathervane) {
749 0 : AP_BoardConfig::allocation_error("weathervane");
750 : }
751 201 : AP_Param::load_object_from_eeprom(weathervane, weathervane->var_info);
752 :
753 201 : motors->init(frame_class, frame_type);
754 201 : motors->update_throttle_range();
755 201 : motors->set_update_rate(rc_speed);
756 201 : attitude_control->parameter_sanity_check();
757 :
758 : // setup the trim of any motors used by AP_Motors so I/O board
759 : // failsafe will disable motors
760 201 : uint32_t mask = plane.quadplane.motors->get_motor_mask();
761 201 : hal.rcout->set_failsafe_pwm(mask, plane.quadplane.motors->get_pwm_output_min());
762 :
763 : // default QAssist state as set with Q_OPTIONS
764 201 : if (option_is_set(QuadPlane::Option::Q_ASSIST_FORCE_ENABLE)) {
765 0 : assist.set_state(VTOL_Assist::STATE::FORCE_ENABLED);
766 : }
767 :
768 201 : setup_defaults();
769 :
770 : // PARAMETER_CONVERSION - Added: Jan-2024 for ArduPlane-4.5
771 201 : land_final_speed_ms.convert_centi_parameter(AP_PARAM_INT16);
772 201 : pilot_speed_z_max_up_ms.convert_centi_parameter(AP_PARAM_INT16);
773 201 : pilot_speed_z_max_dn_ms.convert_centi_parameter(AP_PARAM_INT16);
774 201 : pilot_accel_z_mss.convert_centi_parameter(AP_PARAM_INT16);
775 :
776 : // upgrade attitude controller parameters
777 201 : attitude_control->convert_parameters();
778 :
779 : // upgrade position controller parameters added Dec 2025
780 201 : pos_control->convert_parameters();
781 :
782 : // upgrade waypoint navigation parameters
783 201 : wp_nav->convert_parameters();
784 :
785 : // upgrade loiter navigation parameters
786 201 : loiter_nav->convert_parameters();
787 :
788 : // Provisionally assign the SLT thrust type.
789 : // It will be overwritten by tailsitter or tiltorotor setups.
790 201 : thrust_type = ThrustType::SLT;
791 :
792 201 : tailsitter.setup();
793 :
794 201 : tiltrotor.setup();
795 :
796 201 : if (!transition) {
797 182 : transition = NEW_NOTHROW SLT_Transition(*this, motors);
798 : }
799 201 : if (!transition) {
800 0 : AP_BoardConfig::allocation_error("transition");
801 : }
802 :
803 : // init wp_nav variables after defaults are setup
804 201 : wp_nav->wp_and_spline_init_m();
805 :
806 201 : transition->force_transition_complete();
807 :
808 : // param count will have changed
809 201 : AP_Param::invalidate_count();
810 :
811 : char frame_and_type_string[30];
812 201 : motors->get_frame_and_type_string(frame_and_type_string, ARRAY_SIZE(frame_and_type_string));
813 201 : gcs().send_text(MAV_SEVERITY_INFO, "QuadPlane initialised, %s", frame_and_type_string);
814 201 : initialised = true;
815 201 : return true;
816 : }
817 :
818 : /*
819 : setup default parameters from defaults_table
820 : */
821 201 : void QuadPlane::setup_defaults(void)
822 : {
823 201 : AP_Param::set_defaults_from_table(defaults_table, ARRAY_SIZE(defaults_table));
824 :
825 : // reset ESC calibration
826 201 : if (esc_calibration != 0) {
827 0 : esc_calibration.set_and_save(0);
828 : }
829 : // Quadplanes need the same level of GPS error checking as Copters do, Plane is more relaxed
830 201 : AP_Param::set_default_by_name("EK2_CHECK_SCALE",100);
831 201 : AP_Param::set_default_by_name("EK3_CHECK_SCALE",100);
832 :
833 201 : }
834 :
835 : // run ESC calibration
836 0 : void QuadPlane::run_esc_calibration(void)
837 : {
838 0 : if (!motors->armed()) {
839 0 : motors->set_throttle_passthrough_for_esc_calibration(0);
840 0 : AP_Notify::flags.esc_calibration = false;
841 0 : return;
842 : }
843 0 : if (!AP_Notify::flags.esc_calibration) {
844 0 : gcs().send_text(MAV_SEVERITY_INFO, "Starting ESC calibration");
845 : }
846 0 : AP_Notify::flags.esc_calibration = true;
847 0 : switch (esc_calibration) {
848 0 : case 1:
849 : // throttle based calibration
850 0 : motors->set_throttle_passthrough_for_esc_calibration(plane.get_throttle_input() * 0.01f);
851 0 : break;
852 0 : case 2:
853 : // full range calibration
854 0 : motors->set_throttle_passthrough_for_esc_calibration(1);
855 0 : break;
856 : }
857 : }
858 :
859 : /*
860 : ask the multicopter attitude control to match the roll and pitch rates being demanded by the
861 : fixed wing controller if not in a pure VTOL mode
862 : */
863 588128 : void QuadPlane::multicopter_attitude_rate_update(float yaw_rate_cds)
864 : {
865 588128 : bool use_multicopter_control = in_vtol_mode() && !tailsitter.in_vtol_transition() && !force_fw_control_recovery;
866 588128 : bool use_yaw_target = false;
867 :
868 588128 : float yaw_target_cd = 0.0;
869 611189 : if (!use_multicopter_control && transition->update_yaw_target(yaw_target_cd) &&
870 23061 : !force_fw_control_recovery) {
871 23061 : use_multicopter_control = true;
872 23061 : use_yaw_target = true;
873 : }
874 :
875 : // normal control modes for VTOL and FW flight
876 : // tailsitter in transition to VTOL flight is not really in a VTOL mode yet
877 588128 : if (use_multicopter_control) {
878 :
879 : // Pilot input, use yaw rate time constant
880 174078 : set_pilot_yaw_rate_time_constant();
881 :
882 : // tailsitter-only body-frame roll control options
883 : // Angle mode attitude control for pitch and body-frame roll, rate control for euler yaw.
884 174078 : if (tailsitter.enabled() &&
885 0 : (tailsitter.input_type & Tailsitter::input::TAILSITTER_INPUT_BF_ROLL)) {
886 :
887 0 : if (!(tailsitter.input_type & Tailsitter::input::TAILSITTER_INPUT_PLANE)) {
888 : // In multicopter input mode, the roll and yaw stick axes are independent of pitch
889 0 : attitude_control->input_euler_rate_yaw_euler_angle_pitch_bf_roll_cd(false,
890 0 : plane.nav_roll_cd,
891 0 : plane.nav_pitch_cd,
892 : yaw_rate_cds);
893 0 : return;
894 : } else {
895 : // In plane input mode, the roll and yaw sticks are swapped
896 : // and their effective axes rotate from yaw to roll and vice versa
897 : // as pitch goes from zero to 90.
898 : // So it is necessary to also rotate their scaling.
899 :
900 : // Get the roll angle and yaw rate limits
901 0 : int16_t roll_limit = attitude_control->lean_angle_max_cd();
902 : // separate limit for tailsitter roll, if set
903 0 : if (tailsitter.max_roll_angle > 0) {
904 0 : roll_limit = tailsitter.max_roll_angle * 100.0f;
905 : }
906 : // Prevent a divide by zero
907 0 : const float yaw_rate_max = command_model_pilot.get_rate();
908 0 : float yaw_rate_limit = ((yaw_rate_max < 1.0f) ? 1 : yaw_rate_max) * 100.0f;
909 0 : float yaw2roll_scale = roll_limit / yaw_rate_limit;
910 :
911 : // Rotate as a function of Euler pitch and swap roll/yaw
912 0 : float euler_pitch = radians(.01f * plane.nav_pitch_cd);
913 0 : float spitch = fabsf(sinf(euler_pitch));
914 0 : float y2r_scale = linear_interpolate(1, yaw2roll_scale, spitch, 0, 1);
915 :
916 0 : float p_yaw_rate = plane.nav_roll_cd / y2r_scale;
917 0 : float p_roll_angle = -y2r_scale * yaw_rate_cds;
918 :
919 0 : attitude_control->input_euler_rate_yaw_euler_angle_pitch_bf_roll_cd(true,
920 : p_roll_angle,
921 0 : plane.nav_pitch_cd,
922 : p_yaw_rate);
923 0 : return;
924 : }
925 : }
926 :
927 : // note this is actually in deg/s for some SID_AXIS values for yaw
928 174078 : Vector3f offset_deg;
929 :
930 : #if AP_PLANE_SYSTEMID_ENABLED
931 174078 : auto &systemid = plane.g2.systemid;
932 174078 : systemid.vtol_update();
933 174078 : offset_deg = systemid.get_attitude_offset_deg();
934 : #endif
935 :
936 174078 : if (use_yaw_target) {
937 23061 : attitude_control->input_euler_angle_roll_pitch_yaw_cd(plane.nav_roll_cd + offset_deg.x*100,
938 23061 : plane.nav_pitch_cd + offset_deg.y*100,
939 23061 : yaw_target_cd + offset_deg.z*100,
940 : true);
941 : } else {
942 : // use euler angle attitude control
943 151017 : attitude_control->input_euler_angle_roll_pitch_euler_rate_yaw_cd(plane.nav_roll_cd + offset_deg.x*100,
944 151017 : plane.nav_pitch_cd + offset_deg.y*100,
945 151017 : yaw_rate_cds + offset_deg.z*100);
946 : }
947 : } else {
948 : // use the fixed wing desired rates
949 414050 : Vector3f bf_input_cd { plane.rollController.get_pid_info().target * 100.0f,
950 828100 : plane.pitchController.get_pid_info().target * 100.0f,
951 414050 : yaw_rate_cds };
952 :
953 : // rotate into multicopter attitude reference frame
954 414050 : ahrs_view->rotate(bf_input_cd);
955 :
956 : // disable yaw time constant for 1:1 match of desired rates
957 414050 : disable_yaw_rate_time_constant();
958 :
959 414050 : attitude_control->input_rate_bf_roll_pitch_yaw_no_shaping_cds(bf_input_cd.x, bf_input_cd.y, bf_input_cd.z);
960 : }
961 : }
962 :
963 : // hold in stabilize with given throttle
964 220611 : void QuadPlane::hold_stabilize(float throttle_in)
965 : {
966 : // call attitude controller
967 220611 : multicopter_attitude_rate_update(get_desired_yaw_rate_cds(false));
968 :
969 220611 : if ((throttle_in <= 0) && !air_mode_active()) {
970 7157 : set_desired_spool_state(AP_Motors::DesiredSpoolState::GROUND_IDLE);
971 7157 : attitude_control->set_throttle_out(0, true, 0);
972 7157 : relax_attitude_control();
973 : } else {
974 213454 : set_desired_spool_state(AP_Motors::DesiredSpoolState::THROTTLE_UNLIMITED);
975 213454 : bool should_boost = true;
976 213454 : if (tailsitter.enabled() && assisted_flight) {
977 : // tailsitters in forward flight should not use angle boost
978 2256 : should_boost = false;
979 : }
980 : #if AP_PLANE_SYSTEMID_ENABLED
981 213454 : throttle_in += plane.g2.systemid.get_throttle_offset();
982 : #endif
983 213454 : attitude_control->set_throttle_out(throttle_in, should_boost, 0);
984 : }
985 220611 : }
986 :
987 : // run the multicopter Z controller
988 2534010 : void QuadPlane::run_z_controller(void)
989 : {
990 2534010 : if (motors->get_spool_state() != AP_Motors::SpoolState::THROTTLE_UNLIMITED ) {
991 241682 : return;
992 : }
993 2292328 : const uint32_t now = AP_HAL::millis();
994 2292328 : if (tailsitter.in_vtol_transition(now)) {
995 : // never run Z controller in tailsitter transition
996 790 : return;
997 : }
998 2291538 : if ((now - last_pidz_active_ms) > 20 || !pos_control->D_is_active()) {
999 : // set vertical speed and acceleration limits
1000 : // All limits must be positive
1001 264 : pos_control->D_set_max_speed_accel_m(get_pilot_velocity_z_max_dn_m(), pilot_speed_z_max_up_ms, pilot_accel_z_mss);
1002 :
1003 : // initialise the vertical position controller
1004 264 : if (!tailsitter.enabled()) {
1005 258 : pos_control->D_init_controller();
1006 : } else {
1007 : // initialise the vertical position controller with no descent
1008 6 : pos_control->D_init_controller_no_descent();
1009 : }
1010 264 : last_pidz_init_ms = now;
1011 : }
1012 2291538 : last_pidz_active_ms = now;
1013 2291538 : pos_control->D_update_controller();
1014 : }
1015 :
1016 318754 : void QuadPlane::relax_attitude_control()
1017 : {
1018 : // disable roll and yaw control for vectored tailsitters
1019 : // if not a vectored tailsitter completely disable attitude control
1020 318754 : attitude_control->relax_attitude_controllers(!tailsitter.relax_pitch());
1021 318754 : }
1022 :
1023 1123645 : void QuadPlane::set_climb_rate_ms(float target_climb_rate_ms)
1024 : {
1025 1123645 : float vel_d_m = -target_climb_rate_ms;
1026 1123645 : pos_control->input_vel_accel_D_m(vel_d_m, 0, false);
1027 1123645 : }
1028 :
1029 : /*
1030 : hold hover with target climb rate
1031 : */
1032 367517 : void QuadPlane::hold_hover(float target_climb_rate_cms)
1033 : {
1034 : // motors use full range
1035 367517 : set_desired_spool_state(AP_Motors::DesiredSpoolState::THROTTLE_UNLIMITED);
1036 :
1037 : // set vertical speed and acceleration limits
1038 : // All limits must be positive
1039 367517 : pos_control->D_set_max_speed_accel_m(get_pilot_velocity_z_max_dn_m(), pilot_speed_z_max_up_ms, pilot_accel_z_mss);
1040 :
1041 : // call attitude controller
1042 367517 : multicopter_attitude_rate_update(get_desired_yaw_rate_cds(false));
1043 :
1044 : // call position controller
1045 367517 : set_climb_rate_ms(target_climb_rate_cms * 0.01);
1046 :
1047 367517 : run_z_controller();
1048 367517 : }
1049 :
1050 26448 : float QuadPlane::get_pilot_throttle()
1051 : {
1052 : // get scaled throttle input
1053 26448 : float throttle_in = plane.channel_throttle->get_control_in();
1054 :
1055 : // normalize to [0,1]
1056 26448 : throttle_in /= plane.channel_throttle->get_range();
1057 :
1058 26448 : if (is_positive(throttle_expo)) {
1059 : // get hover throttle level [0,1]
1060 26448 : float thr_mid = motors->get_throttle_hover();
1061 26448 : float thrust_curve_expo = constrain_float(throttle_expo, 0.0f, 1.0f);
1062 :
1063 : // this puts mid stick at hover throttle
1064 26448 : return throttle_curve(thr_mid, thrust_curve_expo, throttle_in);
1065 : } else {
1066 0 : return throttle_in;
1067 : }
1068 : }
1069 :
1070 : /*
1071 : get_pilot_desired_angle - transform pilot's roll or pitch input into a desired lean angle.
1072 : The angle_max_cd and angle_limit_cd are mode dependent
1073 : */
1074 636619 : void QuadPlane::get_pilot_desired_lean_angles(float &roll_out_cd, float &pitch_out_cd, float angle_max_cd, float angle_limit_cd) const
1075 : {
1076 : // failsafe check
1077 636619 : if (plane.failsafe.rc_failsafe || plane.failsafe.throttle_counter > 0) {
1078 0 : roll_out_cd = 0;
1079 0 : pitch_out_cd = 0;
1080 0 : return;
1081 : }
1082 :
1083 : // fetch roll and pitch inputs
1084 636619 : roll_out_cd = plane.channel_roll->get_control_in();
1085 636619 : pitch_out_cd = plane.channel_pitch->get_control_in();
1086 :
1087 : // limit max lean angle, always allow for 10 degrees
1088 636619 : angle_limit_cd = constrain_float(angle_limit_cd, 1000.0f, angle_max_cd);
1089 :
1090 : // scale roll and pitch inputs to ANGLE_MAX parameter range
1091 636619 : float scaler = angle_max_cd/4500.0;
1092 636619 : roll_out_cd *= scaler;
1093 636619 : pitch_out_cd *= scaler;
1094 :
1095 : // apply circular limit
1096 636619 : float total_in = norm(pitch_out_cd, roll_out_cd);
1097 636619 : if (total_in > angle_limit_cd) {
1098 217 : float ratio = angle_limit_cd / total_in;
1099 217 : roll_out_cd *= ratio;
1100 217 : pitch_out_cd *= ratio;
1101 : }
1102 :
1103 : // apply lateral tilt to euler roll conversion
1104 636619 : roll_out_cd = 100 * degrees(atanf(cosf(cd_to_rad(pitch_out_cd)) * tanf(cd_to_rad(roll_out_cd))));
1105 : }
1106 :
1107 : /*
1108 : get pilot throttle in for landing code. Return value on scale of 0 to 1
1109 : */
1110 0 : float QuadPlane::get_pilot_land_throttle(void) const
1111 : {
1112 0 : if (plane.rc_failsafe_active()) {
1113 : // assume zero throttle if lost RC
1114 0 : return 0;
1115 : }
1116 : // get scaled throttle input
1117 0 : float throttle_in = plane.channel_throttle->get_control_in();
1118 :
1119 : // normalize to [0,1]
1120 0 : throttle_in /= plane.channel_throttle->get_range();
1121 :
1122 0 : return constrain_float(throttle_in, 0, 1);
1123 : }
1124 :
1125 : // helper for is_flying()
1126 462811 : bool QuadPlane::is_flying(void)
1127 : {
1128 462811 : if (!available()) {
1129 203595 : return false;
1130 : }
1131 259216 : if (plane.control_mode == &plane.mode_guided && guided_takeoff) {
1132 879 : return true;
1133 : }
1134 258337 : if (motors->get_throttle() > 0.01f && !motors->limit.throttle_lower) {
1135 36777 : return true;
1136 : }
1137 221560 : if (tailsitter.in_vtol_transition()) {
1138 0 : return true;
1139 : }
1140 221560 : return false;
1141 : }
1142 :
1143 : // crude landing detector to prevent tipover
1144 3226556 : bool QuadPlane::should_relax(void)
1145 : {
1146 3226556 : const uint32_t tnow = millis();
1147 :
1148 3226556 : bool motor_at_lower_limit = motors->limit.throttle_lower && attitude_control->is_throttle_mix_min();
1149 3226556 : if (motors->get_throttle() < 0.01f) {
1150 477046 : motor_at_lower_limit = true;
1151 : }
1152 :
1153 3226556 : if (!motor_at_lower_limit) {
1154 2749510 : landing_detect.lower_limit_start_ms = 0;
1155 2749510 : landing_detect.land_start_ms = 0;
1156 2749510 : return false;
1157 477046 : } else if (landing_detect.lower_limit_start_ms == 0) {
1158 276 : landing_detect.lower_limit_start_ms = tnow;
1159 : }
1160 :
1161 477046 : return (tnow - landing_detect.lower_limit_start_ms) > 1000;
1162 : }
1163 :
1164 : // see if we are flying in vtol
1165 12163210 : bool QuadPlane::is_flying_vtol(void) const
1166 : {
1167 12163210 : if (!available()) {
1168 2481605 : return false;
1169 : }
1170 9681605 : if (motors->get_spool_state() == AP_Motors::SpoolState::SHUT_DOWN) {
1171 : // assume that with no motor outputs we're not flying in VTOL mode
1172 3339058 : return false;
1173 : }
1174 6342547 : if (motors->get_throttle() > 0.01f) {
1175 : // if we are demanding more than 1% throttle then don't consider aircraft landed
1176 5628533 : return true;
1177 : }
1178 714014 : if (plane.control_mode->is_vtol_man_throttle() && air_mode_active()) {
1179 : // in manual throttle modes with airmode on, don't consider aircraft landed
1180 22514 : return true;
1181 : }
1182 691500 : if (plane.control_mode == &plane.mode_guided && guided_takeoff) {
1183 1922 : return true;
1184 : }
1185 689578 : if (plane.control_mode->is_vtol_man_mode()) {
1186 : // in manual flight modes only consider aircraft landed when pilot demanded throttle is zero
1187 91024 : return is_positive(get_throttle_input());
1188 : }
1189 598554 : if (in_vtol_mode() && millis() - landing_detect.lower_limit_start_ms > 5000) {
1190 : // use landing detector
1191 105093 : return true;
1192 : }
1193 493461 : return false;
1194 : }
1195 :
1196 : /*
1197 : smooth out descent rate for landing to prevent a jerk as we get to
1198 : land_final_alt_m.
1199 : */
1200 856222 : float QuadPlane::landing_descent_rate_ms(float height_above_ground_m)
1201 : {
1202 856222 : if (poscontrol.last_override_descent_ms != 0) {
1203 15795 : const uint32_t now = AP_HAL::millis();
1204 15795 : if (now - poscontrol.last_override_descent_ms < 1000) {
1205 2296 : return poscontrol.override_descent_rate_ms;
1206 : }
1207 : }
1208 :
1209 853926 : if (poscontrol.get_state() == QPOS_LAND_FINAL) {
1210 : // when in final use descent rate for final even if alt has climbed again
1211 361512 : height_above_ground_m = MIN(height_above_ground_m, land_final_alt_m);
1212 : }
1213 853926 : const float max_climb_speed_ms = wp_nav->get_default_speed_up_ms();
1214 853926 : float ret_ms = linear_interpolate(land_final_speed_ms, wp_nav->get_default_speed_down_ms(),
1215 : height_above_ground_m,
1216 853926 : land_final_alt_m, land_final_alt_m + 6);
1217 :
1218 853926 : if (option_is_set(QuadPlane::Option::THR_LANDING_CONTROL)) {
1219 : // allow throttle control for landing speed
1220 0 : const float thr_in = get_pilot_land_throttle();
1221 0 : if (thr_in > THR_CTRL_LAND_THRESH) {
1222 0 : thr_ctrl_land = true;
1223 : }
1224 0 : if (thr_ctrl_land) {
1225 0 : const float dz = 0.1;
1226 0 : const float thresh1 = 0.5 + dz;
1227 0 : const float thresh2 = 0.5 - dz;
1228 0 : const float scaling = 1.0 / (0.5 - dz);
1229 0 : if (thr_in > thresh1) {
1230 : // start climbing
1231 0 : ret_ms = -(thr_in - thresh1) * scaling * max_climb_speed_ms;
1232 0 : } else if (thr_in > thresh2) {
1233 : // hold height
1234 0 : ret_ms = 0;
1235 : } else {
1236 0 : ret_ms *= (thresh2 - thr_in) * scaling;
1237 : }
1238 : }
1239 : }
1240 :
1241 853926 : if (poscontrol.pilot_correction_active) {
1242 : // stop descent when repositioning
1243 0 : ret_ms = MIN(0, ret_ms);
1244 : }
1245 :
1246 853926 : return ret_ms;
1247 : }
1248 :
1249 : /*
1250 : get pilot input yaw rate in cd/s
1251 : */
1252 2502050 : float QuadPlane::get_pilot_input_yaw_rate_cds(void) const
1253 : {
1254 2502050 : const auto rudder_in = plane.channel_rudder->get_control_in();
1255 2502050 : bool manual_air_mode = plane.control_mode->is_vtol_man_throttle() && air_mode_active();
1256 7497414 : if (!manual_air_mode &&
1257 2493314 : !is_positive(get_throttle_input()) &&
1258 1624418 : (!plane.control_mode->does_auto_throttle() || motors->limit.throttle_lower) &&
1259 488518 : plane.arming.get_rudder_arming_type() == AP_Arming::RudderArming::ARMDISARM &&
1260 4995364 : rudder_in < 0 &&
1261 0 : fabsf(inertial_nav.get_velocity_z_up_cms()) < (0.5 * get_pilot_velocity_z_max_dn_m()) * 100) {
1262 : // the user may be trying to disarm, disable pilot yaw control
1263 0 : return 0;
1264 : }
1265 :
1266 2549484 : if ((plane.g.stick_mixing == StickMixing::NONE) &&
1267 78916 : (plane.control_mode == &plane.mode_qrtl ||
1268 62964 : plane.control_mode->is_guided_mode() ||
1269 31482 : in_vtol_auto())) {
1270 15952 : return 0;
1271 : }
1272 :
1273 : // add in rudder input
1274 2486098 : const float yaw_rate_max = command_model_pilot.get_rate();
1275 2486098 : float max_rate = yaw_rate_max;
1276 2486098 : if (!in_vtol_mode() && tailsitter.enabled()) {
1277 : // scale by RUDD_DT_GAIN when not in a VTOL mode for
1278 : // tailsitters. This allows for flat turns in tailsitters for
1279 : // fixed wing modes if you want them, but prevents crazy yaw
1280 : // rate demands in fixed wing based on your preferred yaw rate
1281 : // when hovering
1282 2914 : max_rate *= plane.g2.rudd_dt_gain * 0.01;
1283 : }
1284 2562136 : if (tailsitter.enabled() &&
1285 76038 : tailsitter.input_type & Tailsitter::input::TAILSITTER_INPUT_BF_ROLL) {
1286 : // must have a non-zero max yaw rate for scaling to work
1287 0 : max_rate = (yaw_rate_max < 1.0f) ? 1 : yaw_rate_max;
1288 : }
1289 2486098 : return input_expo(rudder_in * (1/4500.0), command_model_pilot.get_expo()) * max_rate * 100.0;
1290 : }
1291 :
1292 : /*
1293 : get overall desired yaw rate in cd/s
1294 : */
1295 1354271 : float QuadPlane::get_desired_yaw_rate_cds(bool should_weathervane)
1296 : {
1297 1354271 : float yaw_cds = 0;
1298 1354271 : if (assisted_flight) {
1299 : // use bank angle to get desired yaw rate
1300 420416 : yaw_cds += desired_auto_yaw_rate_cds();
1301 : }
1302 :
1303 : // add in pilot input
1304 1354271 : yaw_cds += get_pilot_input_yaw_rate_cds();
1305 :
1306 1354271 : if (should_weathervane) {
1307 : // add in weathervaning
1308 766143 : yaw_cds += get_weathervane_yaw_rate_cds();
1309 : }
1310 :
1311 1354271 : return yaw_cds;
1312 : }
1313 :
1314 : // get pilot desired climb rate in cm/s
1315 649966 : float QuadPlane::get_pilot_desired_climb_rate_cms(void) const
1316 : {
1317 649966 : if (!rc().has_valid_input()) {
1318 : // no valid input means no sensible pilot desired climb rate.
1319 : // descend at 0.5m/s for now
1320 0 : return -50;
1321 : }
1322 649966 : uint16_t dead_zone = plane.channel_throttle->get_dead_zone();
1323 649966 : uint16_t trim = (plane.channel_throttle->get_radio_max() + plane.channel_throttle->get_radio_min()) / 2;
1324 649966 : const float throttle_request = plane.channel_throttle->pwm_to_angle_dz_trim(dead_zone, trim) * 0.01f;
1325 649966 : return throttle_request * (throttle_request > 0.0f ? pilot_speed_z_max_up_ms : get_pilot_velocity_z_max_dn_m()) * 100;
1326 : }
1327 :
1328 :
1329 : /*
1330 : initialise throttle_wait based on throttle and is_flying()
1331 : */
1332 15622 : void QuadPlane::init_throttle_wait(void)
1333 : {
1334 15944 : if (get_throttle_input() >= 10 ||
1335 322 : plane.is_flying()) {
1336 15315 : throttle_wait = false;
1337 : } else {
1338 307 : throttle_wait = true;
1339 : }
1340 15622 : }
1341 :
1342 : // set motor arming
1343 816 : void QuadPlane::set_armed(bool armed)
1344 : {
1345 816 : if (!initialised) {
1346 354 : return;
1347 : }
1348 462 : motors->armed(armed);
1349 :
1350 462 : if (plane.control_mode == &plane.mode_guided) {
1351 12 : guided_wait_takeoff = armed;
1352 : }
1353 :
1354 : // re-init throttle wait on arm and disarm, to prevent rudder
1355 : // arming on 2nd flight causing yaw
1356 462 : if (!air_mode_active()) {
1357 454 : init_throttle_wait();
1358 : }
1359 : }
1360 :
1361 :
1362 : /*
1363 : estimate desired climb rate for assistance (in cm/s)
1364 : */
1365 219920 : float QuadPlane::assist_climb_rate_cms(void) const
1366 : {
1367 : float climb_rate_cms;
1368 219920 : if (plane.control_mode->does_auto_throttle()) {
1369 : // use altitude_error_cm, spread over 10s interval
1370 183980 : climb_rate_cms = plane.calc_altitude_error_cm() * 0.1f;
1371 : } else {
1372 : // otherwise estimate from pilot input
1373 35940 : climb_rate_cms = plane.g.flybywire_climb_rate * (plane.nav_pitch_cd / (plane.aparm.pitch_limit_max * 100));
1374 35940 : climb_rate_cms *= plane.get_throttle_input();
1375 : }
1376 219920 : climb_rate_cms = constrain_float(climb_rate_cms, -wp_nav->get_default_speed_down_ms() * 100.0, wp_nav->get_default_speed_up_ms() * 100.0);
1377 :
1378 : // bring in the demanded climb rate over 2 seconds
1379 219920 : const uint32_t ramp_up_time_ms = 2000;
1380 219920 : const uint32_t dt_since_start = last_pidz_active_ms - last_pidz_init_ms;
1381 219920 : if (dt_since_start < ramp_up_time_ms) {
1382 26777 : climb_rate_cms = linear_interpolate(0, climb_rate_cms, dt_since_start, 0, ramp_up_time_ms);
1383 : }
1384 :
1385 219920 : return climb_rate_cms;
1386 : }
1387 :
1388 : /*
1389 : calculate desired yaw rate for assistance
1390 : */
1391 945068 : float QuadPlane::desired_auto_yaw_rate_cds(bool body_frame) const
1392 : {
1393 : float aspeed;
1394 945068 : if (!ahrs.airspeed_EAS(aspeed) || aspeed < plane.aparm.airspeed_min) {
1395 332062 : aspeed = plane.aparm.airspeed_min;
1396 : }
1397 945068 : if (aspeed < 1) {
1398 0 : aspeed = 1;
1399 : }
1400 945068 : if (body_frame) {
1401 374475 : return degrees(GRAVITY_MSS * sinf(cd_to_rad(plane.nav_roll_cd))/aspeed) * 100;
1402 : }
1403 570593 : return degrees(GRAVITY_MSS * tanf(cd_to_rad(plane.nav_roll_cd))/aspeed) * 100;
1404 : }
1405 :
1406 : /*
1407 : update for transition from quadplane to fixed wing mode
1408 : */
1409 3408218 : void SLT_Transition::update()
1410 : {
1411 3408218 : const uint32_t now = millis();
1412 :
1413 3408218 : if (!plane.arming.is_armed_and_safety_off()) {
1414 : // reset the failure timer if we are disarmed
1415 814855 : transition_start_ms = now;
1416 : }
1417 :
1418 : float aspeed;
1419 3408218 : bool have_airspeed = quadplane.ahrs.airspeed_EAS(aspeed);
1420 :
1421 : /*
1422 : see if we should provide some assistance
1423 : */
1424 3408218 : if (quadplane.assist.should_assist(aspeed, have_airspeed)) {
1425 : // the quad should provide some assistance to the plane
1426 163422 : quadplane.assisted_flight = true;
1427 : // update transition state for vehicles using airspeed wait
1428 163422 : if (!in_forced_transition) {
1429 163422 : const bool show_message = transition_state != State::AIRSPEED_WAIT || transition_start_ms == 0;
1430 163422 : if (show_message) {
1431 148 : gcs().send_text(MAV_SEVERITY_INFO, "Transition started airspeed %.1f", (double)aspeed);
1432 : }
1433 163422 : transition_state = State::AIRSPEED_WAIT;
1434 163422 : if (transition_start_ms == 0) {
1435 102 : transition_start_ms = now;
1436 : }
1437 : }
1438 : } else {
1439 3244796 : quadplane.assisted_flight = false;
1440 : }
1441 :
1442 :
1443 : // if rotors are fully forward then we are not transitioning,
1444 : // unless we are waiting for airspeed to increase (in which case
1445 : // the tilt will decrease rapidly)
1446 3408218 : if (quadplane.tiltrotor.fully_fwd() && transition_state != State::AIRSPEED_WAIT) {
1447 224256 : if (transition_state == State::TIMER) {
1448 : float throttle;
1449 13 : if (plane.quadplane.tiltrotor.get_forward_throttle(throttle)) {
1450 : // Reset the TECS minimum throttle to match throttle of forward thrust motors
1451 : // and set the throttle channel slew rate limiter to prevent a sudden drop in throttle
1452 7 : if (plane.control_mode->does_auto_throttle()) {
1453 6 : plane.TECS_controller.set_throttle_min(throttle, true);
1454 : }
1455 7 : SRV_Channels::set_slew_last_scaled_output(SRV_Channel::k_throttle, throttle * 100);
1456 7 : SRV_Channels::set_output_scaled(SRV_Channel::k_throttle, throttle * 100);
1457 : }
1458 13 : gcs().send_text(MAV_SEVERITY_INFO, "Transition FW done");
1459 : }
1460 224256 : transition_state = State::DONE;
1461 224256 : transition_start_ms = 0;
1462 224256 : transition_low_airspeed_ms = 0;
1463 : }
1464 :
1465 3408218 : if (transition_state != State::DONE) {
1466 : // during transition we ask TECS to use a synthetic
1467 : // airspeed. Otherwise the pitch limits will throw off the
1468 : // throttle calculation which is driven by pitch
1469 392449 : plane.TECS_controller.use_synthetic_airspeed();
1470 : }
1471 :
1472 3408218 : switch (transition_state) {
1473 193361 : case State::AIRSPEED_WAIT: {
1474 193361 : quadplane.set_desired_spool_state(AP_Motors::DesiredSpoolState::THROTTLE_UNLIMITED);
1475 : // we hold in hover until the required airspeed is reached
1476 193361 : if (transition_start_ms == 0) {
1477 4 : gcs().send_text(MAV_SEVERITY_INFO, "Transition airspeed wait");
1478 4 : transition_start_ms = now;
1479 : }
1480 :
1481 : // check if we have failed to transition while in State::AIRSPEED_WAIT
1482 580083 : if (transition_start_ms != 0 &&
1483 196265 : (quadplane.transition_failure.timeout > 0) &&
1484 2904 : ((now - transition_start_ms) > ((uint32_t)quadplane.transition_failure.timeout * 1000))) {
1485 1 : if (!quadplane.transition_failure.warned) {
1486 1 : gcs().send_text(MAV_SEVERITY_CRITICAL, "Transition failed, exceeded time limit");
1487 1 : quadplane.transition_failure.warned = true;
1488 : }
1489 : // if option is set and ground speed> 1/2 AIRSPEED_MIN for non-tiltrotors, then complete transition, otherwise QLAND.
1490 : // tiltrotors will immediately transition
1491 1 : const bool tiltrotor_with_ground_speed = quadplane.tiltrotor.enabled() && (plane.ahrs.groundspeed() > plane.aparm.airspeed_min * 0.5);
1492 1 : if (quadplane.option_is_set(QuadPlane::Option::TRANS_FAIL_TO_FW) && tiltrotor_with_ground_speed) {
1493 0 : transition_state = State::TIMER;
1494 0 : in_forced_transition = true;
1495 : } else {
1496 1 : switch (QuadPlane::TRANS_FAIL::ACTION(quadplane.transition_failure.action)) {
1497 0 : case QuadPlane::TRANS_FAIL::ACTION::QLAND:
1498 0 : plane.set_mode(plane.mode_qland, ModeReason::VTOL_FAILED_TRANSITION);
1499 0 : break;
1500 :
1501 1 : case QuadPlane::TRANS_FAIL::ACTION::QRTL:
1502 1 : plane.set_mode(plane.mode_qrtl, ModeReason::VTOL_FAILED_TRANSITION);
1503 1 : quadplane.poscontrol.set_state(QuadPlane::QPOS_POSITION1);
1504 1 : break;
1505 :
1506 0 : default:
1507 0 : break;
1508 : }
1509 : }
1510 : } else {
1511 193360 : quadplane.transition_failure.warned = false;
1512 : }
1513 :
1514 193361 : transition_low_airspeed_ms = now;
1515 193361 : if (have_airspeed && aspeed > plane.aparm.airspeed_min && !quadplane.assisted_flight) {
1516 172 : transition_state = State::TIMER;
1517 172 : airspeed_reached_tilt = quadplane.tiltrotor.current_tilt;
1518 172 : gcs().send_text(MAV_SEVERITY_INFO, "Transition airspeed reached %.1f", (double)aspeed);
1519 : }
1520 193361 : quadplane.assisted_flight = true;
1521 :
1522 : // do not allow a climb on the quad motors during transition a
1523 : // climb would add load to the airframe, and prolongs the
1524 : // transition. We don't limit the climb rate on tilt rotors as
1525 : // otherwise the plane can end up in high-alpha flight with
1526 : // low VTOL thrust and may not complete a transition
1527 193361 : float climb_rate_cms = quadplane.assist_climb_rate_cms();
1528 193361 : if (quadplane.option_is_set(QuadPlane::Option::LEVEL_TRANSITION) && !quadplane.tiltrotor.enabled()) {
1529 0 : climb_rate_cms = MIN(climb_rate_cms, 0.0f);
1530 : }
1531 193361 : quadplane.hold_hover(climb_rate_cms);
1532 :
1533 193361 : if (!quadplane.tiltrotor.is_vectored()) {
1534 : // set desired yaw rate to a coordinated turn
1535 177874 : quadplane.attitude_control->reset_yaw_target_and_rate();
1536 177874 : quadplane.attitude_control->rate_bf_yaw_target(quadplane.desired_auto_yaw_rate_cds(true));
1537 : }
1538 193361 : if (quadplane.tiltrotor.enabled() && !quadplane.tiltrotor.has_fw_motor()) {
1539 : // tilt rotors without dedicated fw motors do not have forward throttle output in this stage
1540 : // prevent throttle I wind up
1541 21278 : plane.TECS_controller.reset_throttle_I();
1542 : }
1543 :
1544 193361 : last_throttle = motors->get_throttle();
1545 :
1546 : // reset integrators while we are below target airspeed as we
1547 : // may build up too much while still primarily under
1548 : // multicopter control
1549 193361 : plane.pitchController.reset_I();
1550 193361 : plane.rollController.reset_I();
1551 :
1552 : // give full authority to attitude control
1553 193361 : quadplane.attitude_control->set_throttle_mix_max(1.0f);
1554 193361 : break;
1555 : }
1556 :
1557 199088 : case State::TIMER: {
1558 199088 : quadplane.set_desired_spool_state(AP_Motors::DesiredSpoolState::THROTTLE_UNLIMITED);
1559 : // after airspeed is reached we degrade throttle over the transition time, but continue
1560 : // to stabilize and wait for any required forward tilt to complete and the timer to expire
1561 199088 : const uint32_t transition_timer_ms = now - transition_low_airspeed_ms;
1562 199088 : const float trans_time_ms = constrain_float(quadplane.transition_time_ms, 500, 30000);
1563 199088 : const bool tilt_fwd_complete = !quadplane.tiltrotor.enabled() || quadplane.tiltrotor.tilt_angle_achieved();
1564 199088 : if (transition_timer_ms > unsigned(trans_time_ms) && tilt_fwd_complete) {
1565 112 : transition_state = State::DONE;
1566 112 : in_forced_transition = false;
1567 112 : transition_start_ms = 0;
1568 112 : transition_low_airspeed_ms = 0;
1569 : float throttle;
1570 112 : if (plane.quadplane.tiltrotor.get_forward_throttle(throttle)) {
1571 : // Reset the TECS minimum throttle to match throttle of forward thrust motors
1572 : // and set the throttle channel slew rate limiter to prevent a sudden drop in throttle
1573 0 : if (plane.control_mode->does_auto_throttle()) {
1574 0 : plane.TECS_controller.set_throttle_min(throttle, true);
1575 : }
1576 0 : SRV_Channels::set_slew_last_scaled_output(SRV_Channel::k_throttle, throttle * 100);
1577 0 : SRV_Channels::set_output_scaled(SRV_Channel::k_throttle, throttle * 100);
1578 : }
1579 112 : gcs().send_text(MAV_SEVERITY_INFO, "Transition done");
1580 : }
1581 :
1582 199088 : float transition_scale = (trans_time_ms - transition_timer_ms) / trans_time_ms;
1583 199088 : float throttle_scaled = last_throttle * transition_scale;
1584 :
1585 : // set zero throttle mix, to give full authority to
1586 : // throttle. This ensures that the fixed wing controllers get
1587 : // a chance to learn the right integrators during the transition
1588 199088 : quadplane.attitude_control->set_throttle_mix_value(0.5 * transition_scale);
1589 :
1590 199088 : if (throttle_scaled < 0.01) {
1591 : // ensure we don't drop all the way to zero or the motors
1592 : // will stop stabilizing
1593 150137 : throttle_scaled = 0.01;
1594 : }
1595 199088 : if (quadplane.tiltrotor.enabled() && !quadplane.tiltrotor.has_vtol_motor() && !quadplane.tiltrotor.has_fw_motor()) {
1596 : // All motors tilting, Use a combination of vertical and forward throttle based on current tilt angle
1597 : // scale from all VTOL throttle at airspeed_reached_tilt to all forward throttle at fully forward tilt
1598 : // this removes a step change in throttle once assistance is stopped
1599 0 : const float ratio = (constrain_float(quadplane.tiltrotor.current_tilt, airspeed_reached_tilt, quadplane.tiltrotor.get_fully_forward_tilt()) - airspeed_reached_tilt) / (quadplane.tiltrotor.get_fully_forward_tilt() - airspeed_reached_tilt);
1600 0 : const float fw_throttle = MAX(SRV_Channels::get_output_scaled(SRV_Channel::k_throttle),0) * 0.01;
1601 0 : throttle_scaled = constrain_float(throttle_scaled * (1.0-ratio) + fw_throttle * ratio, 0.0, 1.0);
1602 : }
1603 199088 : quadplane.assisted_flight = true;
1604 199088 : quadplane.hold_stabilize(throttle_scaled);
1605 :
1606 199088 : if (!quadplane.tiltrotor.is_vectored()) {
1607 : // set desired yaw rate to a coordinated turn
1608 196601 : quadplane.attitude_control->reset_yaw_target_and_rate();
1609 196601 : quadplane.attitude_control->rate_bf_yaw_target(quadplane.desired_auto_yaw_rate_cds(true));
1610 : }
1611 199088 : break;
1612 : }
1613 :
1614 3015769 : case State::DONE:
1615 3015769 : quadplane.set_desired_spool_state(AP_Motors::DesiredSpoolState::SHUT_DOWN);
1616 3015769 : motors->output();
1617 3015769 : set_last_fw_pitch();
1618 3015769 : in_forced_transition = false;
1619 3015769 : return;
1620 : }
1621 :
1622 392449 : quadplane.motors_output();
1623 :
1624 392449 : set_last_fw_pitch();
1625 : }
1626 :
1627 2671882 : void SLT_Transition::VTOL_update()
1628 : {
1629 : /*
1630 : setup the transition state appropriately for next time we go into a non-VTOL mode
1631 : */
1632 2671882 : transition_start_ms = 0;
1633 2671882 : transition_low_airspeed_ms = 0;
1634 2671882 : if (quadplane.throttle_wait && !plane.is_flying()) {
1635 316080 : in_forced_transition = false;
1636 316080 : transition_state = State::DONE;
1637 : } else {
1638 : /*
1639 : setup for airspeed wait for later
1640 : */
1641 2355802 : transition_state = State::AIRSPEED_WAIT;
1642 : }
1643 2671882 : last_throttle = motors->get_throttle();
1644 :
1645 : // Keep assistance reset while not checking
1646 2671882 : quadplane.assist.reset();
1647 2671882 : }
1648 :
1649 : /*
1650 : update motor output for quadplane
1651 : */
1652 8377244 : void QuadPlane::update(void)
1653 : {
1654 8377244 : if (!setup()) {
1655 1873190 : return;
1656 : }
1657 :
1658 : // keep motors interlock state upto date with E-stop
1659 6504054 : motors->set_interlock(!SRV_Channels::get_emergency_stop());
1660 :
1661 6504054 : if ((ahrs_view != NULL) && !is_equal(_last_ahrs_trim_pitch, ahrs_trim_pitch.get())) {
1662 0 : _last_ahrs_trim_pitch = ahrs_trim_pitch.get();
1663 0 : ahrs_view->set_pitch_trim(_last_ahrs_trim_pitch);
1664 : }
1665 :
1666 : #if AP_ADVANCEDFAILSAFE_ENABLED
1667 6504054 : if (plane.afs.should_crash_vehicle() && !plane.afs.terminating_vehicle_via_landing()) {
1668 0 : set_desired_spool_state(AP_Motors::DesiredSpoolState::SHUT_DOWN);
1669 0 : motors->output();
1670 0 : return;
1671 : }
1672 : #endif
1673 :
1674 6504054 : if (motor_test.running) {
1675 6000 : motor_test_output();
1676 6000 : return;
1677 : }
1678 :
1679 6498054 : if (SRV_Channels::get_emergency_stop()) {
1680 0 : attitude_control->reset_rate_controller_I_terms();
1681 : }
1682 :
1683 6498054 : if (!plane.arming.is_armed_and_safety_off()) {
1684 : /*
1685 : make sure we don't have any residual control from previous flight stages
1686 : */
1687 1487549 : if (tailsitter.enabled()) {
1688 : // tailsitters only relax I terms, to make ground testing easier
1689 39313 : attitude_control->reset_rate_controller_I_terms();
1690 : } else {
1691 : // otherwise full relax
1692 1448236 : attitude_control->relax_attitude_controllers();
1693 : }
1694 : // todo: do you want to set the throttle at this point?
1695 1487549 : pos_control->D_relax_controller(0);
1696 : }
1697 :
1698 6498054 : const uint32_t now = AP_HAL::millis();
1699 6498054 : if (!in_vtol_mode() && !in_vtol_airbrake()) {
1700 : // we're in a fixed wing mode, cope with transitions and check
1701 : // for assistance needed
1702 3731271 : if (plane.control_mode == &plane.mode_manual ||
1703 3473152 : plane.control_mode == &plane.mode_acro ||
1704 3471953 : plane.control_mode == &plane.mode_training) {
1705 : // in manual modes quad motors are always off
1706 260698 : if (!tailsitter.enabled()) {
1707 260194 : set_desired_spool_state(AP_Motors::DesiredSpoolState::SHUT_DOWN);
1708 260194 : motors->output();
1709 : }
1710 260698 : transition->force_transition_complete();
1711 260698 : assisted_flight = false;
1712 : } else {
1713 3470573 : transition->update();
1714 : }
1715 :
1716 : } else {
1717 :
1718 2766783 : assisted_flight = in_vtol_airbrake();
1719 :
1720 : // output to motors
1721 2766783 : motors_output();
1722 :
1723 2766783 : transition->VTOL_update();
1724 :
1725 : }
1726 :
1727 : // disable throttle_wait when throttle rises above 10%
1728 9720073 : if (throttle_wait &&
1729 1611035 : (plane.get_throttle_input() > 10 ||
1730 1610984 : !rc().has_valid_input())) {
1731 51 : throttle_wait = false;
1732 : }
1733 :
1734 6498054 : tiltrotor.update();
1735 :
1736 6498054 : if (in_vtol_mode()) {
1737 : // if enabled output forward throttle else 0
1738 2741745 : float fwd_thr = 0;
1739 2741745 : if (allow_forward_throttle_in_vtol_mode()) {
1740 2292091 : fwd_thr = forward_throttle_pct();
1741 : }
1742 2741745 : SRV_Channels::set_output_scaled(SRV_Channel::k_throttle, fwd_thr);
1743 : }
1744 :
1745 : #if HAL_LOGGING_ENABLED
1746 : // motors logging
1747 6498054 : if (motors->armed()) {
1748 5010505 : const bool motors_active = in_vtol_mode() || assisted_flight;
1749 5010505 : if (motors_active && (motors->get_spool_state() != AP_Motors::SpoolState::SHUT_DOWN)) {
1750 : // log ANG at main loop rate
1751 2663143 : bool sysid_running = false;
1752 : #if AP_PLANE_SYSTEMID_ENABLED
1753 2663143 : sysid_running = plane.g2.systemid.is_running();
1754 : #endif
1755 2663143 : if (!sysid_running) {
1756 2663143 : if (show_vtol_view()) {
1757 2301001 : attitude_control->Write_ANG();
1758 : }
1759 : // log RATE at main loop rate
1760 2663143 : attitude_control->Write_Rate(*pos_control);
1761 : }
1762 :
1763 : // log MOTB at 10 Hz
1764 2663143 : if (now - last_motb_log_ms > 100) {
1765 86115 : last_motb_log_ms = now;
1766 86115 : motors->Log_Write();
1767 : }
1768 : }
1769 : // log QTUN at 25 Hz if motors are active, or have been active in the last quarter second
1770 5010505 : if ((motors_active || (now - last_motors_active_ms < 250)) && (now - last_qtun_log_ms > 40)) {
1771 205625 : last_qtun_log_ms = now;
1772 205625 : Log_Write_QControl_Tuning();
1773 : }
1774 : }
1775 : #else
1776 : (void)now;
1777 : #endif // HAL_LOGGING_ENABLED
1778 : }
1779 :
1780 : /*
1781 : see if motors should be shutdown. If they should be then change AP_Motors state to
1782 : AP_Motors::DesiredSpoolState::SHUT_DOWN
1783 :
1784 : This is a safety check to prevent accidental motor runs on the
1785 : ground, such as if RC fails and QRTL is started
1786 : */
1787 2737526 : void QuadPlane::update_throttle_suppression(void)
1788 : {
1789 : // if the motors have been running in the last 2 seconds then
1790 : // allow them to run now
1791 2737526 : if (AP_HAL::millis() - last_motors_active_ms < 2000) {
1792 2401343 : return;
1793 : }
1794 :
1795 : // see if motors are already disabled
1796 336183 : if (motors->get_desired_spool_state() < AP_Motors::DesiredSpoolState::THROTTLE_UNLIMITED) {
1797 18235 : return;
1798 : }
1799 :
1800 317948 : if (guided_wait_takeoff) {
1801 412 : goto idle_state;
1802 : }
1803 :
1804 : /* if the users throttle is above zero then allow motors to run
1805 :
1806 : if the user has unset the "check throttle zero when arming"
1807 : then the RC controller has a sprung throttle and we should not
1808 : consider non-zero throttle to mean that pilot is commanding
1809 : takeoff unless in a manual throttle mode
1810 : */
1811 458219 : if (!is_zero(get_throttle_input()) &&
1812 140683 : (rc().arming_check_throttle() ||
1813 0 : plane.control_mode->is_vtol_man_throttle() ||
1814 0 : plane.channel_throttle->norm_input_dz() > 0)) {
1815 140683 : return;
1816 : }
1817 :
1818 : // if in a VTOL manual throttle mode and air_mode is on then allow motors to run
1819 176853 : if (plane.control_mode->is_vtol_man_throttle() && air_mode_active()) {
1820 15847 : return;
1821 : }
1822 :
1823 : // if we are in a fixed wing auto throttle mode and we have
1824 : // unsuppressed the throttle then allow motors to run
1825 161006 : if (plane.control_mode->does_auto_throttle() && !plane.throttle_suppressed) {
1826 142845 : return;
1827 : }
1828 :
1829 : // if our vertical velocity is greater than 1m/s then allow motors to run
1830 18161 : if (fabsf(inertial_nav.get_velocity_z_up_cms()) > 100) {
1831 2299 : return;
1832 : }
1833 :
1834 : // if we are more than 5m from home altitude then allow motors to run
1835 15862 : if (plane.relative_ground_altitude(RangeFinderUse::TAKEOFF_LANDING) > 5) {
1836 202 : return;
1837 : }
1838 :
1839 : // allow for takeoff
1840 15660 : if (plane.control_mode == &plane.mode_auto && is_vtol_takeoff(plane.mission.get_current_nav_cmd().id)) {
1841 4207 : return;
1842 : }
1843 :
1844 11453 : idle_state:
1845 : // motors should be in the spin when armed state to warn user they could become active
1846 11865 : set_desired_spool_state(AP_Motors::DesiredSpoolState::GROUND_IDLE);
1847 11865 : motors->set_throttle(0);
1848 11865 : last_motors_active_ms = 0;
1849 : }
1850 :
1851 : // update estimated throttle required to hover (if necessary)
1852 : // called at 100hz
1853 3894351 : void QuadPlane::update_throttle_hover()
1854 : {
1855 3894351 : if (!available()) {
1856 3810735 : return;
1857 : }
1858 :
1859 : // if not armed or landed exit
1860 2167938 : if (!motors->armed() || !is_flying_vtol()) {
1861 1376366 : return;
1862 : }
1863 :
1864 : // do not update while climbing or descending
1865 791572 : if (!is_zero(pos_control->get_vel_desired_U_ms())) {
1866 656406 : return;
1867 : }
1868 :
1869 : // do not update if quadplane forward motor is running (wing may be generating lift)
1870 : // we use the THR_MIN value to account for petrol motors idling at THR_MIN
1871 135166 : if (!tailsitter.enabled() && (SRV_Channels::get_output_scaled(SRV_Channel::k_throttle) > MAX(0, plane.aparm.throttle_min + 10))) {
1872 5400 : return;
1873 : }
1874 :
1875 : // don't update if Z controller not running
1876 129766 : const uint32_t now = AP_HAL::millis();
1877 129766 : if (now - last_pidz_active_ms > 20) {
1878 46150 : return;
1879 : }
1880 :
1881 : // get throttle output
1882 83616 : float throttle = motors->get_throttle();
1883 :
1884 : float aspeed;
1885 : // calc average throttle if we are in a level hover and low airspeed
1886 78911 : if (throttle > 0.0f && fabsf(inertial_nav.get_velocity_z_up_cms()) < 60 &&
1887 141351 : labs(ahrs_view->roll_sensor) < 500 && labs(ahrs_view->pitch_sensor) < 500 &&
1888 229457 : ahrs.airspeed_EAS(aspeed) && aspeed < plane.aparm.airspeed_min * 0.3) {
1889 : // Can we set the time constant automatically
1890 55011 : motors->update_throttle_hover(0.01f);
1891 : #if HAL_GYROFFT_ENABLED
1892 55011 : plane.gyro_fft.update_freq_hover(0.01f, motors->get_throttle_out());
1893 : #endif
1894 : }
1895 : }
1896 : /*
1897 : output motors and do any copter needed
1898 : */
1899 3259693 : void QuadPlane::motors_output(bool run_rate_controller)
1900 : {
1901 : // QuadPlane has no pre-takeoff RPM gate that asserts the spool-up block,
1902 : // so clear it every cycle before motors->output() runs output_logic().
1903 : // Without this the block set during ground-idle ramp would never clear
1904 : // and the spool would stay in GROUND_IDLE.
1905 3259693 : motors->set_spoolup_block(false);
1906 :
1907 : /* Delay for ARMING_DELAY_MS after arming before allowing props to spin:
1908 : 1) for safety (OPTION_DELAY_ARMING)
1909 : 2) to allow motors to return to vertical (OPTION_DISARMED_TILT)
1910 : */
1911 3259693 : if (option_is_set(QuadPlane::Option::DISARMED_TILT) || option_is_set(QuadPlane::Option::DELAY_ARMING)) {
1912 0 : if (plane.arming.get_delay_arming()) {
1913 : // delay motor start after arming
1914 0 : set_desired_spool_state(AP_Motors::DesiredSpoolState::SHUT_DOWN);
1915 0 : motors->output();
1916 0 : return;
1917 : }
1918 : }
1919 :
1920 : #if AP_ADVANCEDFAILSAFE_ENABLED
1921 5998438 : if (!plane.arming.is_armed_and_safety_off() ||
1922 5998438 : (plane.afs.should_crash_vehicle() && !plane.afs.terminating_vehicle_via_landing()) ||
1923 2738745 : SRV_Channels::get_emergency_stop()) {
1924 : #else
1925 : if (!plane.arming.is_armed_and_safety_off() || SRV_Channels::get_emergency_stop()) {
1926 : #endif
1927 520948 : set_desired_spool_state(AP_Motors::DesiredSpoolState::SHUT_DOWN);
1928 520948 : motors->output();
1929 520948 : return;
1930 : }
1931 2738745 : if (esc_calibration && AP_Notify::flags.esc_calibration && plane.control_mode == &plane.mode_qstabilize) {
1932 : // output is direct from run_esc_calibration()
1933 0 : return;
1934 : }
1935 :
1936 2738745 : const uint32_t now = AP_HAL::millis();
1937 2738745 : if (tailsitter.in_vtol_transition(now) && !assisted_flight) {
1938 : /*
1939 : don't run the motor outputs while in tailsitter->vtol
1940 : transition. That is taken care of by the fixed wing
1941 : stabilisation code
1942 : */
1943 1219 : return;
1944 : }
1945 :
1946 2737526 : if (run_rate_controller) {
1947 2661945 : if (now - last_att_control_ms > 100) {
1948 : // relax if have been inactive
1949 281 : relax_attitude_control();
1950 : }
1951 :
1952 : // see if we need to be in VTOL recovery
1953 2661945 : assist.check_VTOL_recovery();
1954 :
1955 : // run low level rate controllers that only require IMU data and set loop time
1956 2661945 : const float last_loop_time_s = AP::scheduler().get_last_loop_time_s();
1957 2661945 : motors->set_dt_s(last_loop_time_s);
1958 2661945 : attitude_control->set_dt_s(last_loop_time_s);
1959 2661945 : pos_control->set_dt_s(last_loop_time_s);
1960 2661945 : attitude_control->rate_controller_run();
1961 : // reset sysid and other temporary inputs
1962 2661945 : attitude_control->rate_controller_target_reset();
1963 2661945 : last_att_control_ms = now;
1964 : }
1965 :
1966 : // see if motors should be shut down
1967 2737526 : update_throttle_suppression();
1968 :
1969 2737526 : motors->output();
1970 :
1971 : // Run thrust loss check, reset if motors have not been active
1972 2737526 : const bool motors_inactive = (now - last_motors_active_ms) > 100;
1973 2737526 : thrust_loss_check(motors_inactive);
1974 :
1975 : // remember when motors were last active for throttle suppression
1976 2737526 : if (motors->get_throttle() > 0.01f || tiltrotor.motors_active()) {
1977 2290934 : last_motors_active_ms = now;
1978 : }
1979 :
1980 : }
1981 :
1982 : /*
1983 : handle a MAVLink DO_VTOL_TRANSITION
1984 : */
1985 6 : bool QuadPlane::handle_do_vtol_transition(enum MAV_VTOL_STATE state) const
1986 : {
1987 6 : if (!available()) {
1988 0 : gcs().send_text(MAV_SEVERITY_NOTICE, "VTOL not available");
1989 0 : return false;
1990 : }
1991 6 : if (plane.control_mode != &plane.mode_auto) {
1992 0 : gcs().send_text(MAV_SEVERITY_NOTICE, "VTOL transition only in AUTO");
1993 0 : return false;
1994 : }
1995 6 : switch (state) {
1996 3 : case MAV_VTOL_STATE_MC:
1997 3 : if (!plane.auto_state.vtol_mode) {
1998 3 : gcs().send_text(MAV_SEVERITY_NOTICE, "Entered VTOL mode");
1999 : }
2000 3 : plane.auto_state.vtol_mode = true;
2001 : // This is a precaution. It should be looked after by the call to QuadPlane::mode_enter(void) on mode entry.
2002 3 : plane.quadplane.q_fwd_throttle = 0.0f;
2003 3 : plane.quadplane.q_fwd_pitch_lim_cd = 100.0f * plane.quadplane.q_fwd_pitch_lim;
2004 3 : return true;
2005 :
2006 3 : case MAV_VTOL_STATE_FW:
2007 3 : if (plane.auto_state.vtol_mode) {
2008 3 : gcs().send_text(MAV_SEVERITY_NOTICE, "Exited VTOL mode");
2009 : }
2010 3 : plane.auto_state.vtol_mode = false;
2011 :
2012 3 : return true;
2013 :
2014 0 : default:
2015 0 : break;
2016 : }
2017 :
2018 0 : gcs().send_text(MAV_SEVERITY_NOTICE, "Invalid VTOL mode");
2019 0 : return false;
2020 : }
2021 :
2022 : /*
2023 : are we in a VTOL auto state?
2024 : */
2025 247595824 : bool QuadPlane::in_vtol_auto(void) const
2026 : {
2027 247595824 : if (!available()) {
2028 6993797 : return false;
2029 : }
2030 240602030 : if (plane.control_mode != &plane.mode_auto) {
2031 138330909 : return false;
2032 : }
2033 102271121 : if (plane.auto_state.vtol_mode) {
2034 1773827 : return true;
2035 : }
2036 100497294 : uint16_t id = plane.mission.get_current_nav_cmd().id;
2037 100497282 : switch (id) {
2038 11783724 : case MAV_CMD_NAV_VTOL_TAKEOFF:
2039 11783724 : return true;
2040 2233760 : case MAV_CMD_NAV_LOITER_UNLIM:
2041 : case MAV_CMD_NAV_LOITER_TIME:
2042 : case MAV_CMD_NAV_LOITER_TURNS:
2043 : case MAV_CMD_NAV_LOITER_TO_ALT:
2044 2233760 : return plane.auto_state.vtol_loiter;
2045 6232762 : case MAV_CMD_NAV_TAKEOFF:
2046 6232762 : return is_vtol_takeoff(id);
2047 22744941 : case MAV_CMD_NAV_VTOL_LAND:
2048 : case MAV_CMD_NAV_LAND:
2049 : case MAV_CMD_NAV_PAYLOAD_PLACE:
2050 22744941 : return is_vtol_land(id);
2051 57502095 : default:
2052 57502095 : return false;
2053 : }
2054 : }
2055 :
2056 : /*
2057 : are we in a VTOL mode? This is used to decide if we run the
2058 : transition handling code or not
2059 :
2060 : note that AIRBRAKE is not considered in_vtol_mode even though the
2061 : VTOL motors are running
2062 : */
2063 76641966 : bool QuadPlane::in_vtol_mode(void) const
2064 : {
2065 76641966 : if (!available()) {
2066 6786345 : return false;
2067 : }
2068 69855621 : if (in_vtol_land_sequence()) {
2069 14161694 : return poscontrol.get_state() != QPOS_APPROACH && poscontrol.get_state() != QPOS_AIRBRAKE;
2070 : }
2071 55693927 : if (plane.control_mode->is_vtol_mode()) {
2072 18077066 : return true;
2073 : }
2074 37616861 : if (plane.control_mode->is_guided_mode()
2075 42138960 : && plane.auto_state.vtol_loiter &&
2076 4522099 : poscontrol.get_state() > QPOS_APPROACH) {
2077 4522099 : return true;
2078 : }
2079 33094762 : if (plane.control_mode == &plane.mode_guided &&
2080 2784106 : guided_takeoff) {
2081 0 : return true;
2082 : }
2083 33094762 : if (in_vtol_auto()) {
2084 4427492 : if (!plane.auto_state.vtol_loiter || poscontrol.get_state() > QPOS_AIRBRAKE) {
2085 4427492 : return true;
2086 : }
2087 : }
2088 28667270 : return false;
2089 : }
2090 :
2091 : /*
2092 : are we in a VTOL mode that needs position and velocity estimates?
2093 : */
2094 488395 : bool QuadPlane::in_vtol_posvel_mode(void) const
2095 : {
2096 488395 : if (!available()) {
2097 290897 : return false;
2098 : }
2099 379903 : return (plane.control_mode == &plane.mode_qloiter ||
2100 182405 : plane.control_mode == &plane.mode_qland ||
2101 173290 : plane.control_mode == &plane.mode_qrtl ||
2102 : #if QAUTOTUNE_ENABLED
2103 317364 : plane.control_mode == &plane.mode_qautotune ||
2104 : #endif
2105 156513 : (plane.control_mode->is_guided_mode() &&
2106 21013 : plane.auto_state.vtol_loiter &&
2107 379903 : poscontrol.get_state() > QPOS_APPROACH) ||
2108 343743 : in_vtol_auto());
2109 : }
2110 :
2111 : /*
2112 : update landing positioning offset
2113 : */
2114 862928 : void QuadPlane::update_land_positioning(void)
2115 : {
2116 862928 : if (!option_is_set(QuadPlane::Option::REPOSITION_LANDING)) {
2117 : // not enabled
2118 862928 : poscontrol.pilot_correction_active = false;
2119 862928 : poscontrol.target_vel_ms.zero();
2120 862928 : return;
2121 : }
2122 0 : const float scale = 1.0 / 4500;
2123 0 : float roll_in = plane.channel_roll->get_control_in() * scale;
2124 0 : float pitch_in = plane.channel_pitch->get_control_in() * scale;
2125 :
2126 : // limit correction speed to accel with stopping time constant of 0.5s
2127 0 : const float speed_max_ms = wp_nav->get_wp_acceleration_mss() * 0.5;
2128 0 : const float dt = plane.scheduler.get_loop_period_s();
2129 :
2130 0 : poscontrol.target_vel_ms = Vector3f(-pitch_in, roll_in, 0) * speed_max_ms;
2131 0 : poscontrol.target_vel_ms.rotate_xy(ahrs_view->yaw);
2132 :
2133 : // integrate our corrected position
2134 0 : poscontrol.correction_ne_m += poscontrol.target_vel_ms.xy() * dt;
2135 :
2136 0 : poscontrol.pilot_correction_active = (!is_zero(roll_in) || !is_zero(pitch_in));
2137 0 : if (poscontrol.pilot_correction_active) {
2138 0 : poscontrol.pilot_correction_done = true;
2139 : }
2140 : }
2141 :
2142 : /*
2143 : run (and possibly init) xy controller
2144 : */
2145 1341938 : void QuadPlane::run_xy_controller(float accel_limit_mss)
2146 : {
2147 1341938 : float accel_mss = wp_nav->get_wp_acceleration_mss();
2148 1341938 : if (is_positive(accel_limit_mss)) {
2149 : // allow for accel limit override
2150 802669 : accel_mss = MAX(accel_mss, accel_limit_mss);
2151 : }
2152 1341938 : const float speed_ms = wp_nav->get_default_speed_NE_ms();
2153 1341938 : pos_control->NE_set_max_speed_accel_m(speed_ms, accel_mss);
2154 1341938 : pos_control->NE_set_correction_speed_accel_m(speed_ms, accel_mss);
2155 1341938 : if (!pos_control->NE_is_active()) {
2156 110 : pos_control->NE_init_controller();
2157 : }
2158 1341938 : pos_control->set_lean_angle_max_cd(MIN(4500, MAX(accel_mss_to_angle_deg(accel_limit_mss) * 100, attitude_control->lean_angle_max_cd())));
2159 1341938 : if (q_fwd_throttle > 0.95f) {
2160 : // prevent wind up of the velocity controller I term due to a saturated forward throttle
2161 0 : pos_control->NE_set_externally_limited();
2162 : }
2163 1341938 : pos_control->NE_update_controller();
2164 1341938 : }
2165 :
2166 : /*
2167 : initialise QPOS_APPROACH
2168 : */
2169 131240 : void QuadPlane::poscontrol_init_approach(void)
2170 : {
2171 131240 : const float dist = plane.current_loc.get_distance(plane.next_WP_loc);
2172 262480 : if (option_is_set(QuadPlane::Option::DISABLE_APPROACH) ||
2173 131240 : (is_positive(approach_distance_m) && dist < approach_distance_m)) {
2174 : // go straight to QPOS_POSITION1
2175 0 : poscontrol.set_state(QPOS_POSITION1);
2176 0 : gcs().send_text(MAV_SEVERITY_INFO,"VTOL Position1 d=%.1f", dist);
2177 131240 : } else if (poscontrol.get_state() != QPOS_APPROACH) {
2178 : // check if we are close to the destination. We don't want to
2179 : // do a full approach when very close
2180 146 : if (dist < transition_threshold_m()) {
2181 126 : if (tailsitter.enabled() || motors->get_desired_spool_state() == AP_Motors::DesiredSpoolState::THROTTLE_UNLIMITED) {
2182 99 : gcs().send_text(MAV_SEVERITY_INFO,"VTOL Position1 d=%.1f", dist);
2183 99 : poscontrol.set_state(QPOS_POSITION1);
2184 99 : transition->set_last_fw_pitch();
2185 : } else {
2186 54 : gcs().send_text(MAV_SEVERITY_INFO,"VTOL airbrake v=%.1f d=%.0f sd=%.0f h=%.1f",
2187 27 : plane.ahrs.groundspeed(),
2188 : dist,
2189 27 : stopping_distance_m(),
2190 27 : plane.relative_ground_altitude(RangeFinderUse::TAKEOFF_LANDING));
2191 27 : poscontrol.set_state(QPOS_AIRBRAKE);
2192 : }
2193 : } else {
2194 20 : gcs().send_text(MAV_SEVERITY_INFO,"VTOL approach d=%.1f", dist);
2195 20 : poscontrol.set_state(QPOS_APPROACH);
2196 : }
2197 146 : poscontrol.thrust_loss_start_ms = 0;
2198 : }
2199 131240 : poscontrol.pilot_correction_done = false;
2200 131240 : poscontrol.correction_ne_m.zero();
2201 131240 : poscontrol.slow_descent = false;
2202 131240 : }
2203 :
2204 : #if HAL_LOGGING_ENABLED
2205 : /*
2206 : log the QPOS message
2207 : */
2208 106130 : void QuadPlane::log_QPOS(void)
2209 : {
2210 : // @LoggerMessage: QPOS
2211 : // @Description: Quadplane position data
2212 : // @Field: TimeUS: Time since system startup
2213 : // @Field: State: Position control state
2214 : // @FieldValueEnum: State: QuadPlane::position_control_state
2215 : // @Field: Dist: Distance to next waypoint
2216 : // @Field: TSpd: Target speed
2217 : // @Field: TAcc: Target acceleration
2218 : // @Field: OShoot: True if landing point is overshot or heading off by more than 60 degrees
2219 :
2220 212260 : AP::logger().WriteStreaming("QPOS", "TimeUS,State,Dist,TSpd,TAcc,OShoot", "QBfffB",
2221 : AP_HAL::micros64(),
2222 106130 : poscontrol.get_state(),
2223 106130 : plane.auto_state.wp_distance,
2224 106130 : poscontrol.target_speed_ms,
2225 106130 : poscontrol.target_accel_mss,
2226 106130 : poscontrol.overshoot);
2227 106130 : }
2228 : #endif
2229 :
2230 : /*
2231 : change position control state
2232 : */
2233 2430 : void QuadPlane::PosControlState::set_state(enum position_control_state s)
2234 : {
2235 2430 : const uint32_t now = AP_HAL::millis();
2236 2430 : if (state != s) {
2237 472 : auto &qp = plane.quadplane;
2238 472 : pilot_correction_done = false;
2239 : // handle resets needed for when the state changes
2240 472 : if (s == QPOS_POSITION1) {
2241 87 : reached_wp_speed = false;
2242 : // never do a rate reset, if attitude control is not active it will be automatically reset before running, see: last_att_control_ms
2243 : // if it is active then the rate control should not be reset at all
2244 87 : qp.attitude_control->reset_yaw_target_and_rate(false);
2245 87 : pos1_speed_limit_ms = plane.ahrs.groundspeed_vector().length();
2246 87 : done_accel_init = false;
2247 385 : } else if (s == QPOS_AIRBRAKE) {
2248 : // start with zero integrator on vertical throttle
2249 40 : qp.pos_control->D_get_accel_pid().set_integrator(0);
2250 345 : } else if (s == QPOS_LAND_DESCEND) {
2251 : // reset throttle descent control
2252 67 : qp.thr_ctrl_land = false;
2253 67 : qp.land_descend_start_alt_m = plane.current_loc.alt*0.01;
2254 67 : last_override_descent_ms = 0;
2255 278 : } else if (s == QPOS_LAND_ABORT) {
2256 : // reset throttle descent control
2257 0 : qp.thr_ctrl_land = false;
2258 278 : } else if (s == QPOS_LAND_FINAL) {
2259 : // remember last pos reset to handle GPS glitch in LAND_FINAL
2260 67 : ahrs_position_NE_reset_count = plane.ahrs.get_position_NE_reset_count();
2261 67 : qp.landing_detect.land_start_ms = 0;
2262 67 : qp.landing_detect.lower_limit_start_ms = 0;
2263 : }
2264 : // double log to capture the state change
2265 : #if HAL_LOGGING_ENABLED
2266 472 : qp.log_QPOS();
2267 : #endif
2268 472 : state = s;
2269 : #if HAL_LOGGING_ENABLED
2270 472 : qp.log_QPOS();
2271 : #endif
2272 472 : last_log_ms = now;
2273 472 : overshoot = false;
2274 : }
2275 2430 : last_state_change_ms = now;
2276 :
2277 : // we consider setting the state to be equivalent to running to
2278 : // prevent code from overriding the state as stale
2279 2430 : last_run_ms = now;
2280 2430 : }
2281 :
2282 : /*
2283 : main landing controller. Used for landing and RTL.
2284 : */
2285 1267474 : void QuadPlane::vtol_position_controller(void)
2286 : {
2287 1267474 : if (!setup()) {
2288 0 : return;
2289 : }
2290 :
2291 1267474 : const Location &loc = plane.next_WP_loc;
2292 1267474 : uint32_t now_ms = AP_HAL::millis();
2293 :
2294 : // distance that we switch to QPOS_POSITION2
2295 1267474 : const float position2_dist_threshold_m = 10.0;
2296 :
2297 : // target speed when we reach position2 threshold
2298 1267474 : const float position2_target_speed_ms = 3.0;
2299 :
2300 1267474 : if (plane.arming.is_armed_and_safety_off()) {
2301 1225361 : poscontrol.last_run_ms = now_ms;
2302 : }
2303 :
2304 : // avoid running the z controller in approach and airbrake if we're not already running it
2305 : // and tilt is more than tilt max
2306 1267474 : bool suppress_z_controller = false;
2307 :
2308 1267474 : Vector2f landing_velocity_ne_ms;
2309 1267474 : if (now_ms - poscontrol.last_velocity_match_ms < 1000) {
2310 13946 : landing_velocity_ne_ms = poscontrol.velocity_match_ms;
2311 : }
2312 :
2313 : // horizontal position control
2314 1267474 : switch (poscontrol.get_state()) {
2315 :
2316 0 : case QPOS_NONE:
2317 0 : poscontrol.set_state(QPOS_POSITION1);
2318 0 : INTERNAL_ERROR(AP_InternalError::error_t::flow_of_control);
2319 0 : break;
2320 :
2321 131114 : case QPOS_APPROACH:
2322 131114 : if (in_vtol_mode()) {
2323 : // this means we're not running transition update code and
2324 : // thus not doing qassist checking, force POSITION1 mode
2325 : // now. We don't expect this to trigger, it is a failsafe
2326 : // for a logic error
2327 0 : gcs().send_text(MAV_SEVERITY_INFO,"VTOL position1 nvtol");
2328 0 : poscontrol.set_state(QPOS_POSITION1);
2329 0 : INTERNAL_ERROR(AP_InternalError::error_t::flow_of_control);
2330 : }
2331 : FALLTHROUGH;
2332 :
2333 : case QPOS_AIRBRAKE: {
2334 : float aspeed_ms;
2335 157074 : const Vector2f closing_vel_ne_ms = landing_closing_velocity_NE_ms();
2336 157074 : const Vector2f desired_closing_vel_ne_ms = landing_desired_closing_velocity_NE_ms();
2337 157074 : const float groundspeed_ms = plane.ahrs.groundspeed();
2338 157074 : const float distance_m = plane.auto_state.wp_distance;
2339 157074 : const float closing_speed_ms = closing_vel_ne_ms.length();
2340 157074 : const float desired_closing_speed_ms = desired_closing_vel_ne_ms.length();
2341 157074 : if (!plane.ahrs.airspeed_EAS(aspeed_ms)) {
2342 0 : aspeed_ms = groundspeed_ms;
2343 : }
2344 :
2345 157074 : if (tiltrotor.enabled() && poscontrol.get_state() == QPOS_AIRBRAKE) {
2346 11648 : if ((now_ms - last_pidz_active_ms > 2000 && tiltrotor.tilt_over_max_angle()) ||
2347 4137 : tiltrotor.current_tilt >= tiltrotor.get_fully_forward_tilt()) {
2348 : // use low throttle stabilization when airbraking on a
2349 : // tiltrotor. We don't want quite zero throttle as we
2350 : // want some drag, but don't want to run the Z
2351 : // controller which can result in high throttle on
2352 : // motors that are tilted forward, thus increasing
2353 : // speed
2354 3374 : suppress_z_controller = true;
2355 3374 : hold_stabilize(0.01);
2356 : }
2357 : }
2358 :
2359 : // speed for crossover to POSITION1 controller
2360 157074 : const float aspeed_threshold_ms = MAX(plane.aparm.airspeed_min - 2, assist.speed);
2361 :
2362 : // run fixed wing navigation
2363 157074 : plane.nav_controller->update_waypoint(plane.auto_state.crosstrack ? plane.prev_WP_loc : plane.current_loc, loc);
2364 :
2365 : // use TECS for throttle
2366 157074 : SRV_Channels::set_output_scaled(SRV_Channel::k_throttle, plane.TECS_controller.get_throttle_demand());
2367 :
2368 : // use TECS for pitch
2369 157074 : int32_t commanded_pitch = plane.TECS_controller.get_pitch_demand();
2370 157074 : plane.nav_pitch_cd = constrain_int32(commanded_pitch, plane.pitch_limit_min*100, plane.aparm.pitch_limit_max.get()*100);
2371 157074 : if (poscontrol.get_state() == QPOS_AIRBRAKE) {
2372 : // don't allow down pitch in airbrake
2373 25960 : plane.nav_pitch_cd = MAX(plane.nav_pitch_cd, 0);
2374 : }
2375 :
2376 : // use nav controller roll
2377 157074 : plane.calc_nav_roll();
2378 :
2379 : // work out the point to enter airbrake mode. We want enough
2380 : // distance to stop, plus some margin for the time it takes to
2381 : // change the accel (jerk limit) plus the min time in airbrake
2382 : // mode. For simplicity we assume 2 seconds margin
2383 157074 : const float stop_distance = stopping_distance_m() + 2*closing_speed_ms;
2384 :
2385 157074 : if (!suppress_z_controller && poscontrol.get_state() == QPOS_AIRBRAKE) {
2386 22586 : hold_hover(0);
2387 : // don't run Z controller again in this loop
2388 22586 : suppress_z_controller = true;
2389 : }
2390 :
2391 : /*
2392 : see if we should start airbraking stage. For non-tailsitters
2393 : we can use the VTOL motors as airbrakes by firing them up
2394 : before we transition. This gives a smoother transition and
2395 : gives us a nice lot of deceleration
2396 : */
2397 157074 : if (poscontrol.get_state() == QPOS_APPROACH && distance_m < stop_distance) {
2398 20 : if (tailsitter.enabled() || motors->get_desired_spool_state() == AP_Motors::DesiredSpoolState::THROTTLE_UNLIMITED) {
2399 : // tailsitters don't use airbrake stage for landing
2400 1 : gcs().send_text(MAV_SEVERITY_INFO,"VTOL position1 v=%.1f d=%.0f sd=%.0f h=%.1f",
2401 : groundspeed_ms,
2402 1 : plane.auto_state.wp_distance,
2403 : stop_distance,
2404 1 : plane.relative_ground_altitude(RangeFinderUse::TAKEOFF_LANDING));
2405 1 : poscontrol.set_state(QPOS_POSITION1);
2406 1 : transition->set_last_fw_pitch();
2407 : } else {
2408 38 : gcs().send_text(MAV_SEVERITY_INFO,"VTOL airbrake v=%.1f d=%.0f sd=%.0f h=%.1f",
2409 : groundspeed_ms,
2410 : distance_m,
2411 : stop_distance,
2412 19 : plane.relative_ground_altitude(RangeFinderUse::TAKEOFF_LANDING));
2413 19 : poscontrol.set_state(QPOS_AIRBRAKE);
2414 : }
2415 : }
2416 :
2417 : /*
2418 : we must switch to POSITION1 if our airspeed drops below the
2419 : assist speed. We additionally switch to POSITION1 if we are
2420 : too far above our desired velocity profile, or our attitude
2421 : has deviated too much
2422 : */
2423 157074 : const int32_t attitude_error_threshold_cd = 1000;
2424 :
2425 : // use at least 1s of airbrake time to ensure motors have a chance to
2426 : // properly spin up
2427 157074 : const uint32_t min_airbrake_ms = 1000;
2428 183053 : if (poscontrol.get_state() == QPOS_AIRBRAKE &&
2429 183053 : poscontrol.time_since_state_start_ms() > min_airbrake_ms &&
2430 13963 : (aspeed_ms < aspeed_threshold_ms || // too low airspeed
2431 13949 : fabsf(degrees(closing_vel_ne_ms.angle(desired_closing_vel_ne_ms))) > 60 || // wrong direction
2432 171019 : closing_speed_ms > MAX(desired_closing_speed_ms*1.2, desired_closing_speed_ms+2) || // too fast
2433 13925 : closing_speed_ms < desired_closing_speed_ms*0.5 || // too slow ground speed
2434 13925 : labs(plane.ahrs.roll_sensor - plane.nav_roll_cd) > attitude_error_threshold_cd || // bad attitude
2435 13923 : labs(plane.ahrs.pitch_sensor - plane.nav_pitch_cd) > attitude_error_threshold_cd)) {
2436 40 : gcs().send_text(MAV_SEVERITY_INFO,"VTOL position1 v=%.1f d=%.1f h=%.1f dc=%.1f",
2437 : (double)groundspeed_ms,
2438 40 : (double)plane.auto_state.wp_distance,
2439 40 : plane.relative_ground_altitude(RangeFinderUse::TAKEOFF_LANDING),
2440 : desired_closing_speed_ms);
2441 40 : poscontrol.set_state(QPOS_POSITION1);
2442 40 : transition->set_last_fw_pitch();
2443 :
2444 : // switch to vfwd for throttle control
2445 40 : vel_forward.integrator = SRV_Channels::get_output_scaled(SRV_Channel::k_throttle);
2446 :
2447 : // adjust the initial forward throttle based on our desired and actual closing speed
2448 : // this allows for significant initial forward throttle
2449 : // when we have a strong headwind, but low throttle in the usual case where
2450 : // we want to slow down ready for POSITION2
2451 40 : vel_forward.integrator = linear_interpolate(0, vel_forward.integrator,
2452 : closing_speed_ms,
2453 : 1.2*desired_closing_speed_ms, 0.5*desired_closing_speed_ms);
2454 :
2455 : // limit our initial forward throttle in POSITION1 to be 0.5 of cruise throttle
2456 40 : vel_forward.integrator = constrain_float(vel_forward.integrator, 0, plane.aparm.throttle_cruise*0.5);
2457 :
2458 40 : vel_forward.last_ms = now_ms;
2459 : }
2460 :
2461 157074 : if (!tiltrotor.enabled() && !tailsitter.enabled()) {
2462 : /*
2463 : cope with fwd motor thrust loss during approach. We detect
2464 : this by looking for the fwd throttle saturating. This only
2465 : applies to separate lift-thrust vehicles
2466 : */
2467 130948 : bool throttle_saturated = SRV_Channels::get_output_scaled(SRV_Channel::k_throttle) >= plane.aparm.throttle_max;
2468 0 : if (throttle_saturated &&
2469 0 : motors->get_desired_spool_state() < AP_Motors::DesiredSpoolState::THROTTLE_UNLIMITED &&
2470 130948 : plane.auto_state.sink_rate > 0.2 && aspeed_ms < aspeed_threshold_ms + 4) {
2471 0 : if (poscontrol.thrust_loss_start_ms == 0) {
2472 0 : poscontrol.thrust_loss_start_ms = now_ms;
2473 : }
2474 0 : if (now_ms - poscontrol.thrust_loss_start_ms > 5000) {
2475 0 : gcs().send_text(MAV_SEVERITY_INFO,"VTOL pos1 thrust loss as=%.1f at=%.1f",
2476 : aspeed_ms, aspeed_threshold_ms);
2477 0 : poscontrol.set_state(QPOS_POSITION1);
2478 0 : transition->set_last_fw_pitch();
2479 : }
2480 : } else {
2481 130948 : poscontrol.thrust_loss_start_ms = 0;
2482 : }
2483 :
2484 : // handle loss of forward thrust in approach based on low airspeed detection
2485 130948 : if (poscontrol.get_state() == QPOS_APPROACH && aspeed_ms < aspeed_threshold_ms &&
2486 0 : motors->get_desired_spool_state() < AP_Motors::DesiredSpoolState::THROTTLE_UNLIMITED) {
2487 0 : gcs().send_text(MAV_SEVERITY_INFO,"VTOL pos1 low speed as=%.1f at=%.1f",
2488 : aspeed_ms, aspeed_threshold_ms);
2489 0 : poscontrol.set_state(QPOS_POSITION1);
2490 0 : transition->set_last_fw_pitch();
2491 : }
2492 : }
2493 :
2494 157074 : if (poscontrol.get_state() == QPOS_APPROACH) {
2495 131094 : poscontrol_init_approach();
2496 : }
2497 157074 : break;
2498 : }
2499 :
2500 217588 : case QPOS_POSITION1: {
2501 217588 : setup_target_position();
2502 :
2503 217588 : if (tailsitter.enabled() && tailsitter.in_vtol_transition(now_ms)) {
2504 379 : break;
2505 : }
2506 :
2507 217209 : const Vector2f wp_distance_ne_m = plane.current_loc.get_distance_NE(loc);
2508 217209 : const float wp_distance_m = wp_distance_ne_m.length();
2509 217209 : const Vector2f rel_groundspeed_vector_ne_ms = landing_closing_velocity_NE_ms();
2510 217209 : const float rel_groundspeed_sq = rel_groundspeed_vector_ne_ms.length_squared();
2511 217209 : float closing_groundspeed_ms = 0;
2512 :
2513 217209 : if (wp_distance_m > 0.1) {
2514 217198 : closing_groundspeed_ms = rel_groundspeed_vector_ne_ms * wp_distance_ne_m.normalized();
2515 : }
2516 :
2517 : // calculate speed we should be at to reach the position2
2518 : // target speed at the position2 distance threshold, assuming
2519 : // Q_TRANS_DECEL is correct
2520 217209 : const float stopping_speed_ms = safe_sqrt(MAX(0, wp_distance_m - position2_dist_threshold_m) * 2 * transition_decel_mss + sq(position2_target_speed_ms));
2521 :
2522 217209 : float approach_speed_ms = stopping_speed_ms;
2523 :
2524 : // maximum configured VTOL speed
2525 217209 : const float wp_speed_ms = MAX(1.0, wp_nav->get_default_speed_NE_ms());
2526 217209 : const float scaled_wp_speed_ms = get_scaled_wp_speed(degrees(wp_distance_ne_m.angle()));
2527 :
2528 : // limit target speed to a the pos1 speed limit, which starts out at the initial speed
2529 : // but is adjusted if we start putting our nose down. We always allow at least twice
2530 : // the WP speed
2531 217209 : approach_speed_ms = MIN(MAX(poscontrol.pos1_speed_limit_ms, 2 * wp_speed_ms), approach_speed_ms);
2532 :
2533 139462 : if (poscontrol.reached_wp_speed ||
2534 356671 : rel_groundspeed_sq < sq(wp_speed_ms) ||
2535 139439 : wp_speed_ms > 1.35*scaled_wp_speed_ms) {
2536 : // once we get below the Q_WP_SPD then we don't want to
2537 : // speed up again. At that point we should fly within the
2538 : // limits of the configured VTOL controller we also apply
2539 : // this limit when we are more than 45 degrees off the
2540 : // target in yaw, which is when we start to become
2541 : // unstable
2542 77776 : approach_speed_ms = MIN(approach_speed_ms, scaled_wp_speed_ms);
2543 77776 : poscontrol.reached_wp_speed = true;
2544 : }
2545 :
2546 : // run fixed wing navigation
2547 217209 : plane.nav_controller->update_waypoint(plane.current_loc, loc);
2548 :
2549 217209 : Vector2f target_speed_ne_ms;
2550 217209 : Vector2f target_accel_ne_mss;
2551 217209 : bool have_target_yaw = false;
2552 : float target_yaw_deg;
2553 217209 : const float approach_accel_mss = MIN(accel_needed(wp_distance_m, sq(closing_groundspeed_ms)), transition_decel_mss * 2);
2554 217209 : if (wp_distance_m > 0.1) {
2555 217198 : Vector2f diff_wp_norm = wp_distance_ne_m.normalized();
2556 217198 : target_speed_ne_ms = diff_wp_norm * approach_speed_ms;
2557 217198 : target_accel_ne_mss = diff_wp_norm * (-approach_accel_mss);
2558 217198 : target_yaw_deg = degrees(diff_wp_norm.angle());
2559 217198 : const float yaw_err_deg = wrap_180(target_yaw_deg - plane.ahrs.get_yaw_deg());
2560 217198 : bool overshoot = (closing_groundspeed_ms < 0 || fabsf(yaw_err_deg) > 60);
2561 217198 : if (overshoot && !poscontrol.overshoot) {
2562 14 : gcs().send_text(MAV_SEVERITY_INFO,"VTOL Overshoot d=%.1f cs=%.1f yerr=%.1f",
2563 : wp_distance_m, closing_groundspeed_ms, yaw_err_deg);
2564 14 : poscontrol.overshoot = true;
2565 14 : pos_control->set_accel_desired_NE_mss(Vector2f());
2566 : }
2567 217198 : if (poscontrol.overshoot) {
2568 : /* we have overshot the landing point or our nose is
2569 : off by more than 60 degrees. Zero target accel and
2570 : point nose at the landing point. Set target speed
2571 : to our position2 threshold speed
2572 : */
2573 70130 : target_accel_ne_mss.zero();
2574 :
2575 : // allow up to the WP speed when we are further away, slowing to the pos2 target speed
2576 : // when we are close
2577 70130 : approach_speed_ms = linear_interpolate(position2_target_speed_ms, wp_speed_ms,
2578 : wp_distance_m,
2579 : position2_dist_threshold_m*1.5,
2580 70130 : 2*position2_dist_threshold_m + stopping_distance_m(rel_groundspeed_sq));
2581 :
2582 70130 : target_speed_ne_ms = diff_wp_norm * approach_speed_ms;
2583 :
2584 : // adjust target yaw angle into the apparent wind
2585 70130 : Vector3f wind_ned_ms;
2586 : // use the estimate even if it is not marked valid, to
2587 : // preserve existing behaviour
2588 70130 : IGNORE_RETURN(plane.ahrs.get_wind(wind_ned_ms));
2589 70130 : const Vector2f wind_ms = wind_ned_ms.xy();
2590 70130 : const Vector2f airspeed_ne_ms = plane.ahrs.groundspeed_vector() - wind_ms;
2591 70130 : if (airspeed_ne_ms.length_squared() < 1) {
2592 : // Don't cause unpredictable yaw swings if the airspeed vector
2593 : // is very small; let the weathervane logic handle it instead.
2594 3098 : have_target_yaw = false;
2595 : } else {
2596 67032 : have_target_yaw = true;
2597 67032 : target_yaw_deg = degrees(airspeed_ne_ms.angle());
2598 : }
2599 : }
2600 : }
2601 217209 : const float target_speed_ms = target_speed_ne_ms.length();
2602 :
2603 217209 : target_speed_ne_ms += landing_velocity_ne_ms;
2604 217209 : poscontrol.target_speed_ms = target_speed_ms;
2605 217209 : poscontrol.target_accel_mss = approach_accel_mss;
2606 :
2607 139433 : if (!poscontrol.reached_wp_speed &&
2608 190556 : rel_groundspeed_sq < sq(target_speed_ms) &&
2609 407765 : rel_groundspeed_sq > sq(2*wp_speed_ms) &&
2610 48686 : plane.nav_pitch_cd < 0) {
2611 : // we have slowed down more than expected, likely due to
2612 : // drag from the props and we're starting to put our nose
2613 : // down as a result. We want to accept the slowdown and
2614 : // re-calculate the target speed profile
2615 12366 : poscontrol.pos1_speed_limit_ms = sqrtf(rel_groundspeed_sq);
2616 : }
2617 :
2618 : // use input shaping and abide by accel and jerk limits
2619 217209 : pos_control->input_vel_accel_NE_m(target_speed_ne_ms, target_accel_ne_mss);
2620 : // During POS1, we only want to control velocity and acceleration
2621 217209 : pos_control->NE_stop_pos_stabilisation();
2622 :
2623 : // run horizontal velocity controller
2624 217209 : run_xy_controller(MAX(approach_accel_mss, transition_decel_mss)*1.5);
2625 :
2626 217209 : if (!poscontrol.done_accel_init) {
2627 : /*
2628 : the pos controller init assumes zero accel, we need to
2629 : override that so that we can start decelerating more
2630 : quickly at the start of POSITION1
2631 : */
2632 78 : poscontrol.done_accel_init = true;
2633 78 : pos_control->set_accel_desired_NE_mss(target_accel_ne_mss);
2634 : }
2635 :
2636 : // nav roll and pitch are controller by position controller
2637 217209 : plane.nav_roll_cd = pos_control->get_roll_cd();
2638 217209 : plane.nav_pitch_cd = pos_control->get_pitch_cd();
2639 :
2640 217209 : assign_tilt_to_fwd_thr();
2641 :
2642 217209 : if (transition->set_VTOL_roll_pitch_limit(plane.nav_roll_cd, plane.nav_pitch_cd)) {
2643 20545 : pos_control->NE_set_externally_limited();
2644 : }
2645 :
2646 : // call attitude controller
2647 217209 : disable_yaw_rate_time_constant();
2648 :
2649 : // setup scaling of roll and pitch angle P gains to match fixed wing gains
2650 217209 : setup_rp_fw_angle_gains();
2651 :
2652 217209 : if (have_target_yaw) {
2653 67032 : attitude_control->input_euler_angle_roll_pitch_yaw_cd(plane.nav_roll_cd,
2654 67032 : plane.nav_pitch_cd,
2655 : target_yaw_deg * 100, true);
2656 : } else {
2657 300354 : attitude_control->input_euler_angle_roll_pitch_euler_rate_yaw_cd(plane.nav_roll_cd,
2658 150177 : plane.nav_pitch_cd,
2659 150177 : desired_auto_yaw_rate_cds() + get_weathervane_yaw_rate_cds());
2660 : }
2661 217673 : if ((plane.auto_state.wp_distance < position2_dist_threshold_m) && tiltrotor.tilt_angle_achieved() &&
2662 464 : fabsf(rel_groundspeed_sq) < sq(3 * position2_target_speed_ms)) {
2663 : // if continuous tiltrotor only advance to position 2 once tilts have finished moving
2664 78 : poscontrol.set_state(QPOS_POSITION2);
2665 78 : poscontrol.pilot_correction_done = false;
2666 156 : gcs().send_text(MAV_SEVERITY_INFO,"VTOL position2 started v=%.1f d=%.1f h=%.1f",
2667 78 : (double)ahrs.groundspeed(), (double)plane.auto_state.wp_distance,
2668 78 : plane.relative_ground_altitude(RangeFinderUse::TAKEOFF_LANDING));
2669 : }
2670 217209 : break;
2671 : }
2672 :
2673 585460 : case QPOS_POSITION2:
2674 : case QPOS_LAND_ABORT:
2675 : case QPOS_LAND_DESCEND: {
2676 585460 : setup_target_position();
2677 : /*
2678 : for final land repositioning and descent we run the position controller
2679 : */
2680 585460 : Vector2f zero;
2681 585460 : Vector2f vel_ne_ms = poscontrol.target_vel_ms.xy() + landing_velocity_ne_ms;
2682 585460 : pos_control->input_pos_vel_accel_NE_m(poscontrol.target_ned_m.xy(), vel_ne_ms, zero);
2683 :
2684 : // also run fixed wing navigation
2685 585460 : plane.nav_controller->update_waypoint(plane.current_loc, loc);
2686 :
2687 585460 : update_land_positioning();
2688 :
2689 585460 : run_xy_controller(transition_decel_mss*1.5);
2690 :
2691 : // nav roll and pitch are controlled by position controller
2692 585460 : plane.nav_roll_cd = pos_control->get_roll_cd();
2693 585460 : plane.nav_pitch_cd = pos_control->get_pitch_cd();
2694 :
2695 585460 : assign_tilt_to_fwd_thr();
2696 :
2697 585460 : if (transition->set_VTOL_roll_pitch_limit(plane.nav_roll_cd, plane.nav_pitch_cd)) {
2698 589 : pos_control->NE_set_externally_limited();
2699 : }
2700 :
2701 : // call attitude controller
2702 585460 : set_pilot_yaw_rate_time_constant();
2703 1170920 : attitude_control->input_euler_angle_roll_pitch_euler_rate_yaw_cd(plane.nav_roll_cd,
2704 585460 : plane.nav_pitch_cd,
2705 585460 : get_pilot_input_yaw_rate_cds() + get_weathervane_yaw_rate_cds());
2706 585460 : break;
2707 : }
2708 :
2709 277468 : case QPOS_LAND_FINAL:
2710 277468 : setup_target_position();
2711 277468 : update_land_positioning();
2712 :
2713 : // relax when close to the ground
2714 277468 : if (should_relax()) {
2715 60956 : pos_control->NE_relax_velocity_controller();
2716 : } else {
2717 216512 : Vector2f zero;
2718 216512 : Vector2f vel_ne_ms = poscontrol.target_vel_ms.xy() + landing_velocity_ne_ms;
2719 216512 : const uint16_t last_reset_count = plane.ahrs.get_position_NE_reset_count();
2720 : /* we use velocity control when we may be touching the
2721 : ground or if we've had a position reset from AHRS. This
2722 : helps us handle a GPS glitch in the final land phase,
2723 : and also prevents trying to reposition after touchdown
2724 : */
2725 634756 : if (motors->limit.throttle_lower ||
2726 411334 : motors->get_throttle() < 0.5*motors->get_throttle_hover() ||
2727 194822 : last_reset_count != poscontrol.ahrs_position_NE_reset_count) {
2728 21690 : pos_control->input_vel_accel_NE_m(vel_ne_ms, zero);
2729 : } else {
2730 : // otherwise use full pos control
2731 194822 : pos_control->input_pos_vel_accel_NE_m(poscontrol.target_ned_m.xy(), vel_ne_ms, zero);
2732 : }
2733 : }
2734 :
2735 277468 : run_xy_controller();
2736 :
2737 : // nav roll and pitch are controller by position controller
2738 277468 : plane.nav_roll_cd = pos_control->get_roll_cd();
2739 277468 : plane.nav_pitch_cd = pos_control->get_pitch_cd();
2740 :
2741 277468 : assign_tilt_to_fwd_thr();
2742 :
2743 : // call attitude controller
2744 277468 : set_pilot_yaw_rate_time_constant();
2745 554936 : attitude_control->input_euler_angle_roll_pitch_euler_rate_yaw_cd(plane.nav_roll_cd,
2746 277468 : plane.nav_pitch_cd,
2747 277468 : get_pilot_input_yaw_rate_cds() + get_weathervane_yaw_rate_cds());
2748 277468 : break;
2749 :
2750 29884 : case QPOS_LAND_COMPLETE:
2751 : // nothing to do
2752 29884 : break;
2753 : }
2754 :
2755 : // now height control
2756 1267474 : switch (poscontrol.get_state()) {
2757 0 : case QPOS_NONE:
2758 0 : poscontrol.set_state(QPOS_POSITION1);
2759 0 : INTERNAL_ERROR(AP_InternalError::error_t::flow_of_control);
2760 0 : break;
2761 :
2762 157033 : case QPOS_APPROACH:
2763 : case QPOS_AIRBRAKE:
2764 : // we just want stability from the VTOL controller in these
2765 : // phases of landing, so relax the Z controller, unless we are
2766 : // providing assistance
2767 157033 : if (transition->complete()) {
2768 131134 : pos_control->D_relax_controller(0);
2769 : }
2770 157033 : break;
2771 217551 : case QPOS_POSITION1:
2772 217551 : if (tailsitter.in_vtol_transition(now_ms)) {
2773 380 : pos_control->D_relax_controller(0);
2774 380 : break;
2775 : }
2776 : FALLTHROUGH;
2777 : case QPOS_POSITION2: {
2778 491702 : bool vtol_loiter_auto = false;
2779 491702 : if (plane.control_mode == &plane.mode_auto) {
2780 151112 : switch (plane.mission.get_current_nav_cmd().id) {
2781 0 : case MAV_CMD_NAV_LOITER_UNLIM:
2782 : case MAV_CMD_NAV_LOITER_TIME:
2783 : case MAV_CMD_NAV_LOITER_TURNS:
2784 : case MAV_CMD_NAV_LOITER_TO_ALT:
2785 0 : vtol_loiter_auto = true;
2786 0 : break;
2787 : }
2788 : }
2789 491702 : if (plane.control_mode == &plane.mode_guided || vtol_loiter_auto) {
2790 : // FIXME: we have updated current_loc without updating the
2791 : // health flag. And why are we doing this here?!
2792 257498 : UNUSED_RESULT(plane.ahrs.get_location(plane.current_loc));
2793 : int32_t target_altitude_cm;
2794 257498 : if (!plane.next_WP_loc.get_alt_cm(Location::AltFrame::ABOVE_ORIGIN,target_altitude_cm)) {
2795 2767 : break;
2796 : }
2797 383621 : if (poscontrol.slow_descent &&
2798 128890 : plane.prev_WP_loc.get_distance(plane.next_WP_loc) > 50) {
2799 : // gradually descend as we approach target
2800 115731 : plane.auto_state.wp_proportion = plane.current_loc.line_path_proportion(plane.prev_WP_loc, plane.next_WP_loc);
2801 : int32_t prev_alt_cm;
2802 115731 : if (plane.prev_WP_loc.get_alt_cm(Location::AltFrame::ABOVE_ORIGIN,prev_alt_cm)) {
2803 115731 : target_altitude_cm = linear_interpolate(prev_alt_cm,
2804 : target_altitude_cm,
2805 : plane.auto_state.wp_proportion,
2806 : 0, 1);
2807 : }
2808 : }
2809 254731 : float zero = 0;
2810 254731 : float target_d_m = -target_altitude_cm * 0.01;
2811 254731 : pos_control->input_pos_vel_accel_D_m(target_d_m, zero, 0);
2812 488935 : } else if (plane.control_mode == &plane.mode_qrtl) {
2813 83092 : Location loc2 = loc;
2814 83092 : loc2.change_alt_frame(Location::AltFrame::ABOVE_ORIGIN);
2815 83092 : float target_d_m = -(loc2.alt * 0.01);
2816 83092 : float zero = 0;
2817 83092 : pos_control->input_pos_vel_accel_D_m(target_d_m, zero, 0);
2818 : } else {
2819 151112 : set_climb_rate_ms(0);
2820 : }
2821 488935 : break;
2822 : }
2823 :
2824 588475 : case QPOS_LAND_DESCEND:
2825 : case QPOS_LAND_ABORT:
2826 : case QPOS_LAND_FINAL: {
2827 588475 : float height_above_ground_m = plane.relative_ground_altitude(RangeFinderUse::TAKEOFF_LANDING);
2828 588475 : if (poscontrol.get_state() == QPOS_LAND_FINAL) {
2829 277468 : if (!option_is_set(QuadPlane::Option::DISABLE_GROUND_EFFECT_COMP)) {
2830 277468 : ahrs.set_touchdown_expected(true);
2831 : }
2832 : }
2833 588475 : if (poscontrol.get_state() == QPOS_LAND_ABORT) {
2834 0 : set_climb_rate_ms(wp_nav->get_default_speed_up_ms());
2835 0 : break;
2836 : }
2837 588475 : const float descent_rate_ms = landing_descent_rate_ms(height_above_ground_m);
2838 588475 : pos_control->D_set_pos_target_from_climb_rate_ms(-descent_rate_ms, descent_rate_ms > 0);
2839 588475 : break;
2840 : }
2841 :
2842 29884 : case QPOS_LAND_COMPLETE:
2843 29884 : break;
2844 : }
2845 :
2846 : /*
2847 : run the z controller unless something has already run it or set a target throttle
2848 : */
2849 1267474 : if (!suppress_z_controller) {
2850 : // otherwise run z controller
2851 1241514 : run_z_controller();
2852 : }
2853 :
2854 : #if HAL_LOGGING_ENABLED
2855 1267474 : if (now_ms - poscontrol.last_log_ms >= 40) {
2856 : // log poscontrol at 25Hz
2857 105186 : poscontrol.last_log_ms = now_ms;
2858 105186 : log_QPOS();
2859 : }
2860 : #endif
2861 : }
2862 :
2863 : /*
2864 : determine which fwd throttle handling method is active
2865 : */
2866 6846771 : QuadPlane::ActiveFwdThr QuadPlane::get_vfwd_method(void) const
2867 : {
2868 : // May not be enabled by type spesfic transition logic
2869 6846771 : if (!transition->allow_vfwd()) {
2870 17499 : return ActiveFwdThr::NONE;
2871 : }
2872 :
2873 6829272 : const bool have_fwd_thr_gain = is_positive(q_fwd_thr_gain);
2874 6829272 : const bool have_vfwd_gain = is_positive(vel_forward.gain);
2875 :
2876 : #if AP_ICENGINE_ENABLED
2877 6829272 : const auto ice_state = plane.g2.ice_control.get_state();
2878 6829272 : if (ice_state != AP_ICEngine::ICE_DISABLED && ice_state != AP_ICEngine::ICE_RUNNING) {
2879 : // we need the engine running for fwd throttle
2880 100181 : return ActiveFwdThr::NONE;
2881 : }
2882 : #endif
2883 :
2884 : #if QAUTOTUNE_ENABLED
2885 6729091 : if (plane.control_mode == &plane.mode_qautotune) {
2886 259048 : return ActiveFwdThr::NONE;
2887 : }
2888 : #endif
2889 :
2890 6470043 : if (have_fwd_thr_gain) {
2891 6470043 : if (vfwd_enable_active) {
2892 : // user has used AUX function to activate new method
2893 0 : return ActiveFwdThr::NEW;
2894 : }
2895 6470043 : if (q_fwd_thr_use == FwdThrUse::ALL) {
2896 41896 : return ActiveFwdThr::NEW;
2897 : }
2898 6428147 : if (q_fwd_thr_use == FwdThrUse::POSCTRL && pos_control->NE_is_active()) {
2899 0 : return ActiveFwdThr::NEW;
2900 : }
2901 : }
2902 6428147 : if (have_vfwd_gain && pos_control->NE_is_active()) {
2903 265499 : return ActiveFwdThr::OLD;
2904 : }
2905 6162648 : return ActiveFwdThr::NONE;
2906 : }
2907 :
2908 : /*
2909 : map from pitch tilt to fwd throttle when enabled
2910 : */
2911 2171921 : void QuadPlane::assign_tilt_to_fwd_thr(void)
2912 : {
2913 :
2914 2171921 : const auto fwd_thr_active = get_vfwd_method();
2915 2171921 : if (fwd_thr_active != ActiveFwdThr::NEW) {
2916 2150855 : q_fwd_throttle = 0.0f;
2917 2150855 : q_fwd_pitch_lim_cd = 100.0f * q_fwd_pitch_lim;
2918 2150855 : return;
2919 : }
2920 : // Handle the case where we are limiting the forward pitch angle to prevent negative wing lift
2921 : // and are using the forward thrust motor or tilting rotors to provide the forward acceleration
2922 21066 : float fwd_tilt_rad = radians(constrain_float(-0.01f * (float)plane.nav_pitch_cd, 0.0f, 45.0f));
2923 21066 : q_fwd_throttle = MIN(q_fwd_thr_gain * tanf(fwd_tilt_rad), 1.0f);
2924 :
2925 : // Relax forward tilt limit if the position controller is saturating in the forward direction because
2926 : // the forward thrust motor could be failed. Do not do this with tilt rotors because they do not rely on
2927 : // forward throttle during VTOL flight
2928 21066 : const float angle_max_cd = attitude_control->lean_angle_max_cd();
2929 21066 : if (!tiltrotor.enabled()) {
2930 21066 : const float fwd_tilt_range_cd = angle_max_cd - 100.0f * q_fwd_pitch_lim;
2931 21066 : if (is_positive(fwd_tilt_range_cd)) {
2932 : // rate limit the forward tilt change to slew between the motor good and motor failed
2933 : // value over 10 seconds
2934 21066 : const bool fwd_limited = plane.quadplane.pos_control->NE_is_active() and plane.quadplane.pos_control->get_fwd_pitch_is_limited();
2935 21066 : const float fwd_pitch_lim_cd_tgt = fwd_limited ? angle_max_cd : 100.0f * q_fwd_pitch_lim;
2936 21066 : const float delta_max = 0.1f * fwd_tilt_range_cd * plane.G_Dt;
2937 21066 : q_fwd_pitch_lim_cd += constrain_float((fwd_pitch_lim_cd_tgt - q_fwd_pitch_lim_cd), -delta_max, delta_max);
2938 : // Don't let the forward pitch limit be more than the forward pitch demand before limiting to
2939 : // avoid opening up the limit more than necessary
2940 21066 : q_fwd_pitch_lim_cd = MIN(q_fwd_pitch_lim_cd, MAX(-(float)plane.nav_pitch_cd, 100.0f * q_fwd_pitch_lim));
2941 : } else {
2942 : // take the lesser of the two limits
2943 0 : q_fwd_pitch_lim_cd = angle_max_cd;
2944 : }
2945 : }
2946 :
2947 : // Prevent the wing from being overloaded when braking from high speed in a VTOL mode
2948 21066 : float nav_pitch_upper_limit_cd = 100.0f * q_bck_pitch_lim;
2949 : float aspeed;
2950 21066 : if (is_positive(q_bck_pitch_lim) && ahrs.airspeed_EAS(aspeed)) {
2951 21066 : const float reference_speed = MAX(plane.aparm.airspeed_min, MIN_AIRSPEED_MIN);
2952 21066 : float speed_scaler = sq(reference_speed / MAX(aspeed, 0.1f));
2953 21066 : nav_pitch_upper_limit_cd *= speed_scaler;
2954 21066 : nav_pitch_upper_limit_cd = MIN(nav_pitch_upper_limit_cd, angle_max_cd);
2955 :
2956 21066 : const float tconst = 0.5f;
2957 21066 : const float dt = AP_HAL::millis() - q_pitch_limit_update_ms;
2958 21066 : q_pitch_limit_update_ms = AP_HAL::millis();
2959 21066 : if (is_positive(dt)) {
2960 21066 : const float coef = dt / (dt + tconst);
2961 21066 : q_bck_pitch_lim_cd = (1.0f - coef) * q_bck_pitch_lim_cd + coef * nav_pitch_upper_limit_cd;
2962 : }
2963 :
2964 21066 : plane.nav_pitch_cd = MIN(plane.nav_pitch_cd, (int32_t)q_bck_pitch_lim_cd);
2965 :
2966 : #if HAL_LOGGING_ENABLED
2967 : // @LoggerMessage: QBRK
2968 : // @Description: Quadplane Braking
2969 : // @Field: TimeUS: Time since system startup
2970 : // @Field: SpdScaler: braking speed scaler
2971 : // @Field: NPULCD: upper limit for navigation pitch
2972 : // @Field: QBPLCD: upper limit for back transition pitch
2973 : // @Field: NPCD: demanded navigation pitch
2974 42132 : AP::logger().WriteStreaming("QBRK",
2975 : "TimeUS,SpdScaler,NPULCD,QBPLCD,NPCD", // labels
2976 : "Qffii", // fmt
2977 : AP_HAL::micros64(),
2978 : (double)speed_scaler,
2979 : (double)nav_pitch_upper_limit_cd,
2980 21066 : (int32_t)q_bck_pitch_lim_cd,
2981 21066 : (int32_t)plane.nav_pitch_cd);
2982 : #endif
2983 : }
2984 :
2985 : float fwd_thr_scaler;
2986 21066 : if (!in_vtol_land_approach()) {
2987 : // To prevent forward motor prop strike, reduce throttle to zero when close to ground.
2988 21066 : float alt_cutoff_m = MAX(0, vel_forward_alt_cutoff_m);
2989 21066 : float height_above_ground_m = plane.relative_ground_altitude(RangeFinderUse::TAKEOFF_LANDING);
2990 21066 : fwd_thr_scaler = linear_interpolate(0.0f, 1.0f, height_above_ground_m, alt_cutoff_m, alt_cutoff_m + 2);
2991 : } else {
2992 : // When we are doing horizontal positioning in a VTOL land we always allow the fwd motor
2993 : // to run. Otherwise a bad height above landing point estimate could cause the aircraft
2994 : // not to be able to approach the landing point
2995 0 : fwd_thr_scaler = 1.0f;
2996 : }
2997 21066 : q_fwd_throttle *= fwd_thr_scaler;
2998 :
2999 : // When reducing forward throttle use, relax lower pitch limit to maintain forward
3000 : // acceleration capability.
3001 21066 : const float nav_pitch_lower_limit_cd = - (int32_t)(angle_max_cd * (1.0f - fwd_thr_scaler) + q_fwd_pitch_lim_cd * fwd_thr_scaler);
3002 :
3003 : #if HAL_LOGGING_ENABLED
3004 : // Diagnostics logging - remove when feature is fully flight tested.
3005 : // @LoggerMessage: FWDT
3006 : // @Description: Forward Throttle calculations
3007 : // @Field: TimeUS: Time since system startup
3008 : // @Field: fts: forward throttle scaler
3009 : // @Field: qfplcd: quadplane forward pitch limit
3010 : // @Field: npllcd: navigation pitch lower limit
3011 : // @Field: npcd: demanded navigation pitch
3012 : // @Field: qft: quadplane forward throttle
3013 : // @Field: npulcd: upper limit for navigation pitch
3014 42132 : AP::logger().WriteStreaming("FWDT",
3015 : "TimeUS,fts,qfplcd,npllcd,npcd,qft,npulcd", // labels
3016 : "Qffffff", // fmt
3017 : AP_HAL::micros64(),
3018 : (double)fwd_thr_scaler,
3019 21066 : (double)q_fwd_pitch_lim_cd,
3020 : (double)nav_pitch_lower_limit_cd,
3021 21066 : (double)plane.nav_pitch_cd,
3022 21066 : (double)q_fwd_throttle,
3023 : (float)nav_pitch_upper_limit_cd);
3024 : #endif
3025 :
3026 21066 : plane.nav_pitch_cd = MAX(plane.nav_pitch_cd, (int32_t)nav_pitch_lower_limit_cd);
3027 : }
3028 :
3029 : /*
3030 : we want to limit WP speed to a lower speed when more than 20 degrees
3031 : off pointing at the destination. quadplanes are often
3032 : unstable when flying sideways or backwards
3033 : */
3034 217209 : float QuadPlane::get_scaled_wp_speed(float target_bearing_deg) const
3035 : {
3036 217209 : const float yaw_difference = fabsf(wrap_180(plane.ahrs.get_yaw_deg() - target_bearing_deg));
3037 217209 : const float wp_speed_ms = wp_nav->get_default_speed_NE_ms();
3038 217209 : if (yaw_difference > 20) {
3039 : // this gives a factor of 2x reduction in max speed when
3040 : // off by 90 degrees, and 3x when off by 180 degrees
3041 33162 : const float speed_reduction = linear_interpolate(1, 3,
3042 : yaw_difference,
3043 : 20, 160);
3044 33162 : return wp_speed_ms / speed_reduction;
3045 : }
3046 184047 : return wp_speed_ms;
3047 : }
3048 :
3049 : /*
3050 : setup the target position based on plane.next_WP_loc
3051 : */
3052 1369006 : void QuadPlane::setup_target_position(void)
3053 : {
3054 1369006 : const Location &loc = plane.next_WP_loc;
3055 1369006 : Location origin;
3056 1369006 : if (!ahrs.get_origin(origin)) {
3057 2769 : origin.zero();
3058 : }
3059 2738012 : if ((!in_vtol_land_approach() || poscontrol.get_state() > QPOS_APPROACH) &&
3060 1369006 : poscontrol.get_state() != QPOS_LAND_FINAL) {
3061 1091521 : set_desired_spool_state(AP_Motors::DesiredSpoolState::THROTTLE_UNLIMITED);
3062 : }
3063 :
3064 1369006 : Vector2f diff2d = origin.get_distance_NE(loc);
3065 1369006 : diff2d += poscontrol.correction_ne_m;
3066 1369006 : poscontrol.target_ned_m.x = diff2d.x;
3067 1369006 : poscontrol.target_ned_m.y = diff2d.y;
3068 1369006 : poscontrol.target_ned_m.z = -(plane.next_WP_loc.alt - origin.alt) * 0.01;
3069 :
3070 : // set vertical speed and acceleration limits
3071 : // All limits must be positive
3072 1369006 : pos_control->D_set_max_speed_accel_m(get_pilot_velocity_z_max_dn_m(), pilot_speed_z_max_up_ms, pilot_accel_z_mss);
3073 1369006 : pos_control->D_set_correction_speed_accel_m(get_pilot_velocity_z_max_dn_m(), pilot_speed_z_max_up_ms, pilot_accel_z_mss);
3074 1369006 : }
3075 :
3076 : /*
3077 : run takeoff controller to climb vertically
3078 : */
3079 348529 : void QuadPlane::takeoff_controller(void)
3080 : {
3081 : // reset fixed wing controller to neutral as base output
3082 348529 : plane.nav_roll_cd = 0;
3083 348529 : plane.nav_pitch_cd = 0;
3084 :
3085 : #if HAL_WITH_ESC_TELEM
3086 : // calling this early so it can clear its warning timer when disarmed
3087 348529 : const bool motor_check_passed = plane.motors_takeoff_check(takeoff_rpm_min, takeoff_rpm_max);
3088 : #endif
3089 :
3090 348529 : if (!plane.arming.is_armed_and_safety_off()) {
3091 86728 : return;
3092 : }
3093 :
3094 266344 : uint32_t now = AP_HAL::millis();
3095 266344 : const auto spool_state = motors->get_desired_spool_state();
3096 : // Don't allow takeoff until it is safe to do so
3097 266344 : if (spool_state != AP_Motors::DesiredSpoolState::THROTTLE_UNLIMITED) {
3098 : // Waiting for motors to tilt up
3099 0 : if (plane.control_mode == &plane.mode_guided && guided_takeoff
3100 4586 : && tiltrotor.enabled() && !tiltrotor.fully_up()) {
3101 0 : takeoff_start_time_ms = now;
3102 0 : return;
3103 : }
3104 : #if HAL_WITH_ESC_TELEM
3105 : // Waiting for motors to reach the expected RPM
3106 4586 : if (!motor_check_passed) {
3107 : // start motors spinning if not spinning already so RPM check can work
3108 1381 : set_desired_spool_state(AP_Motors::DesiredSpoolState::GROUND_IDLE);
3109 1381 : takeoff_start_time_ms = now;
3110 1381 : return;
3111 : }
3112 : #endif
3113 : // Waiting for stick to be re-centered after rudder arming
3114 3205 : if (plane.arming.last_arm_method() == AP_Arming::Method::RUDDER &&
3115 3163 : (takeoff_last_run_ms == 0 ||
3116 6368 : now - takeoff_last_run_ms > 1000) &&
3117 3163 : !rc().seen_neutral_rudder()) {
3118 : // start motor spinning if not spinning already so user sees it is armed
3119 3162 : set_desired_spool_state(AP_Motors::DesiredSpoolState::GROUND_IDLE);
3120 3162 : takeoff_start_time_ms = now;
3121 3162 : if (now - plane.takeoff_state.rudder_takeoff_warn_ms > TAKEOFF_RUDDER_WARNING_TIMEOUT) {
3122 3 : gcs().send_text(MAV_SEVERITY_WARNING, "Takeoff waiting for rudder release");
3123 3 : plane.takeoff_state.rudder_takeoff_warn_ms = now;
3124 : }
3125 3162 : return;
3126 : }
3127 : }
3128 :
3129 :
3130 : /*
3131 : for takeoff we use the position controller
3132 : */
3133 261801 : setup_target_position();
3134 :
3135 : // set position control target and update
3136 :
3137 261801 : Vector2f vel_ne_ms, zero;
3138 261801 : if (AP_HAL::millis() - poscontrol.last_velocity_match_ms < 1000) {
3139 12447 : vel_ne_ms = poscontrol.velocity_match_ms;
3140 : }
3141 :
3142 : /*
3143 : support zeroing roll/pitch during early part of takeoff. This
3144 : can help particularly with poor GPS velocity data
3145 : */
3146 261801 : bool no_navigation = false;
3147 261801 : if (takeoff_navalt_min_m > 0) {
3148 0 : const float alt_m = plane.current_loc.alt * 0.01;
3149 0 : if (takeoff_last_run_ms == 0 ||
3150 0 : now - takeoff_last_run_ms > 1000) {
3151 0 : takeoff_start_alt_m = alt_m;
3152 : }
3153 0 : if (alt_m - takeoff_start_alt_m < takeoff_navalt_min_m) {
3154 0 : no_navigation = true;
3155 : }
3156 : }
3157 261801 : takeoff_last_run_ms = now;
3158 :
3159 261801 : if (no_navigation) {
3160 0 : pos_control->NE_relax_velocity_controller();
3161 : } else {
3162 261801 : pos_control->input_vel_accel_NE_m(vel_ne_ms, zero);
3163 :
3164 : // nav roll and pitch are controller by position controller
3165 261801 : plane.nav_roll_cd = pos_control->get_roll_cd();
3166 261801 : plane.nav_pitch_cd = pos_control->get_pitch_cd();
3167 :
3168 261801 : assign_tilt_to_fwd_thr();
3169 : }
3170 :
3171 261801 : run_xy_controller();
3172 :
3173 261801 : set_pilot_yaw_rate_time_constant();
3174 523602 : attitude_control->input_euler_angle_roll_pitch_euler_rate_yaw_cd(plane.nav_roll_cd,
3175 261801 : plane.nav_pitch_cd,
3176 261801 : get_pilot_input_yaw_rate_cds() + get_weathervane_yaw_rate_cds());
3177 :
3178 261801 : float vel_u_ms = wp_nav->get_default_speed_up_ms();
3179 261801 : if (plane.control_mode == &plane.mode_guided && guided_takeoff) {
3180 : // for guided takeoff we aim for a specific height with zero
3181 : // velocity at that height
3182 52242 : Location origin;
3183 52242 : if (ahrs.get_origin(origin)) {
3184 : // a small margin to ensure we do move to the next takeoff
3185 : // stage
3186 52242 : const int32_t margin_cm = 5;
3187 52242 : float pos_d_m = -(margin_cm + plane.next_WP_loc.alt - origin.alt) * 0.01;
3188 52242 : float vel_d_ms = 0.0;
3189 52242 : pos_control->input_pos_vel_accel_D_m(pos_d_m, vel_d_ms, 0);
3190 : } else {
3191 0 : set_climb_rate_ms(vel_u_ms);
3192 : }
3193 52242 : } else {
3194 209559 : set_climb_rate_ms(vel_u_ms);
3195 : }
3196 :
3197 261801 : run_z_controller();
3198 : }
3199 :
3200 : /*
3201 : run waypoint controller between prev_WP_loc and next_WP_loc
3202 : */
3203 26559 : void QuadPlane::waypoint_controller(void)
3204 : {
3205 26559 : setup_target_position();
3206 :
3207 26559 : const Location &loc = plane.next_WP_loc;
3208 26559 : const uint32_t now = AP_HAL::millis();
3209 53115 : if (!loc.same_loc_as(last_auto_target) ||
3210 26556 : now - last_loiter_ms > 500) {
3211 4 : wp_nav->set_wp_destination_NED_m(poscontrol.target_ned_m);
3212 4 : last_auto_target = loc;
3213 : }
3214 26559 : last_loiter_ms = now;
3215 :
3216 : /*
3217 : this is full copter control of auto flight
3218 : */
3219 : // run wpnav controller
3220 26559 : wp_nav->update_wpnav();
3221 :
3222 : // nav roll and pitch are controller by waypoint controller
3223 26559 : plane.nav_roll_cd = wp_nav->get_roll();
3224 26559 : plane.nav_pitch_cd = wp_nav->get_pitch();
3225 :
3226 26559 : assign_tilt_to_fwd_thr();
3227 :
3228 26559 : if (transition->set_VTOL_roll_pitch_limit(plane.nav_roll_cd, plane.nav_pitch_cd)) {
3229 1712 : pos_control->NE_set_externally_limited();
3230 : }
3231 :
3232 : // call attitude controller
3233 26559 : disable_yaw_rate_time_constant();
3234 53118 : attitude_control->input_euler_angle_roll_pitch_yaw_cd(plane.nav_roll_cd,
3235 26559 : plane.nav_pitch_cd,
3236 26559 : wp_nav->get_yaw(),
3237 : true);
3238 :
3239 : // climb based on altitude error
3240 26559 : set_climb_rate_ms(assist_climb_rate_cms() * 0.01);
3241 26559 : run_z_controller();
3242 26559 : }
3243 :
3244 :
3245 : /*
3246 : handle auto-mode when auto_state.vtol_mode is true
3247 : */
3248 958749 : void QuadPlane::control_auto(void)
3249 : {
3250 958749 : if (!setup()) {
3251 0 : return;
3252 : }
3253 :
3254 958749 : if (poscontrol.get_state() > QPOS_APPROACH) {
3255 623139 : bool should_run_motors = false;
3256 :
3257 : // don't run the motors if in an arming delay
3258 623139 : if (plane.arming.get_delay_arming()) {
3259 2398 : should_run_motors = false;
3260 : }
3261 :
3262 : // don't run motors if we are in the wait state for payload place
3263 638797 : if (motors->get_desired_spool_state() == AP_Motors::DesiredSpoolState::SHUT_DOWN &&
3264 638797 : plane.in_auto_mission_id(MAV_CMD_NAV_PAYLOAD_PLACE) &&
3265 0 : poscontrol.get_state() == QPOS_LAND_COMPLETE) {
3266 0 : should_run_motors = false;
3267 : }
3268 :
3269 623139 : if (should_run_motors) {
3270 0 : set_desired_spool_state(AP_Motors::DesiredSpoolState::THROTTLE_UNLIMITED);
3271 : }
3272 : }
3273 :
3274 958749 : uint16_t id = plane.mission.get_current_nav_cmd().id;
3275 958749 : switch (id) {
3276 296287 : case MAV_CMD_NAV_VTOL_TAKEOFF:
3277 : case MAV_CMD_NAV_TAKEOFF:
3278 296287 : if (is_vtol_takeoff(id)) {
3279 296287 : takeoff_controller();
3280 : }
3281 296287 : break;
3282 635903 : case MAV_CMD_NAV_VTOL_LAND:
3283 : case MAV_CMD_NAV_PAYLOAD_PLACE:
3284 : case MAV_CMD_NAV_LAND:
3285 635903 : if (is_vtol_land(id)) {
3286 635903 : vtol_position_controller();
3287 : }
3288 635903 : break;
3289 0 : case MAV_CMD_NAV_LOITER_UNLIM:
3290 : case MAV_CMD_NAV_LOITER_TIME:
3291 : case MAV_CMD_NAV_LOITER_TURNS:
3292 : case MAV_CMD_NAV_LOITER_TO_ALT: {
3293 0 : const uint32_t now = AP_HAL::millis();
3294 0 : if (now - poscontrol.last_run_ms > 100) {
3295 : // ensure that poscontrol is reset
3296 0 : poscontrol.set_state(QPOS_POSITION1);
3297 : }
3298 0 : vtol_position_controller();
3299 : }
3300 0 : break;
3301 26559 : default:
3302 26559 : waypoint_controller();
3303 26559 : break;
3304 : }
3305 : }
3306 :
3307 : /*
3308 : start a VTOL takeoff
3309 : */
3310 2624 : bool QuadPlane::do_vtol_takeoff(const AP_Mission::Mission_Command& cmd)
3311 : {
3312 2624 : if (!setup()) {
3313 0 : return false;
3314 : }
3315 :
3316 : // we always use the current location in XY for takeoff. The altitude defaults
3317 : // to relative to current height, but if Q_OPTIONS is set to respect takeoff frame
3318 : // then it will use normal frame handling for height
3319 2624 : Location loc = cmd.content.location;
3320 2624 : loc.lat = 0;
3321 2624 : loc.lng = 0;
3322 2624 : plane.set_next_WP(loc);
3323 2624 : if (option_is_set(QuadPlane::Option::RESPECT_TAKEOFF_FRAME)) {
3324 : // convert to absolute frame for takeoff
3325 0 : if (!plane.next_WP_loc.change_alt_frame(Location::AltFrame::ABSOLUTE) ||
3326 0 : plane.current_loc.alt >= plane.next_WP_loc.alt) {
3327 : // we are above the takeoff already, no need to do anything
3328 0 : return false;
3329 : }
3330 : } else {
3331 2624 : plane.next_WP_loc.set_alt_cm(plane.current_loc.alt + cmd.content.location.alt,
3332 : Location::AltFrame::ABSOLUTE);
3333 : }
3334 2624 : throttle_wait = false;
3335 :
3336 : // set vertical speed and acceleration limits
3337 : // All limits must be positive
3338 2624 : pos_control->D_set_max_speed_accel_m(get_pilot_velocity_z_max_dn_m(), pilot_speed_z_max_up_ms, pilot_accel_z_mss);
3339 2624 : pos_control->D_set_correction_speed_accel_m(get_pilot_velocity_z_max_dn_m(), pilot_speed_z_max_up_ms, pilot_accel_z_mss);
3340 :
3341 : // initialise the vertical position controller
3342 2624 : pos_control->D_init_controller();
3343 :
3344 : // also update nav_controller for status output
3345 2624 : plane.nav_controller->update_waypoint(plane.current_loc, plane.next_WP_loc);
3346 :
3347 : // calculate the time required to complete a takeoff
3348 : // this may be conservative and accept extra time due to clamping
3349 : // derived from the following latex equations if you want a nicely formatted view
3350 : // t_{accel} = \frac{V_max - V_z}{a}
3351 : // d_{accel} = V_z*t_{accel} + \frac{1}{2}*a*t_{accel}^2
3352 : // d_{remaining} = d_{total} - d_{accel}
3353 : // t_{constant} = \frac{d_{remaining}}{V_z}
3354 : // t = max(t_{accel}, 0) + max(t_{constant}, 0)
3355 2624 : const float d_total_m = (plane.next_WP_loc.alt - plane.current_loc.alt) * 0.01f;
3356 2624 : const float accel_m_s_s = MAX(0.1, pilot_accel_z_mss);
3357 2624 : const float vel_max_ms = MAX(0.1, pilot_speed_z_max_up_ms);
3358 2624 : const float vel_u_ms = inertial_nav.get_velocity_z_up_cms() * 0.01f;
3359 2624 : const float t_accel_s = (vel_max_ms - vel_u_ms) / accel_m_s_s;
3360 2624 : const float d_accel_m = vel_u_ms * t_accel_s + 0.5f * accel_m_s_s * sq(t_accel_s);
3361 2624 : const float d_remaining_m = d_total_m - d_accel_m;
3362 2624 : const float t_constant = d_remaining_m / vel_max_ms;
3363 2624 : const float travel_time_s = MAX(t_accel_s, 0) + MAX(t_constant, 0);
3364 :
3365 : // setup the takeoff failure handling code
3366 2624 : takeoff_start_time_ms = millis();
3367 2624 : takeoff_time_limit_ms = MAX(travel_time_s * takeoff_failure_scalar * 1000, 5000); // minimum time 5 seconds
3368 :
3369 2624 : return true;
3370 : }
3371 :
3372 :
3373 : /*
3374 : start a VTOL landing
3375 : */
3376 31 : bool QuadPlane::do_vtol_land(const AP_Mission::Mission_Command& cmd)
3377 : {
3378 31 : if (!setup()) {
3379 0 : return false;
3380 : }
3381 :
3382 31 : plane.set_next_WP(cmd.content.location);
3383 : // initially aim for current altitude
3384 31 : plane.next_WP_loc.copy_alt_from(plane.current_loc);
3385 :
3386 : // initialise the position controller
3387 31 : pos_control->NE_init_controller();
3388 31 : pos_control->D_init_controller();
3389 :
3390 31 : throttle_wait = false;
3391 31 : landing_detect.lower_limit_start_ms = 0;
3392 31 : landing_detect.land_start_ms = 0;
3393 :
3394 31 : plane.crash_state.is_crashed = false;
3395 :
3396 : // also update nav_controller for status output
3397 31 : plane.nav_controller->update_waypoint(plane.auto_state.crosstrack ? plane.prev_WP_loc : plane.current_loc,
3398 : plane.next_WP_loc);
3399 :
3400 31 : poscontrol_init_approach();
3401 31 : return true;
3402 : }
3403 :
3404 : /*
3405 : check if a VTOL takeoff has completed
3406 : */
3407 9726 : bool QuadPlane::verify_vtol_takeoff(const AP_Mission::Mission_Command &cmd)
3408 : {
3409 9726 : if (!available()) {
3410 0 : return true;
3411 : }
3412 :
3413 9726 : const uint32_t now = millis();
3414 :
3415 : // reset takeoff if we aren't armed
3416 9726 : if (!plane.arming.is_armed_and_safety_off()) {
3417 2575 : do_vtol_takeoff(cmd);
3418 2575 : return false;
3419 : }
3420 :
3421 8621 : if (now - takeoff_start_time_ms < 3000 &&
3422 1470 : !option_is_set(QuadPlane::Option::DISABLE_GROUND_EFFECT_COMP)) {
3423 1470 : ahrs.set_takeoff_expected(true);
3424 : }
3425 :
3426 : // check for failure conditions
3427 7151 : if (is_positive(takeoff_failure_scalar) && ((now - takeoff_start_time_ms) > takeoff_time_limit_ms)) {
3428 0 : gcs().send_text(MAV_SEVERITY_CRITICAL, "Failed to complete takeoff within time limit");
3429 0 : plane.set_mode(plane.mode_qland, ModeReason::VTOL_FAILED_TAKEOFF);
3430 0 : return false;
3431 : }
3432 :
3433 : #if AP_AIRSPEED_ENABLED
3434 7151 : if (is_positive(maximum_takeoff_airspeed_ms) && (plane.airspeed.get_airspeed() > maximum_takeoff_airspeed_ms)) {
3435 0 : gcs().send_text(MAV_SEVERITY_CRITICAL, "Failed to complete takeoff, excessive wind");
3436 0 : plane.set_mode(plane.mode_qland, ModeReason::VTOL_FAILED_TAKEOFF);
3437 0 : return false;
3438 : }
3439 : #endif
3440 :
3441 7151 : if (plane.current_loc.alt < plane.next_WP_loc.alt) {
3442 7107 : return false;
3443 : }
3444 44 : transition->restart();
3445 44 : plane.TECS_controller.set_pitch_max(transition_pitch_max);
3446 44 : plane.TECS_controller.set_pitch_min(-transition_pitch_max);
3447 :
3448 : // todo: why are you doing this, I want to delete it.
3449 44 : set_alt_target_current();
3450 :
3451 : #if AP_FENCE_ENABLED
3452 44 : plane.fence.auto_enable_fence_after_takeoff();
3453 : #endif
3454 :
3455 44 : if (plane.control_mode == &plane.mode_auto) {
3456 : // we reset TECS so that the target height filter is not
3457 : // constrained by the climb and sink rates from the initial
3458 : // takeoff height.
3459 44 : plane.TECS_controller.reset();
3460 : }
3461 :
3462 : // don't crosstrack on next WP
3463 44 : plane.auto_state.next_wp_crosstrack = false;
3464 :
3465 44 : return true;
3466 : }
3467 :
3468 : /*
3469 : a landing detector based on change in altitude over a timeout
3470 : */
3471 438311 : bool QuadPlane::land_detector(uint32_t timeout_ms)
3472 : {
3473 438311 : bool might_be_landed = should_relax() && !poscontrol.pilot_correction_active;
3474 438311 : if (!might_be_landed) {
3475 384378 : landing_detect.land_start_ms = 0;
3476 384378 : return false;
3477 : }
3478 53933 : const uint32_t now = AP_HAL::millis();
3479 53933 : float height_m = inertial_nav.get_position_z_up_cm() * 0.01;
3480 53933 : if (landing_detect.land_start_ms == 0) {
3481 406 : landing_detect.land_start_ms = now;
3482 406 : landing_detect.vpos_start_m = height_m;
3483 : }
3484 :
3485 : // we only consider the vehicle landed when the motors have been
3486 : // at minimum for timeout_ms+1000 and the vertical position estimate has not
3487 : // changed by more than 20cm for timeout_ms
3488 53933 : if (fabsf(height_m - landing_detect.vpos_start_m) > landing_detect.detect_alt_change_m) {
3489 : // height has changed, call off landing detection
3490 331 : landing_detect.land_start_ms = 0;
3491 331 : return false;
3492 : }
3493 :
3494 53602 : if ((now - landing_detect.land_start_ms) < timeout_ms ||
3495 69 : (now - landing_detect.lower_limit_start_ms) < (timeout_ms+1000)) {
3496 : // not landed yet
3497 53533 : return false;
3498 : }
3499 :
3500 69 : return true;
3501 : }
3502 :
3503 : /*
3504 : check if a landing is complete
3505 : */
3506 487507 : bool QuadPlane::check_land_complete(void)
3507 : {
3508 487507 : if (poscontrol.get_state() != QPOS_LAND_FINAL) {
3509 : // only apply to final landing phase
3510 295827 : return false;
3511 : }
3512 191680 : if (land_detector(4000)) {
3513 67 : poscontrol.set_state(QPOS_LAND_COMPLETE);
3514 67 : gcs().send_text(MAV_SEVERITY_INFO,"Land complete");
3515 :
3516 67 : if (plane.in_auto_mission_id(MAV_CMD_NAV_PAYLOAD_PLACE)) {
3517 : // for payload place with full landing we shutdown motors
3518 : // and wait for the lua script to trigger a climb (using
3519 : // landing abort) or disarm
3520 0 : set_desired_spool_state(AP_Motors::DesiredSpoolState::SHUT_DOWN);
3521 0 : return false;
3522 : }
3523 :
3524 98 : if (plane.control_mode != &plane.mode_auto ||
3525 31 : !plane.mission.continue_after_land()) {
3526 : // disarm on land unless we have MIS_OPTIONS setup to
3527 : // continue after land in AUTO
3528 67 : plane.arming.disarm(AP_Arming::Method::LANDED);
3529 : }
3530 67 : return true;
3531 : }
3532 191613 : return false;
3533 : }
3534 :
3535 :
3536 : /*
3537 : check if we should switch from QPOS_LAND_DESCEND to QPOS_LAND_FINAL
3538 : */
3539 246696 : bool QuadPlane::check_land_final(void)
3540 : {
3541 246696 : float height_above_ground_m = plane.relative_ground_altitude(RangeFinderUse::TAKEOFF_LANDING);
3542 : // we require 2 readings at 10Hz to be within 5m of each other to
3543 : // trigger the switch to land final. This prevents a short term
3544 : // glitch at high altitude from triggering land final
3545 246696 : const float max_change_m = 5;
3546 246761 : if (height_above_ground_m < land_final_alt_m &&
3547 65 : fabsf(height_above_ground_m - last_land_final_agl_m) < max_change_m) {
3548 65 : return true;
3549 : }
3550 246631 : last_land_final_agl_m = height_above_ground_m;
3551 :
3552 : /*
3553 : also apply landing detector, in case we have landed in descent
3554 : phase. Use a longer threshold
3555 : */
3556 246631 : return land_detector(6000);
3557 : }
3558 :
3559 : /*
3560 : check if a VTOL landing has completed
3561 : */
3562 219760 : bool QuadPlane::verify_vtol_land(void)
3563 : {
3564 219760 : if (!available()) {
3565 0 : return true;
3566 : }
3567 :
3568 219760 : if (poscontrol.get_state() == QPOS_POSITION2) {
3569 : // see if we should move onto the descend stage of landing
3570 19634 : const float descend_dist_threshold_m = 2.0;
3571 19634 : const float descend_speed_threshold_ms = 3.0;
3572 19634 : bool reached_position = false;
3573 19634 : if (poscontrol.pilot_correction_done) {
3574 0 : reached_position = !poscontrol.pilot_correction_active;
3575 : } else {
3576 19634 : const float dist_m = (inertial_nav.get_position_neu_cm().topostype() * 0.01 - poscontrol.target_ned_m).xy().length();
3577 19634 : reached_position = dist_m < descend_dist_threshold_m;
3578 : }
3579 19634 : Vector2f approach_vel_ne_ms;
3580 19634 : if (AP_HAL::millis() - poscontrol.last_velocity_match_ms < 1000) {
3581 732 : approach_vel_ne_ms = poscontrol.velocity_match_ms;
3582 : }
3583 19634 : Vector3f vel_ned_ms;
3584 19634 : UNUSED_RESULT(plane.ahrs.get_velocity_NED(vel_ned_ms));
3585 :
3586 20080 : if (reached_position &&
3587 20080 : (vel_ned_ms.xy() - approach_vel_ne_ms).length() < descend_speed_threshold_ms) {
3588 50 : poscontrol.set_state(QPOS_LAND_DESCEND);
3589 50 : poscontrol.pilot_correction_done = false;
3590 50 : pos_control->set_lean_angle_max_cd(0);
3591 50 : poscontrol.correction_ne_m.zero();
3592 : #if AP_LANDINGGEAR_ENABLED
3593 50 : plane.g2.landing_gear.deploy_for_landing();
3594 : #endif
3595 50 : last_land_final_agl_m = plane.relative_ground_altitude(RangeFinderUse::TAKEOFF_LANDING);
3596 50 : gcs().send_text(MAV_SEVERITY_INFO,"Land descend started");
3597 50 : if (plane.control_mode == &plane.mode_auto) {
3598 : // set height to mission height, so we can use the mission
3599 : // WP height for triggering land final if no rangefinder
3600 : // available
3601 31 : plane.set_next_WP(plane.mission.get_current_nav_cmd().content.location);
3602 : } else {
3603 19 : plane.set_next_WP(plane.next_WP_loc);
3604 19 : plane.next_WP_loc.copy_alt_from(ahrs.get_home());
3605 : }
3606 : }
3607 : }
3608 :
3609 : // at land_final_alt_m begin final landing
3610 219760 : if (poscontrol.get_state() == QPOS_LAND_DESCEND && check_land_final()) {
3611 50 : poscontrol.set_state(QPOS_LAND_FINAL);
3612 :
3613 : #if AP_ICENGINE_ENABLED
3614 : // cut IC engine if enabled
3615 50 : if (land_icengine_cut != 0) {
3616 50 : plane.g2.ice_control.engine_control(0, 0, 0, false);
3617 : }
3618 : #endif // AP_ICENGINE_ENABLED
3619 50 : gcs().send_text(MAV_SEVERITY_INFO,"Land final started");
3620 : }
3621 :
3622 : // at land_final_alt_m begin final landing
3623 219760 : if (poscontrol.get_state() == QPOS_LAND_ABORT &&
3624 0 : plane.current_loc.alt * 0.01 >= land_descend_start_alt_m) {
3625 : // continue to next WP, if there is one
3626 0 : return true;
3627 : }
3628 :
3629 219760 : if (plane.in_auto_mission_id(MAV_CMD_NAV_PAYLOAD_PLACE) &&
3630 0 : (poscontrol.get_state() == QPOS_LAND_DESCEND ||
3631 0 : poscontrol.get_state() == QPOS_LAND_FINAL)) {
3632 0 : const auto &cmd = plane.mission.get_current_nav_cmd();
3633 0 : if (cmd.p1 > 0 && plane.current_loc.alt * 0.01 < land_descend_start_alt_m - cmd.p1 * 0.01) {
3634 0 : gcs().send_text(MAV_SEVERITY_INFO,"Payload place aborted");
3635 0 : poscontrol.set_state(QPOS_LAND_ABORT);
3636 : }
3637 : }
3638 :
3639 219760 : if (check_land_complete() && plane.mission.continue_after_land()) {
3640 0 : gcs().send_text(MAV_SEVERITY_INFO,"Mission continue");
3641 0 : return true;
3642 : }
3643 219760 : return false;
3644 : }
3645 :
3646 : #if HAL_LOGGING_ENABLED
3647 : // Write a control tuning packet
3648 205625 : void QuadPlane::Log_Write_QControl_Tuning()
3649 : {
3650 205625 : float des_alt_m = 0.0f;
3651 205625 : float target_climb_rate_ms = 0;
3652 205625 : if (plane.control_mode != &plane.mode_qstabilize) {
3653 204916 : des_alt_m = pos_control->get_pos_desired_U_m();
3654 204916 : target_climb_rate_ms = pos_control->get_vel_target_U_ms();
3655 : }
3656 :
3657 : // Assemble assistance bitmask, definition here is used to generate log documentation
3658 : enum class log_assistance_flags {
3659 : in_assisted_flight = 1U<<0, // true if VTOL assist is active
3660 : forced = 1U<<1, // true if assistance is forced
3661 : speed = 1U<<2, // true if assistance due to low airspeed
3662 : alt = 1U<<3, // true if assistance due to low altitude
3663 : angle = 1U<<4, // true if assistance due to attitude error
3664 : fw_force = 1U<<5, // true if forcing use of fixed wing controllers
3665 : spin_recovery = 1U<<6, // true if recovering from a spin
3666 : };
3667 :
3668 205625 : uint8_t assist_flags = 0;
3669 205625 : if (assisted_flight) {
3670 28631 : assist_flags |= (uint8_t)log_assistance_flags::in_assisted_flight;
3671 : }
3672 205625 : if (assist.in_force_assist()) {
3673 0 : assist_flags |= (uint8_t)log_assistance_flags::forced;
3674 : }
3675 205625 : if (assist.in_speed_assist()) {
3676 11942 : assist_flags |= (uint8_t)log_assistance_flags::speed;
3677 : }
3678 205625 : if (assist.in_alt_assist()) {
3679 232 : assist_flags |= (uint8_t)log_assistance_flags::alt;
3680 : }
3681 205625 : if (assist.in_angle_assist()) {
3682 1582 : assist_flags |= (uint8_t)log_assistance_flags::angle;
3683 : }
3684 205625 : if (force_fw_control_recovery) {
3685 2451 : assist_flags |= (uint8_t)log_assistance_flags::fw_force;
3686 : }
3687 205625 : if (in_spin_recovery) {
3688 227 : assist_flags |= (uint8_t)log_assistance_flags::spin_recovery;
3689 : }
3690 :
3691 205625 : struct log_QControl_Tuning pkt = {
3692 : LOG_PACKET_HEADER_INIT(LOG_QTUN_MSG),
3693 205625 : time_us : AP_HAL::micros64(),
3694 205625 : throttle_in : attitude_control->get_throttle_in(),
3695 205625 : angle_boost : attitude_control->angle_boost(),
3696 205625 : throttle_out : motors->get_throttle(),
3697 205625 : throttle_hover : motors->get_throttle_hover(),
3698 : desired_alt : des_alt_m,
3699 411250 : inav_alt : inertial_nav.get_position_z_up_cm() * 0.01f,
3700 411250 : baro_alt : int32_t(plane.barometer.get_altitude() * 100),
3701 205625 : target_climb_rate : int16_t(target_climb_rate_ms * 100.0),
3702 411250 : climb_rate : int16_t(inertial_nav.get_velocity_z_up_cms()),
3703 205625 : throttle_mix : attitude_control->get_throttle_mix(),
3704 205625 : transition_state : transition->get_log_transition_state(),
3705 : assist : assist_flags,
3706 205625 : };
3707 205625 : plane.logger.WriteBlock(&pkt, sizeof(pkt));
3708 :
3709 : // write multicopter position control message
3710 205625 : pos_control->write_log();
3711 :
3712 : // Write tiltrotor tilt angle log
3713 205625 : tiltrotor.write_log();
3714 205625 : }
3715 : #endif
3716 :
3717 :
3718 : /*
3719 : calculate the forward throttle percentage. The forward throttle can
3720 : be used to assist with position hold and with landing approach. It
3721 : reduces the need for down pitch which reduces load on the vertical
3722 : lift motors.
3723 : */
3724 2292091 : float QuadPlane::forward_throttle_pct()
3725 : {
3726 : // handle special case where forward thrust motor is used instead of forward pitch.
3727 2292091 : if (get_vfwd_method() == ActiveFwdThr::NEW) {
3728 20830 : return 100.0f * q_fwd_throttle;
3729 : }
3730 :
3731 : /*
3732 : Unless an RC channel is assigned for manual forward throttle control,
3733 : we don't use forward throttle in QHOVER or QSTABILIZE as they are the primary
3734 : recovery modes for a quadplane and need to be as simple as
3735 : possible. They will drift with the wind.
3736 : */
3737 2271261 : if (plane.control_mode == &plane.mode_qacro ||
3738 2263040 : plane.control_mode == &plane.mode_qstabilize ||
3739 2253860 : plane.control_mode == &plane.mode_qhover) {
3740 :
3741 157063 : if (rc_fwd_thr_ch == nullptr) {
3742 157063 : return 0;
3743 : } else {
3744 : // calculate fwd throttle demand from manual input
3745 0 : float fwd_thr = rc_fwd_thr_ch->percent_input();
3746 :
3747 : // set forward throttle to fwd_thr_max * (manual input + mix): range [0,100]
3748 0 : fwd_thr *= 0.01f * constrain_float(fwd_thr_max, 0, 100);
3749 0 : return fwd_thr;
3750 : }
3751 : }
3752 :
3753 : /*
3754 : see if the controller should be active
3755 : */
3756 2114198 : if (get_vfwd_method() != ActiveFwdThr::OLD) {
3757 2047824 : return 0;
3758 : }
3759 :
3760 : /*
3761 : in modes with a velocity controller
3762 : */
3763 66374 : float deltat = (AP_HAL::millis() - vel_forward.last_ms) * 0.001f;
3764 66374 : if (deltat > 1 || deltat < 0) {
3765 4 : vel_forward.integrator = 0;
3766 4 : deltat = 0.1;
3767 : }
3768 66374 : if (deltat < 0.1) {
3769 : // run at 10Hz
3770 64167 : return vel_forward.last_pct;
3771 : }
3772 2207 : vel_forward.last_ms = AP_HAL::millis();
3773 :
3774 : // work out the desired speed in forward direction
3775 2207 : Vector3f desired_velocity_ned_ms = pos_control->get_vel_desired_NED_ms();
3776 :
3777 2207 : Vector3f vel_ned_ms;
3778 2207 : if (!plane.ahrs.get_velocity_NED(vel_ned_ms)) {
3779 : // we don't know our velocity? EKF must be pretty sick
3780 0 : vel_forward.last_pct = 0;
3781 0 : vel_forward.integrator = 0;
3782 0 : return 0;
3783 : }
3784 : // get component of velocity error in fwd body frame direction
3785 2207 : Vector3f vel_error_body_ms = ahrs.get_rotation_body_to_ned().transposed() * (desired_velocity_ned_ms - vel_ned_ms);
3786 :
3787 2207 : float fwd_vel_error_ms = vel_error_body_ms.x;
3788 :
3789 : // scale forward velocity error by maximum airspeed
3790 2207 : fwd_vel_error_ms /= MAX(plane.aparm.airspeed_max, 5);
3791 :
3792 : // add in a component from our current pitch demand. This tends to
3793 : // move us to zero pitch. Assume that LIM_PITCH would give us the
3794 : // WP nav speed.
3795 2207 : fwd_vel_error_ms -= wp_nav->get_default_speed_NE_ms() * plane.nav_pitch_cd / (plane.aparm.pitch_limit_max * 100);
3796 :
3797 2207 : if (should_relax() && vel_ned_ms.length() < 1) {
3798 : // we may be landed
3799 123 : fwd_vel_error_ms = 0;
3800 123 : vel_forward.integrator *= 0.95f;
3801 : }
3802 :
3803 : // integrator as throttle percentage (-100 to 100)
3804 2207 : vel_forward.integrator += fwd_vel_error_ms * deltat * vel_forward.gain * 100;
3805 :
3806 : // inhibit reverse throttle and allow petrol engines with min > 0
3807 2207 : int8_t fwd_throttle_min = plane.have_reverse_thrust() ? 0 : plane.aparm.throttle_min;
3808 2207 : vel_forward.integrator = constrain_float(vel_forward.integrator, fwd_throttle_min, plane.aparm.throttle_cruise);
3809 :
3810 2207 : if (in_vtol_land_approach()) {
3811 : // when we are doing horizontal positioning in a VTOL land
3812 : // we always allow the fwd motor to run. Otherwise a bad
3813 : // lidar could cause the aircraft not to be able to
3814 : // approach the landing point when landing below the takeoff point
3815 353 : vel_forward.last_pct = vel_forward.integrator;
3816 3556 : } else if ((in_vtol_land_final() && motors->limit.throttle_lower) ||
3817 : #if AP_RANGEFINDER_ENABLED
3818 1702 : (plane.rangefinder_use(RangeFinderUse::TAKEOFF_LANDING) &&
3819 0 : (plane.rangefinder.status_orient(plane.rangefinder_orientation()) == RangeFinder::Status::OutOfRangeLow))) {
3820 : #else
3821 : false) {
3822 : #endif
3823 : // we're in the settling phase of landing or using a rangefinder that is out of range low, disable fwd motor
3824 152 : vel_forward.last_pct = 0;
3825 152 : vel_forward.integrator = 0;
3826 : } else {
3827 : // If we are below alt_cutoff_m then scale down the effect until
3828 : // it turns off at alt_cutoff_m and decay the integrator
3829 1702 : float alt_cutoff_m = MAX(0, vel_forward_alt_cutoff_m);
3830 1702 : float height_above_ground_m = plane.relative_ground_altitude(RangeFinderUse::TAKEOFF_LANDING);
3831 :
3832 1702 : vel_forward.last_pct = linear_interpolate(0, vel_forward.integrator,
3833 : height_above_ground_m, alt_cutoff_m, alt_cutoff_m + 2);
3834 : }
3835 2207 : if (is_zero(vel_forward.last_pct)) {
3836 : // if the percent is 0 then decay the integrator
3837 1838 : vel_forward.integrator *= 0.95f;
3838 : }
3839 :
3840 2207 : return vel_forward.last_pct;
3841 : }
3842 :
3843 : /*
3844 : get weathervaning yaw rate in cd/s
3845 : */
3846 2041049 : float QuadPlane::get_weathervane_yaw_rate_cds(void)
3847 : {
3848 : /*
3849 : we only do weathervaning in modes where we are doing VTOL
3850 : position control.
3851 : */
3852 2041049 : if (!in_vtol_mode() ||
3853 2041049 : !transition->allow_weathervane() ||
3854 2027850 : !motors->armed() || (motors->get_desired_spool_state() != AP_Motors::DesiredSpoolState::THROTTLE_UNLIMITED) ||
3855 2000505 : plane.control_mode == &plane.mode_qstabilize ||
3856 : #if QAUTOTUNE_ENABLED
3857 2000505 : plane.control_mode == &plane.mode_qautotune ||
3858 : #endif
3859 5954049 : plane.control_mode == &plane.mode_qhover ||
3860 1871951 : should_relax()
3861 : ) {
3862 : // Ensure the weathervane controller is reset to prevent weathervaning from happening outside of the timer
3863 367907 : weathervane->reset();
3864 367907 : return 0.0;
3865 : }
3866 :
3867 1673142 : const bool is_takeoff = in_vtol_auto() && is_vtol_takeoff(plane.mission.get_current_nav_cmd().id);
3868 : float wv_output;
3869 3346284 : if (weathervane->get_yaw_out(wv_output,
3870 1673142 : plane.channel_rudder->get_control_in(),
3871 : plane.relative_ground_altitude(RangeFinderUse::TAKEOFF_LANDING),
3872 1673142 : pos_control->get_roll_cd(),
3873 1673142 : pos_control->get_pitch_cd(),
3874 : is_takeoff,
3875 1673142 : in_vtol_land_sequence())) {
3876 51130 : return constrain_float(wv_output * (1/45.0), -100.0, 100.0) * command_model_pilot.get_rate() * 0.5;
3877 : }
3878 :
3879 1622012 : return 0.0;
3880 : }
3881 :
3882 : /*
3883 : start guided mode control
3884 : */
3885 96 : void QuadPlane::guided_start(void)
3886 : {
3887 96 : guided_takeoff = false;
3888 96 : setup_target_position();
3889 : int32_t from_alt;
3890 : int32_t to_alt;
3891 96 : poscontrol_init_approach();
3892 96 : if (plane.current_loc.get_alt_cm(Location::AltFrame::ABSOLUTE, from_alt) && plane.next_WP_loc.get_alt_cm(Location::AltFrame::ABSOLUTE, to_alt)) {
3893 96 : poscontrol.slow_descent = from_alt > to_alt;
3894 : } else {
3895 : // default back to old method
3896 0 : poscontrol.slow_descent = (plane.current_loc.alt > plane.next_WP_loc.alt);
3897 : }
3898 96 : }
3899 :
3900 : /*
3901 : update guided mode control
3902 : */
3903 310640 : void QuadPlane::guided_update(void)
3904 : {
3905 310640 : if (plane.control_mode == &plane.mode_guided && guided_takeoff && plane.current_loc.alt < plane.next_WP_loc.alt) {
3906 52242 : throttle_wait = false;
3907 52242 : set_desired_spool_state(AP_Motors::DesiredSpoolState::THROTTLE_UNLIMITED);
3908 52242 : takeoff_controller();
3909 : } else {
3910 258398 : if (guided_takeoff) {
3911 5 : poscontrol.set_state(QPOS_POSITION2);
3912 : }
3913 258398 : guided_takeoff = false;
3914 : // run VTOL position controller
3915 258398 : vtol_position_controller();
3916 : }
3917 310640 : }
3918 :
3919 303 : void QuadPlane::afs_terminate(void)
3920 : {
3921 303 : if (available()) {
3922 0 : set_desired_spool_state(AP_Motors::DesiredSpoolState::SHUT_DOWN);
3923 0 : motors->output();
3924 : }
3925 303 : }
3926 :
3927 : /*
3928 : return true if we should do guided mode loitering using VTOL motors
3929 : */
3930 78515 : bool QuadPlane::guided_mode_enabled(void)
3931 : {
3932 78515 : if (!available()) {
3933 58349 : return false;
3934 : }
3935 : // only use quadplane guided when in AUTO or GUIDED mode
3936 20166 : if (plane.control_mode != &plane.mode_guided && plane.control_mode != &plane.mode_auto) {
3937 6359 : return false;
3938 : }
3939 14585 : if (plane.control_mode == &plane.mode_auto &&
3940 778 : plane.mission.get_current_nav_cmd().id == MAV_CMD_NAV_LOITER_TURNS) {
3941 : // loiter turns is a fixed wing only operation
3942 0 : return false;
3943 : }
3944 13807 : return guided_mode != 0;
3945 : }
3946 :
3947 : /*
3948 : set altitude target to current altitude
3949 : */
3950 44 : void QuadPlane::set_alt_target_current(void)
3951 : {
3952 44 : pos_control->set_pos_desired_U_m(inertial_nav.get_position_z_up_cm() * 0.01);
3953 44 : }
3954 :
3955 : // user initiated takeoff for guided mode
3956 12 : bool QuadPlane::do_user_takeoff(float takeoff_altitude)
3957 : {
3958 12 : if (plane.control_mode != &plane.mode_guided) {
3959 0 : gcs().send_text(MAV_SEVERITY_INFO, "User Takeoff only in GUIDED mode");
3960 0 : return false;
3961 : }
3962 12 : if (!plane.arming.is_armed_and_safety_off()) {
3963 0 : gcs().send_text(MAV_SEVERITY_INFO, "Must be armed for takeoff");
3964 0 : return false;
3965 : }
3966 12 : if (is_flying()) {
3967 0 : gcs().send_text(MAV_SEVERITY_INFO, "Already flying - no takeoff");
3968 0 : return false;
3969 : }
3970 12 : plane.auto_state.vtol_loiter = true;
3971 12 : plane.prev_WP_loc = plane.current_loc;
3972 12 : plane.next_WP_loc = plane.current_loc;
3973 12 : plane.next_WP_loc.offset_up_m(takeoff_altitude);
3974 12 : set_desired_spool_state(AP_Motors::DesiredSpoolState::THROTTLE_UNLIMITED);
3975 12 : guided_start();
3976 12 : guided_takeoff = true;
3977 12 : guided_wait_takeoff = false;
3978 12 : if (!option_is_set(QuadPlane::Option::DISABLE_GROUND_EFFECT_COMP)) {
3979 12 : ahrs.set_takeoff_expected(true);
3980 : }
3981 12 : return true;
3982 : }
3983 :
3984 : // return true if the wp_nav controller is being updated
3985 121945 : bool QuadPlane::using_wp_nav(void) const
3986 : {
3987 121945 : if (plane.control_mode == &plane.mode_qloiter || plane.control_mode == &plane.mode_qland) {
3988 33602 : return true;
3989 : }
3990 88343 : return false;
3991 : }
3992 :
3993 : /*
3994 : return mav_type for heartbeat
3995 : */
3996 60885 : MAV_TYPE QuadPlane::get_mav_type(void) const
3997 : {
3998 60885 : if (!available()) {
3999 : // Not enabled, must be a normal plane
4000 37544 : return MAV_TYPE_FIXED_WING;
4001 : }
4002 23341 : if (mav_type.get() != 0) {
4003 : // Override parameter set by user
4004 0 : return MAV_TYPE(mav_type.get());
4005 : }
4006 23341 : if (tiltrotor.enabled()) {
4007 : // Tiltrotor specific type
4008 2050 : return MAV_TYPE_VTOL_TILTROTOR;
4009 : }
4010 21291 : if (tailsitter.enabled()) {
4011 : // Tailsitter specific types
4012 557 : switch (motors->get_frame_mav_type()) {
4013 88 : case MAV_TYPE_VTOL_DUOROTOR:
4014 88 : return MAV_TYPE_VTOL_DUOROTOR;
4015 :
4016 469 : case MAV_TYPE_QUADROTOR:
4017 469 : return MAV_TYPE_VTOL_QUADROTOR;
4018 :
4019 0 : default:
4020 0 : break;
4021 : }
4022 : }
4023 : // Default to normal plane
4024 20734 : return MAV_TYPE_FIXED_WING;
4025 : }
4026 :
4027 : /*
4028 : return true if current mission item is a vtol takeoff
4029 : */
4030 7304823 : bool QuadPlane::is_vtol_takeoff(uint16_t id) const
4031 : {
4032 7304823 : if (id == MAV_CMD_NAV_VTOL_TAKEOFF) {
4033 324251 : return true;
4034 : }
4035 6980572 : if (id == MAV_CMD_NAV_TAKEOFF && available() && !option_is_set(QuadPlane::Option::ALLOW_FW_TAKEOFF)) {
4036 : // treat fixed wing takeoff as VTOL takeoff
4037 6415629 : return true;
4038 : }
4039 564943 : return false;
4040 : }
4041 :
4042 : /*
4043 : return true if current mission item is a vtol land
4044 : */
4045 63445650 : bool QuadPlane::is_vtol_land(uint16_t id) const
4046 : {
4047 63445650 : if (id == MAV_CMD_NAV_VTOL_LAND || id == MAV_CMD_NAV_PAYLOAD_PLACE) {
4048 42015701 : if (landing_with_fixed_wing_spiral_approach()) {
4049 3843074 : return plane.vtol_approach_s.approach_stage == Plane::VTOLApproach::Stage::VTOL_LANDING;
4050 : } else {
4051 38172619 : return true;
4052 : }
4053 : }
4054 21429949 : if (id == MAV_CMD_NAV_LAND && available() && !option_is_set(QuadPlane::Option::ALLOW_FW_LAND)) {
4055 : // treat fixed wing land as VTOL land
4056 2625752 : return true;
4057 : }
4058 18804197 : return false;
4059 : }
4060 :
4061 : /*
4062 : return true if we are in a transition to fwd flight from hover
4063 : */
4064 21122850 : bool QuadPlane::in_frwd_transition(void) const
4065 : {
4066 21122850 : return available() && transition->active_frwd();
4067 : }
4068 :
4069 : /*
4070 : calculate current stopping distance for a quadplane in fixed wing flight
4071 : */
4072 233022 : float QuadPlane::stopping_distance_m(float ground_speed_squared_m) const
4073 : {
4074 : // use v^2/(2*accel). This is only quite approximate as the drag
4075 : // varies with pitch, but it gives something for the user to
4076 : // control the transition distance in a reasonable way
4077 233022 : return ground_speed_squared_m / (2 * transition_decel_mss);
4078 : }
4079 :
4080 : /*
4081 : calculate acceleration needed to stop in the given distance given current speed
4082 : */
4083 217209 : float QuadPlane::accel_needed(float stop_distance, float ground_speed_squared) const
4084 : {
4085 217209 : return ground_speed_squared / (2 * MAX(1,stop_distance));
4086 : }
4087 :
4088 : /*
4089 : calculate current stopping distance for a quadplane in fixed wing flight
4090 : */
4091 157944 : float QuadPlane::stopping_distance_m(void)
4092 : {
4093 157944 : return stopping_distance_m(plane.ahrs.groundspeed_vector().length_squared());
4094 : }
4095 :
4096 : /*
4097 : distance below which we don't do approach, based on stopping
4098 : distance for cruise speed
4099 : */
4100 146 : float QuadPlane::transition_threshold_m(void)
4101 : {
4102 : // 1.5 times stopping distance for cruise speed
4103 146 : return 1.5 * stopping_distance_m(sq(plane.aparm.airspeed_cruise));
4104 : }
4105 :
4106 : #define LAND_CHECK_ANGLE_ERROR_DEG 30.0f // maximum angle error to be considered landing
4107 : #define LAND_CHECK_LARGE_ANGLE_CD 1500.0f // maximum angle target to be considered landing
4108 : #define LAND_CHECK_ACCEL_MOVING 3.0f // maximum acceleration after subtracting gravity
4109 :
4110 527359 : void QuadPlane::update_throttle_mix(void)
4111 : {
4112 : // update filtered acceleration
4113 527359 : Vector3f accel_ef_mss = ahrs.get_accel_ef();
4114 527359 : accel_ef_mss.z += GRAVITY_MSS;
4115 527359 : throttle_mix_accel_ef_filter.apply(accel_ef_mss, plane.scheduler.get_loop_period_s());
4116 :
4117 : // transition will directly manage the mix
4118 527359 : if (!transition->allow_update_throttle_mix()) {
4119 144500 : return;
4120 : }
4121 :
4122 : // if disarmed or landed prioritise throttle
4123 457792 : if (!motors->armed()) {
4124 74933 : attitude_control->set_throttle_mix_min();
4125 74933 : return;
4126 : }
4127 :
4128 382859 : if (plane.control_mode->is_vtol_man_throttle()) {
4129 : // manual throttle
4130 2960 : if (!is_positive(get_throttle_input()) && !air_mode_active()) {
4131 319 : attitude_control->set_throttle_mix_min();
4132 : } else {
4133 2641 : attitude_control->set_throttle_mix_man();
4134 : }
4135 : } else {
4136 : // autopilot controlled throttle
4137 :
4138 : // check for aggressive flight requests - requested roll or pitch angle below 15 degrees
4139 379899 : const Vector3f angle_target = attitude_control->get_att_target_euler_cd();
4140 379899 : bool large_angle_request = angle_target.xy().length() > LAND_CHECK_LARGE_ANGLE_CD;
4141 :
4142 : // check for large external disturbance - angle error over 30 degrees
4143 379899 : const float angle_error = attitude_control->get_att_error_angle_deg();
4144 379899 : bool large_angle_error = (angle_error > LAND_CHECK_ANGLE_ERROR_DEG);
4145 :
4146 : // check for large acceleration - falling or high turbulence
4147 379899 : bool accel_moving = (throttle_mix_accel_ef_filter.get().length() > LAND_CHECK_ACCEL_MOVING);
4148 :
4149 : // check for requested descent
4150 379899 : bool descent_not_demanded = pos_control->get_vel_desired_U_ms() >= 0.0f;
4151 :
4152 379899 : bool use_mix_max = large_angle_request || large_angle_error || accel_moving || descent_not_demanded;
4153 :
4154 : /*
4155 : special case for auto landing, we want a high degree of
4156 : attitude control until LAND_FINAL
4157 : */
4158 379899 : if (in_vtol_land_sequence()) {
4159 : // during the descent phase we don't want to priortise
4160 : // attitude too much. Rapid descent on quadplanes often
4161 : // leads to very poor attitude control and putting more
4162 : // power into attitude doesn't tend to fix it, what we
4163 : // need to do is slow the descent.
4164 137172 : use_mix_max = !in_vtol_land_descent() || poscontrol.get_state() == QPOS_LAND_ABORT;
4165 : }
4166 :
4167 379899 : if (use_mix_max) {
4168 209846 : attitude_control->set_throttle_mix_max(1.0);
4169 : } else {
4170 170053 : attitude_control->set_throttle_mix_min();
4171 : }
4172 : }
4173 : }
4174 :
4175 : /*
4176 : see if we are in the approach phase of a VTOL landing
4177 : */
4178 68617234 : bool QuadPlane::in_vtol_land_approach(void) const
4179 : {
4180 68876897 : if (plane.control_mode == &plane.mode_qrtl &&
4181 259663 : poscontrol.get_state() <= QPOS_POSITION2) {
4182 90607 : return true;
4183 : }
4184 68526627 : if (in_vtol_auto()) {
4185 36761773 : if (is_vtol_land(plane.mission.get_current_nav_cmd().id) &&
4186 21200885 : (poscontrol.get_state() == QPOS_APPROACH ||
4187 20658652 : poscontrol.get_state() == QPOS_AIRBRAKE ||
4188 18899779 : poscontrol.get_state() == QPOS_POSITION1 ||
4189 8701775 : poscontrol.get_state() == QPOS_POSITION2)) {
4190 3095225 : return true;
4191 : }
4192 : }
4193 65431402 : return false;
4194 : }
4195 :
4196 : /*
4197 : see if we are in the descent phase of a VTOL landing
4198 : */
4199 127141845 : bool QuadPlane::in_vtol_land_descent(void) const
4200 : {
4201 127141845 : const auto state = poscontrol.get_state();
4202 127141853 : if (plane.control_mode == &plane.mode_qrtl &&
4203 304484 : (state == QPOS_LAND_DESCEND || state == QPOS_LAND_FINAL || state == QPOS_LAND_ABORT)) {
4204 390762 : return true;
4205 : }
4206 130366390 : if (in_vtol_auto() && is_vtol_land(plane.mission.get_current_nav_cmd().id) &&
4207 3615299 : (state == QPOS_LAND_DESCEND || state == QPOS_LAND_FINAL || state == QPOS_LAND_ABORT)) {
4208 8047766 : return true;
4209 : }
4210 118703318 : return false;
4211 : }
4212 :
4213 : /*
4214 : see if we are in the final phase of a VTOL landing
4215 : */
4216 57040244 : bool QuadPlane::in_vtol_land_final(void) const
4217 : {
4218 57040244 : return in_vtol_land_descent() && poscontrol.get_state() == QPOS_LAND_FINAL;
4219 : }
4220 :
4221 : /*
4222 : see if we are in any of the phases of a VTOL landing
4223 : */
4224 71908684 : bool QuadPlane::in_vtol_land_sequence(void) const
4225 : {
4226 71908684 : return plane.control_mode == &plane.mode_qrtl || in_vtol_land_approach() || in_vtol_land_descent() || in_vtol_land_final();
4227 : }
4228 :
4229 : /*
4230 : see if we are in the VTOL position control phase of a landing
4231 : */
4232 12422497 : bool QuadPlane::in_vtol_land_poscontrol(void) const
4233 : {
4234 13694303 : if (in_vtol_auto() && is_vtol_land(plane.mission.get_current_nav_cmd().id) &&
4235 1271806 : poscontrol.get_state() >= QPOS_POSITION1) {
4236 1178010 : return true;
4237 : }
4238 11244487 : return false;
4239 : }
4240 :
4241 : /*
4242 : see if we are in the airbrake phase of a VTOL landing
4243 : */
4244 11456632 : bool QuadPlane::in_vtol_airbrake(void) const
4245 : {
4246 11955914 : if (plane.control_mode == &plane.mode_qrtl &&
4247 499282 : poscontrol.get_state() == QPOS_AIRBRAKE) {
4248 25302 : return true;
4249 : }
4250 4672393 : if (plane.control_mode == &plane.mode_auto &&
4251 16103723 : is_vtol_land(plane.mission.get_current_nav_cmd().id) &&
4252 763087 : poscontrol.get_state() == QPOS_AIRBRAKE) {
4253 124898 : return true;
4254 : }
4255 11306432 : return false;
4256 : }
4257 :
4258 : // return true if we should show VTOL view
4259 6011411 : bool QuadPlane::show_vtol_view() const
4260 : {
4261 6011411 : return available() && transition->show_vtol_view() && !force_fw_control_recovery;
4262 : }
4263 :
4264 : // return true if we should show VTOL view
4265 3313786 : bool SLT_Transition::show_vtol_view() const
4266 : {
4267 :
4268 3313786 : return quadplane.in_vtol_mode();
4269 : }
4270 :
4271 : /*
4272 : return the PILOT_SPD_DN value if non zero, otherwise returns the PILOT_SPD_UP value.
4273 : return is in cm/s
4274 : */
4275 4375702 : uint16_t QuadPlane::get_pilot_velocity_z_max_dn_m() const
4276 : {
4277 4375702 : if (is_zero(pilot_speed_z_max_dn_ms)) {
4278 4375702 : return abs(pilot_speed_z_max_up_ms);
4279 : }
4280 0 : return abs(pilot_speed_z_max_dn_ms);
4281 : }
4282 :
4283 : /*
4284 : should we use the fixed wing attitude controllers for roll/pitch control
4285 : */
4286 15082558 : bool QuadPlane::use_fw_attitude_controllers(void) const
4287 : {
4288 27534948 : if (available() &&
4289 22344318 : motors->armed() &&
4290 9891928 : motors->get_desired_spool_state() >= AP_Motors::DesiredSpoolState::THROTTLE_UNLIMITED &&
4291 10463362 : !tailsitter.enabled() &&
4292 32649188 : poscontrol.get_state() != QPOS_AIRBRAKE &&
4293 5064200 : !force_fw_control_recovery) {
4294 :
4295 5001152 : if (in_vtol_mode()) {
4296 : // in VTOL modes always slave fixed wing to VTOL rate control
4297 4354554 : return false;
4298 : }
4299 :
4300 646598 : if (transition->use_multirotor_control_in_fwd_transition()) {
4301 : /*
4302 : special case for vectored yaw tiltrotors in forward
4303 : transition, keep multicopter control until we reach
4304 : target transition airspeed. This can result in loss of
4305 : yaw control on some tilt-vectored airframes without
4306 : strong VTOL yaw control
4307 : */
4308 35962 : return false;
4309 : }
4310 : }
4311 :
4312 10692042 : if (force_fw_control_recovery) {
4313 : // tell the roll controller not to apply roll rate limits
4314 270914 : plane.rollController.set_in_recovery();
4315 : }
4316 :
4317 10692042 : return true;
4318 : }
4319 :
4320 : /*
4321 : calculate our closing velocity vector on the landing point, taking
4322 : into account target velocity
4323 : */
4324 374283 : Vector2f QuadPlane::landing_closing_velocity_NE_ms()
4325 : {
4326 374283 : Vector2f landing_velocity_ne_m;
4327 374283 : if (AP_HAL::millis() - poscontrol.last_velocity_match_ms < 1000) {
4328 4670 : landing_velocity_ne_m = poscontrol.velocity_match_ms;
4329 : }
4330 374283 : return ahrs.groundspeed_vector() - landing_velocity_ne_m;
4331 : }
4332 :
4333 : /*
4334 : calculate our desired closing velocity vector on the landing point.
4335 : */
4336 164172 : Vector2f QuadPlane::landing_desired_closing_velocity_NE_ms()
4337 : {
4338 164172 : if (poscontrol.get_state() >= QPOS_LAND_DESCEND) {
4339 0 : return Vector2f(0,0);
4340 : }
4341 164172 : const Vector2f diff_wp_NE_m = plane.current_loc.get_distance_NE(plane.next_WP_loc);
4342 164172 : float dist_m = diff_wp_NE_m.length();
4343 164172 : if (dist_m < 1) {
4344 915 : return Vector2f(0,0);
4345 : }
4346 :
4347 : // base target speed based on sqrt of distance
4348 163257 : float target_speed_ms = safe_sqrt(2 * transition_decel_mss * dist_m);
4349 :
4350 : // don't let the target speed go above landing approach speed
4351 163257 : const float eas2tas = plane.ahrs.get_EAS2TAS();
4352 163257 : float land_speed_ms = plane.aparm.airspeed_cruise;
4353 163257 : float tecs_land_airspeed_ms = plane.TECS_controller.get_land_airspeed();
4354 163257 : if (is_positive(tecs_land_airspeed_ms)) {
4355 0 : land_speed_ms = tecs_land_airspeed_ms;
4356 : } else {
4357 : // use half way between min airspeed and cruise if
4358 : // TECS_LAND_AIRSPEED not set
4359 163257 : land_speed_ms = 0.5 * (land_speed_ms + plane.aparm.airspeed_min);
4360 : }
4361 163257 : target_speed_ms = MIN(target_speed_ms, eas2tas * land_speed_ms);
4362 :
4363 163257 : Vector2f target_speed_xy = diff_wp_NE_m.normalized() * target_speed_ms;
4364 :
4365 163257 : return target_speed_xy;
4366 : }
4367 :
4368 : /*
4369 : get target airspeed for landing, for use by TECS
4370 : */
4371 12306 : float QuadPlane::get_land_airspeed_ms(void)
4372 : {
4373 12306 : const auto qstate = poscontrol.get_state();
4374 12306 : if (qstate == QPOS_APPROACH ||
4375 7921 : plane.control_mode == &plane.mode_rtl) {
4376 4385 : const float cruise_speed_ms = plane.aparm.airspeed_cruise;
4377 : // assume cruise speed, but try to do better:
4378 4385 : float approach_speed_ms = cruise_speed_ms;
4379 4385 : float tecs_land_airspeed_ms = plane.TECS_controller.get_land_airspeed();
4380 4385 : if (is_positive(tecs_land_airspeed_ms)) {
4381 0 : approach_speed_ms = tecs_land_airspeed_ms;
4382 4385 : } else if (qstate == QPOS_APPROACH) {
4383 : // default to half way between min airspeed and cruise
4384 : // airspeed when on the approach
4385 4385 : approach_speed_ms = 0.5*(cruise_speed_ms + plane.aparm.airspeed_min);
4386 : }
4387 4385 : const float time_to_pos1 = (plane.auto_state.wp_distance - stopping_distance_m(sq(approach_speed_ms))) / MAX(approach_speed_ms, 5);
4388 : /*
4389 : slow down to landing approach speed as we get closer to landing
4390 : */
4391 4385 : approach_speed_ms = linear_interpolate(approach_speed_ms, cruise_speed_ms,
4392 : time_to_pos1,
4393 : 20, 60);
4394 4385 : return approach_speed_ms;
4395 : }
4396 :
4397 7921 : if (qstate == QPOS_AIRBRAKE) {
4398 : // during airbraking ask TECS to slow us to stall speed
4399 823 : return plane.aparm.airspeed_min;
4400 : }
4401 :
4402 : // calculate speed based on landing desired velocity
4403 7098 : Vector2f vel_ne_ms = landing_desired_closing_velocity_NE_ms();
4404 7098 : Vector3f wind_ned_ms;
4405 : // use the estimate even if it is not marked valid, to preserve
4406 : // existing behaviour
4407 7098 : IGNORE_RETURN(plane.ahrs.get_wind(wind_ned_ms));
4408 7098 : const float eas2tas = plane.ahrs.get_EAS2TAS();
4409 7098 : vel_ne_ms -= wind_ned_ms.xy();
4410 7098 : vel_ne_ms /= eas2tas;
4411 7098 : return vel_ne_ms.length();
4412 : }
4413 :
4414 6897704 : void QuadPlane::set_desired_spool_state(AP_Motors::DesiredSpoolState state)
4415 : {
4416 6897704 : if (motors->get_desired_spool_state() != state) {
4417 463098 : if (state == AP_Motors::DesiredSpoolState::SHUT_DOWN) {
4418 : // also request zero throttle, so we avoid the slow ramp down
4419 280 : motors->set_roll(0);
4420 280 : motors->set_pitch(0);
4421 280 : motors->set_yaw(0);
4422 280 : motors->set_throttle(0);
4423 : }
4424 463098 : motors->set_desired_spool_state(state);
4425 : }
4426 6897704 : }
4427 :
4428 77386 : bool QuadPlane::air_mode_active() const
4429 : {
4430 77386 : if ((air_mode == AirMode::ON) || ((air_mode == AirMode::ASSISTED_FLIGHT_ONLY) && assisted_flight)) {
4431 58482 : return true;
4432 : }
4433 18904 : return false;
4434 : }
4435 :
4436 : /*
4437 : return scaling factor for tilting rotors in forward flight throttle
4438 : we want to scale back tilt angle for roll/pitch by throttle in forward flight
4439 : */
4440 122243 : float QuadPlane::FW_vector_throttle_scaling()
4441 : {
4442 122243 : const float throttle = SRV_Channels::get_output_scaled(SRV_Channel::k_throttle) * 0.01;
4443 : // scale relative to a fixed 0.5 mid throttle so that changes in TRIM_THROTTLE in missions don't change
4444 : // the scaling of tilt
4445 122243 : const float mid_throttle = 0.5;
4446 122243 : return mid_throttle / constrain_float(throttle, 0.1, 1.0);
4447 : }
4448 :
4449 : QuadPlane *QuadPlane::_singleton = nullptr;
4450 :
4451 3430086 : bool SLT_Transition::set_FW_roll_limit(int32_t& roll_limit_cd)
4452 : {
4453 3846588 : if (quadplane.assisted_flight && (transition_state == State::AIRSPEED_WAIT || transition_state == State::TIMER) &&
4454 416502 : quadplane.option_is_set(QuadPlane::Option::LEVEL_TRANSITION)) {
4455 : // the user wants transitions to be kept level to within LEVEL_ROLL_LIMIT
4456 0 : roll_limit_cd = MIN(roll_limit_cd, plane.g.level_roll_limit*100);
4457 0 : return true;
4458 : }
4459 3430086 : return false;
4460 : }
4461 :
4462 510705 : bool SLT_Transition::allow_update_throttle_mix() const
4463 : {
4464 : // transition is directly managing throttle mix in these cases
4465 510705 : return !(quadplane.assisted_flight && (transition_state == State::AIRSPEED_WAIT || transition_state == State::TIMER));
4466 : }
4467 :
4468 15919047 : bool SLT_Transition::active_frwd() const
4469 : {
4470 : // We need to be in assisted flight...
4471 15919047 : if (!quadplane.assisted_flight) {
4472 14684631 : return false;
4473 : }
4474 : // ... and a transition must be active...
4475 1234416 : if (!((transition_state == State::AIRSPEED_WAIT) || (transition_state == State::TIMER))) {
4476 337 : return false;
4477 : }
4478 : // ... but not executing a QPOS_AIRBRAKE maneuver during an automated landing.
4479 1234079 : if (quadplane.in_vtol_airbrake()) {
4480 75080 : return false;
4481 : }
4482 :
4483 1158999 : return true;
4484 : }
4485 :
4486 : /*
4487 : limit VTOL roll/pitch in POSITION1, POSITION2 and waypoint controller. This serves three roles:
4488 : 1) an expanding envelope limit on pitch to prevent sudden pitch at the start of a back transition
4489 :
4490 : 2) limiting roll and pitch down to the Q_A_ANGLE_MAX, as the accel limits may push us beyond that for pitch up.
4491 : This is needed as the position controller doesn't have separate limits for pitch and roll
4492 :
4493 : 3) preventing us pitching up a lot when our airspeed may be low
4494 : enough that the real airspeed may be negative, which would result
4495 : in reversed control surfaces
4496 : */
4497 1409001 : bool SLT_Transition::set_VTOL_roll_pitch_limit(int32_t& roll_cd, int32_t& pitch_cd)
4498 : {
4499 1409001 : bool ret = false;
4500 1409001 : const float angle_max_cd = quadplane.attitude_control->lean_angle_max_cd();
4501 :
4502 : /*
4503 : we always limit roll to Q_A_ANGLE_MAX
4504 : */
4505 1409001 : int32_t new_roll_cd = constrain_int32(roll_cd, -angle_max_cd, angle_max_cd);
4506 1409001 : if (new_roll_cd != roll_cd) {
4507 0 : roll_cd = new_roll_cd;
4508 0 : ret = true;
4509 : }
4510 :
4511 : /*
4512 : always limit pitch down to Q_A_ANGLE_MAX. We need to do this as
4513 : the position controller accel limits may exceed this limit
4514 : */
4515 1409001 : if (pitch_cd < -angle_max_cd) {
4516 0 : pitch_cd = -angle_max_cd;
4517 0 : ret = true;
4518 : }
4519 :
4520 : /*
4521 : prevent trying to fly backwards (negative airspeed) at high
4522 : pitch angles, which can result in a high degree of instability
4523 : in SLT aircraft. This can happen with a tailwind in a back
4524 : transition, where the position controller (which is unaware of
4525 : airspeed) demands high pitch to hit the desired landing point
4526 : */
4527 : float airspeed;
4528 568 : if (pitch_cd > angle_max_cd &&
4529 1409569 : plane.ahrs.airspeed_EAS(airspeed) && airspeed < 0.5 * plane.aparm.airspeed_min) {
4530 0 : const float max_limit_cd = linear_interpolate(angle_max_cd, 4500,
4531 : airspeed,
4532 0 : 0, 0.5 * plane.aparm.airspeed_min);
4533 0 : if (pitch_cd > max_limit_cd) {
4534 0 : pitch_cd = max_limit_cd;
4535 0 : ret = true;
4536 : }
4537 : }
4538 :
4539 1409001 : if (quadplane.back_trans_pitch_limit_ms <= 0) {
4540 : // time based pitch envelope disabled
4541 0 : return ret;
4542 : }
4543 :
4544 1409001 : const uint32_t limit_time_ms = quadplane.back_trans_pitch_limit_ms;
4545 :
4546 1409001 : const uint32_t dt = AP_HAL::millis() - last_fw_mode_ms;
4547 1409001 : if (last_fw_mode_ms == 0 || dt > limit_time_ms) {
4548 : // we are beyond the time limit, don't apply envelope
4549 1306275 : last_fw_mode_ms = 0;
4550 1306275 : return ret;
4551 : }
4552 :
4553 : // we limit pitch during initial transition
4554 102726 : const float max_limit_cd = linear_interpolate(MAX(last_fw_nav_pitch_cd,0), MIN(angle_max_cd,plane.aparm.pitch_limit_max*100),
4555 : dt,
4556 : 0, limit_time_ms);
4557 :
4558 102726 : if (pitch_cd > max_limit_cd) {
4559 35475 : pitch_cd = max_limit_cd;
4560 35475 : return true;
4561 : }
4562 :
4563 : /*
4564 : limit the pitch down with an expanding envelope. This
4565 : prevents the velocity controller demanding nose down during
4566 : the initial slowdown if the target velocity curve is higher
4567 : than the actual velocity curve (for a high drag
4568 : aircraft). Nose down will cause a lot of downforce on the
4569 : wings which will draw a lot of current and also cause the
4570 : aircraft to lose altitude rapidly.pitch limit varies also with speed
4571 : to prevent inability to progress to position if moving from a loiter
4572 : to landing
4573 : */
4574 67251 : const float min_limit_cd = linear_interpolate(MIN(last_fw_nav_pitch_cd,0), MAX(-angle_max_cd,plane.aparm.pitch_limit_min*100),
4575 : dt,
4576 : 0, limit_time_ms);
4577 :
4578 67251 : if (plane.nav_pitch_cd < min_limit_cd) {
4579 1044 : plane.nav_pitch_cd = min_limit_cd;
4580 1044 : return true;
4581 : }
4582 :
4583 66207 : return ret;
4584 : }
4585 :
4586 : /*
4587 : remember last fixed wing pitch for pitch envelope in back transition
4588 : */
4589 3668748 : void SLT_Transition::set_last_fw_pitch()
4590 : {
4591 3668748 : last_fw_mode_ms = AP_HAL::millis();
4592 3668748 : last_fw_nav_pitch_cd = plane.nav_pitch_cd;
4593 3668748 : }
4594 :
4595 260392 : void SLT_Transition::force_transition_complete()
4596 : {
4597 260392 : transition_state = State::DONE;
4598 260392 : in_forced_transition = false;
4599 260392 : transition_start_ms = 0;
4600 260392 : transition_low_airspeed_ms = 0;
4601 260392 : set_last_fw_pitch();
4602 :
4603 : // Keep assistance reset while not checking
4604 260392 : quadplane.assist.reset();
4605 260392 : }
4606 :
4607 3428 : MAV_VTOL_STATE SLT_Transition::get_mav_vtol_state() const
4608 : {
4609 3428 : if (quadplane.in_vtol_land_approach()) {
4610 1052 : QuadPlane::position_control_state state = quadplane.poscontrol.get_state();
4611 1052 : if ((state == QuadPlane::position_control_state::QPOS_AIRBRAKE) || (state == QuadPlane::position_control_state::QPOS_POSITION1)) {
4612 384 : return MAV_VTOL_STATE_TRANSITION_TO_MC;
4613 : }
4614 : }
4615 3044 : if (quadplane.in_vtol_mode()) {
4616 1814 : return MAV_VTOL_STATE_MC;
4617 : }
4618 :
4619 1230 : switch (transition_state) {
4620 188 : case State::AIRSPEED_WAIT:
4621 : case State::TIMER:
4622 : // we enter this state during assisted flight, not just
4623 : // during a forward transition.
4624 188 : return MAV_VTOL_STATE_TRANSITION_TO_FW;
4625 :
4626 1042 : case State::DONE:
4627 1042 : return MAV_VTOL_STATE_FW;
4628 : }
4629 :
4630 0 : return MAV_VTOL_STATE_UNDEFINED;
4631 : }
4632 :
4633 : // Set FW roll and pitch limits and keep TECS informed
4634 6346294 : void SLT_Transition::set_FW_roll_pitch(int32_t& nav_pitch_cd, int32_t& nav_roll_cd)
4635 : {
4636 6346294 : if (quadplane.in_vtol_mode() || quadplane.in_vtol_airbrake()) {
4637 : // not in FW flight
4638 2671876 : return;
4639 : }
4640 :
4641 3674418 : if (transition_state == State::DONE) {
4642 : // transition complete, nothing to do
4643 3281995 : return;
4644 : }
4645 :
4646 392423 : if (!plane.control_mode->does_auto_throttle()) {
4647 : // don't limit pitch when in manually controlled modes like FBWA, ACRO
4648 71175 : return;
4649 : }
4650 :
4651 : float max_pitch;
4652 321248 : if (transition_state < State::TIMER) {
4653 157392 : if (plane.ahrs.groundspeed() < 3.0) {
4654 : // until we have some ground speed limit to zero pitch
4655 36239 : max_pitch = 0.0;
4656 : } else {
4657 121153 : max_pitch = quadplane.transition_pitch_max;
4658 : }
4659 : } else {
4660 163856 : max_pitch = (quadplane.transition_pitch_max+1.0)*2.0;
4661 : }
4662 :
4663 : // set a single loop pitch limit in TECS
4664 321248 : plane.TECS_controller.set_pitch_max(max_pitch);
4665 321248 : plane.TECS_controller.set_pitch_min(-max_pitch);
4666 :
4667 : // ensure pitch is constrained to limit
4668 321248 : nav_pitch_cd = constrain_int32(nav_pitch_cd, -max_pitch*100.0, max_pitch*100.0);
4669 : }
4670 :
4671 : /*
4672 : see if we are in a VTOL takeoff
4673 : */
4674 7686 : bool QuadPlane::in_vtol_takeoff(void) const
4675 : {
4676 7686 : if (in_vtol_auto() && is_vtol_takeoff(plane.mission.get_current_nav_cmd().id)) {
4677 949 : return true;
4678 : }
4679 6737 : return false;
4680 : }
4681 :
4682 : // called when we change mode (for any mode, not just Q modes)
4683 1930 : void QuadPlane::mode_enter(void)
4684 : {
4685 1930 : if (available()) {
4686 929 : pos_control->set_lean_angle_max_cd(0);
4687 : }
4688 1930 : poscontrol.correction_ne_m.zero();
4689 1930 : poscontrol.velocity_match_ms.zero();
4690 1930 : poscontrol.last_velocity_match_ms = 0;
4691 1930 : poscontrol.set_state(QuadPlane::QPOS_NONE);
4692 :
4693 : // Clear any pilot corrections
4694 1930 : poscontrol.pilot_correction_done = false;
4695 1930 : poscontrol.pilot_correction_active = false;
4696 1930 : poscontrol.target_vel_ms.zero();
4697 :
4698 : // clear guided takeoff wait on any mode change, but remember the
4699 : // state for special behaviour
4700 1930 : guided_wait_takeoff_on_mode_enter = guided_wait_takeoff;
4701 1930 : guided_wait_takeoff = false;
4702 :
4703 1930 : q_fwd_throttle = 0.0f;
4704 1930 : q_fwd_pitch_lim_cd = 100.0f * q_fwd_pitch_lim;
4705 :
4706 1930 : force_fw_control_recovery = false;
4707 1930 : in_spin_recovery = false;
4708 1930 : }
4709 :
4710 : // Set attitude control yaw rate time constant to pilot input command model value
4711 1935426 : void QuadPlane::set_pilot_yaw_rate_time_constant()
4712 : {
4713 1935426 : attitude_control->set_yaw_rate_tc(command_model_pilot.get_rate_tc());
4714 1935426 : }
4715 :
4716 : // Disable attitude control yaw rate time constant
4717 659925 : void QuadPlane::disable_yaw_rate_time_constant()
4718 : {
4719 659925 : attitude_control->set_yaw_rate_tc(0.0);
4720 659925 : }
4721 :
4722 : // Check if servo auto trim is allowed, only if countrol surfaces are fully in use
4723 0 : bool QuadPlane::allow_servo_auto_trim()
4724 : {
4725 0 : if (!available()) {
4726 : // Quadplane disabled, auto trim always allowed
4727 0 : return true;
4728 : }
4729 0 : if (in_vtol_mode()) {
4730 : // VTOL motors active in VTOL modes
4731 0 : return false;
4732 : }
4733 0 : if (!in_assisted_flight()) {
4734 : // In forward flight and VTOL motors not active
4735 0 : return true;
4736 : }
4737 0 : if (tailsitter.enabled() && option_is_set(QuadPlane::Option::TAILSIT_Q_ASSIST_MOTORS_ONLY)) {
4738 : // Tailsitter in forward flight, motors providing active stabalisation with motors only option
4739 : // Control surfaces are running as normal with I term active, motor I term is zeroed
4740 0 : return true;
4741 : }
4742 : // In forward flight with active VTOL motors
4743 0 : return false;
4744 : }
4745 :
4746 43322124 : bool QuadPlane::landing_with_fixed_wing_spiral_approach(void) const
4747 : {
4748 43322124 : const AP_Mission::Mission_Command cmd = plane.mission.get_current_nav_cmd();
4749 :
4750 43322123 : if (cmd.id == MAV_CMD_NAV_PAYLOAD_PLACE &&
4751 0 : option_is_set(QuadPlane::Option::MISSION_LAND_FW_APPROACH)) {
4752 0 : return true;
4753 : }
4754 :
4755 128916406 : return ((cmd.id == MAV_CMD_NAV_VTOL_LAND) &&
4756 42797147 : (option_is_set(QuadPlane::Option::MISSION_LAND_FW_APPROACH) ||
4757 86119259 : cmd.p1 == NAV_VTOL_LAND_OPTIONS_FW_SPIRAL_APPROACH));
4758 : }
4759 :
4760 : /*
4761 : setup scaling of roll and pitch angle P gains to match fixed wing gains
4762 :
4763 : we setup the angle P gain to match fixed wing at high speed (above
4764 : AIRSPEED_MIN) where fixed wing surfaces are presumed to
4765 : dominate. At lower speeds we use the multicopter angle P gains.
4766 : */
4767 217209 : void QuadPlane::setup_rp_fw_angle_gains(void)
4768 : {
4769 217209 : const float mc_angR = attitude_control->get_angle_roll_p().kP();
4770 217209 : const float mc_angP = attitude_control->get_angle_pitch_p().kP();
4771 217209 : const float fw_angR = plane.rollController.get_angle_p();
4772 217209 : const float fw_angP = plane.pitchController.get_angle_p();
4773 :
4774 217209 : if (!is_positive(mc_angR) || !is_positive(mc_angP)) {
4775 : // bad configuration, don't scale
4776 15764 : return;
4777 : }
4778 :
4779 : float aspeed;
4780 217209 : if (!ahrs.airspeed_EAS(aspeed)) {
4781 : // can't get airspeed, no scaling of VTOL angle gains
4782 0 : return;
4783 : }
4784 :
4785 217209 : const float low_airspeed = 3.0;
4786 217209 : if (aspeed <= low_airspeed || plane.aparm.airspeed_min <= low_airspeed) {
4787 : // no scaling
4788 15764 : return;
4789 : }
4790 :
4791 201445 : const float angR_scale = linear_interpolate(mc_angR, fw_angR,
4792 : aspeed,
4793 201445 : low_airspeed, plane.aparm.airspeed_min) / mc_angR;
4794 201445 : const float angP_scale = linear_interpolate(mc_angP, fw_angP,
4795 : aspeed,
4796 201445 : low_airspeed, plane.aparm.airspeed_min) / mc_angP;
4797 201445 : const Vector3f gain_scale{angR_scale, angP_scale, 1.0};
4798 201445 : attitude_control->set_angle_P_scale(gain_scale);
4799 : }
4800 :
4801 : /*
4802 : abort landing, used by scripting for payload place and ship landing abort
4803 : will return false if not in a landing descent
4804 : */
4805 0 : bool QuadPlane::abort_landing(void)
4806 : {
4807 0 : if (poscontrol.get_state() == QPOS_LAND_ABORT ||
4808 0 : !(plane.control_mode == &plane.mode_auto)) {
4809 : // already aborted or not in AUTO?
4810 0 : return false;
4811 : }
4812 :
4813 : // special case for payload place with full landing
4814 : const bool payload_place_landed =
4815 0 : plane.in_auto_mission_id(MAV_CMD_NAV_PAYLOAD_PLACE) &&
4816 0 : poscontrol.get_state() == QPOS_LAND_COMPLETE;
4817 :
4818 0 : if (!payload_place_landed && !in_vtol_land_descent()) {
4819 0 : return false;
4820 : }
4821 0 : poscontrol.set_state(QuadPlane::QPOS_LAND_ABORT);
4822 0 : return true;
4823 : }
4824 :
4825 : // Should we allow stick mixing from the pilot
4826 4132815 : bool QuadPlane::allow_stick_mixing() const
4827 : {
4828 4132815 : if (!available()) {
4829 : // Quadplane not enabled
4830 1167735 : return true;
4831 : }
4832 : // Ask transition logic
4833 2965080 : return transition->allow_stick_mixing();
4834 : }
4835 :
4836 : /*
4837 : return true if we should disable TECS in the current flight state
4838 : this ensures that TECS resets when we change height in a VTOL mode
4839 : */
4840 3282535 : bool QuadPlane::should_disable_TECS() const
4841 : {
4842 3282535 : if (in_vtol_land_descent()) {
4843 117695 : return true;
4844 : }
4845 3164840 : if (plane.control_mode == &plane.mode_guided &&
4846 243992 : plane.auto_state.vtol_loiter) {
4847 62132 : return true;
4848 : }
4849 3102708 : return false;
4850 : }
4851 :
4852 : // Get pilot throttle input with deadzone, this will return 50% throttle in failsafe!
4853 : // This is a re-implmentation of Plane::get_throttle_input
4854 : // Ignoring the no_deadzone case means we don't need to check for valid RC
4855 : // This is handled by Plane::control_failsafe setting of control in
4856 2920456 : float QuadPlane::get_throttle_input() const
4857 : {
4858 2920456 : float ret = plane.channel_throttle->get_control_in();
4859 2920456 : if (plane.reversed_throttle) {
4860 : // RC option for reverse throttle has been set
4861 0 : ret = -ret;
4862 : }
4863 2920456 : return ret;
4864 : }
4865 :
4866 : // return true if forward throttle from forward_throttle_pct() should be used
4867 5483490 : bool QuadPlane::allow_forward_throttle_in_vtol_mode() const
4868 : {
4869 5483490 : return in_vtol_mode() && motors->armed() && (motors->get_desired_spool_state() != AP_Motors::DesiredSpoolState::SHUT_DOWN);
4870 : }
4871 :
4872 0 : void QuadPlane::Log_Write_AttRate()
4873 : {
4874 0 : attitude_control->Write_ANG();
4875 0 : attitude_control->Write_Rate(*pos_control);
4876 :
4877 0 : }
4878 :
4879 : // check for loss of thrust and trigger thrust boost in motors library
4880 2737526 : void QuadPlane::thrust_loss_check(bool reset)
4881 : {
4882 : // Clear counter if reset
4883 2737526 : if (reset) {
4884 439255 : thrust_loss.counter = 0;
4885 2726181 : return;
4886 : }
4887 :
4888 : // Return if disabled, either completly or in the current flight mode
4889 4596542 : if (thrust_loss.option_is_set(ThrustLoss::Option::DISABLED) ||
4890 2298271 : (thrust_loss.option_is_set(ThrustLoss::Option::VTOL_ONLY) && !in_vtol_mode())) {
4891 0 : thrust_loss.counter = 0;
4892 0 : return;
4893 : }
4894 :
4895 : // return if already engaged, disarmed, not flying, or motors not active
4896 2298271 : if (motors->get_thrust_boost() || !motors->armed() || !plane.is_flying() || (motors->get_desired_spool_state() != AP_Motors::DesiredSpoolState::THROTTLE_UNLIMITED)) {
4897 17514 : thrust_loss.counter = 0;
4898 17514 : return;
4899 : }
4900 :
4901 : // check for desired angle under 15 degrees
4902 2280757 : const Vector3f& angle_target_rad = attitude_control->get_att_target_euler_rad();
4903 2280757 : if (angle_target_rad.xy().length_squared() > sq(radians(15.0))) {
4904 184071 : thrust_loss.counter = 0;
4905 184071 : return;
4906 : }
4907 :
4908 : // check for throttle over 90% or throttle saturation
4909 2096686 : if ((attitude_control->get_throttle_in() < 0.9) && (!motors->limit.throttle_upper)) {
4910 2047327 : thrust_loss.counter = 0;
4911 2047327 : return;
4912 : }
4913 :
4914 : // check throttle is over 25% to prevent checks triggering from thrust limitations caused by low commanded throttle
4915 49359 : if ((attitude_control->get_throttle_in() < 0.25f)) {
4916 745 : thrust_loss.counter = 0;
4917 745 : return;
4918 : }
4919 :
4920 : // check for descent
4921 48614 : Vector3f vel_NED;
4922 48614 : if (!ahrs.get_velocity_NED(vel_NED) || !is_positive(vel_NED.z)) {
4923 : // we have no vertical velocity estimate and/or we are not descending
4924 37269 : thrust_loss.counter = 0;
4925 37269 : return;
4926 : }
4927 :
4928 : // check for angle error over 30 degrees to ensure the aircraft has attitude control
4929 11345 : const float angle_error = attitude_control->get_att_error_angle_deg();
4930 11345 : if (angle_error >= 30.0) {
4931 0 : thrust_loss.counter = 0;
4932 0 : return;
4933 : }
4934 :
4935 : // the aircraft is descending with low requested roll and pitch, at full available throttle, with attitude control
4936 : // we may have lost thrust
4937 11345 : thrust_loss.counter++;
4938 :
4939 : // check if thrust loss for 1 second
4940 11345 : if (thrust_loss.counter >= plane.scheduler.get_loop_rate_hz()) {
4941 : // reset counter
4942 5 : thrust_loss.counter = 0;
4943 5 : LOGGER_WRITE_ERROR(LogErrorSubsystem::THRUST_LOSS_CHECK, LogErrorCode::FAILSAFE_OCCURRED);
4944 : // send message to gcs
4945 5 : gcs().send_text(MAV_SEVERITY_EMERGENCY, "Potential VTOL Thrust Loss (%u)", motors->get_lost_motor() + 1);
4946 : // enable thrust loss handling
4947 5 : motors->set_thrust_boost(true);
4948 : // the motors library disables this when it is no longer needed to achieve the commanded output
4949 : }
4950 : }
4951 :
4952 : #endif // HAL_QUADPLANE_ENABLED
|