2 ******************************************************************************
3 * @addtogroup OpenPilotModules OpenPilot Modules
5 * @addtogroup StabilizationModule Stabilization Module
6 * @brief cruisecontrol mode
7 * @note This file implements the logic for a cruisecontrol
10 * @file cruisecontrol.h
11 * @author The OpenPilot Team, http://www.openpilot.org Copyright (C) 2014.
12 * @brief Attitude stabilization module.
14 * @see The GNU Public License (GPL) Version 3
16 *****************************************************************************/
18 * This program is free software; you can redistribute it and/or modify
19 * it under the terms of the GNU General Public License as published by
20 * the Free Software Foundation; either version 3 of the License, or
21 * (at your option) any later version.
23 * This program is distributed in the hope that it will be useful, but
24 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
25 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
28 * You should have received a copy of the GNU General Public License along
29 * with this program; if not, write to the Free Software Foundation, Inc.,
30 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
33 #include <openpilot.h>
34 #include <stabilization.h>
35 #include <attitudestate.h>
36 #include <sin_lookup.h>
38 static float cruisecontrol_factor
= 1.0f
;
41 static inline float CruiseControlLimitThrust(float thrust
)
43 // limit to user specified absolute max thrust
44 return boundf(thrust
, stabSettings
.cruiseControl
.min_thrust
, stabSettings
.cruiseControl
.max_thrust
);
47 // assumes 1.0 <= factor <= 100.0
48 // a factor of less than 1.0 could make it return a value less than stabSettings.cruiseControl.min_thrust
49 // CP helis need to have min_thrust=-1
51 // multicopters need to have min_thrust=0.05 or so
52 // values below that will not be subject to max / min limiting
53 // that means thrust can be less than min
54 // that means multicopter motors stop spinning at low stick
55 static inline float CruiseControlFactorToThrust(float factor
, float thrust
)
57 // don't touch thrust if it's less than min_thrust
58 // without that test, quadcopter props will spin up
59 // to min thrust even at zero throttle stick
60 // if Cruise Control is enabled on this flight switch position
61 if (thrust
> stabSettings
.cruiseControl
.min_thrust
) {
62 return CruiseControlLimitThrust(thrust
* factor
);
67 static float CruiseControlAngleToFactor(float angle
)
72 if (angle
> 89.999f
&& angle
< 90.001f
) {
73 factor
= stabSettings
.settings
.CruiseControlMaxPowerFactor
;
75 // the simple bank angle boost calculation that Cruise Control revolves around
76 factor
= 1.0f
/ fabsf(cos_lookup_deg(angle
));
77 // factor in the power trim, no effect at 1.0, linear effect increases with factor
78 factor
= (factor
- 1.0f
) * stabSettings
.cruiseControl
.power_trim
+ 1.0f
;
79 // limit to user specified max power multiplier
80 if (factor
> stabSettings
.settings
.CruiseControlMaxPowerFactor
) {
81 factor
= stabSettings
.settings
.CruiseControlMaxPowerFactor
;
88 void cruisecontrol_compute_factor(AttitudeStateData
*attitude
, float thrustDemand
)
90 static float previous_angle
;
91 static uint32_t previous_time
= 0;
92 static bool previous_time_valid
= false;
94 // For multiple, speedy flips this mainly strives to address the
95 // fact that (due to thrust delay) thrust didn't average straight
96 // down, but at an angle. For less speedy flips it acts like it
97 // used to. It can be turned off by setting power delay to 0.
99 // It takes significant time for the motors of a multi-copter to
100 // spin up. It takes significant time for the collective servo of
101 // a CP heli to move from one end to the other. Both of those are
102 // modeled here as linear, i.e. twice as much change takes twice
103 // as long. Given a correctly configured maximum delay time this
104 // code calculates how far in advance to start the control
105 // transition so that half way through the physical transition it
106 // is just crossing the transition angle.
107 // Example: Rotation rate = 360. Full stroke delay = 0.2
108 // Transition angle 90 degrees. Start the transition 0.1 second
109 // before 90 degrees (36 degrees at 360 deg/sec) and it will be
110 // complete 0.1 seconds after 90 degrees.
112 // Note that this code only handles the transition to/from inverted
113 // thrust. It doesn't handle the case where thrust is changed a
114 // lot in a small angle range when that range is close to 90 degrees.
115 // It doesn't handle the small constant "system delay" caused by the
116 // delay between reading sensors and actuators beginning to respond.
117 // It also assumes that the pilot is holding the throttle constant;
118 // when the pilot does change the throttle, the compensation is
119 // simply recalculated.
121 // This implementation of future thrust isn't perfect. That would
122 // probably require an iterative procedure for solving a
123 // transcendental equation of the form linear(x) = 1/cos(x). It's
124 // shortcomings generally don't hurt anything and work better than
125 // without it. It is designed to work perfectly if the pilot is
126 // using full thrust during flips and it is only activated if 70% or
127 // greater thrust is used.
129 uint32_t time
= PIOS_DELAY_GetuS();
131 // Get roll and pitch angles, calculate combined angle, and begin
132 // the general algorithm.
133 // Example: 45 degrees roll plus 45 degrees pitch = 60 degrees
134 // Do it every 8th iteration to save CPU.
135 if (time
!= previous_time
|| previous_time_valid
== false) {
136 float angle
, angle_unmodified
;
138 // spherical right triangle
139 // 0.0 <= angle <= 180.0
140 angle_unmodified
= angle
= RAD2DEG(acosf(cos_lookup_deg(attitude
->Roll
)
141 * cos_lookup_deg(attitude
->Pitch
)));
143 // Calculate rate as a combined (roll and pitch) bank angle
144 // change; in degrees per second. Rate is calculated over the
145 // most recent 8 loops through stabilization. We could have
146 // asked the gyros. This is probably cheaper.
147 if (previous_time_valid
) {
150 // rate can be negative.
151 rate
= (angle
- previous_angle
) / ((float)(time
- previous_time
) / 1000000.0f
);
153 // Define "within range" to be those transitions that should
154 // be executing now. Recall that each impulse transition is
155 // spread out over a range of time / angle.
157 // There is only one transition and the high power level for
159 // 1/fabsf(cos(angle)) * current thrust
160 // or max power factor * current thrust
162 // You can cross the transition with angle either increasing
163 // or decreasing (rate positive or negative).
165 // Thrust is never boosted for negative values of
166 // thrustDemand (negative stick values)
168 // When the aircraft is upright, thrust is always boosted
169 // . for positive values of thrustDemand
170 // When the aircraft is inverted, thrust is sometimes
171 // . boosted or reversed (or combinations thereof) or zeroed
172 // . for positive values of thrustDemand
173 // It depends on the inverted power settings.
174 // Of course, you can set MaxPowerFactor to 1.0 to
175 // . effectively disable boost.
176 if (thrustDemand
> 0.0f
) {
177 // to enable the future thrust calculations, make sure
178 // there is a large enough transition that the result
179 // will be roughly on vs. off; without that, it can
180 // exaggerate the length of time the inverted to upright
181 // transition holds full throttle and reduce the length
182 // of time for full throttle when going upright to inverted.
183 if (thrustDemand
> 0.7f
) {
186 thrust
= CruiseControlFactorToThrust(CruiseControlAngleToFactor((float)stabSettings
.settings
.CruiseControlMaxAngle
), thrustDemand
);
188 // determine if we are in range of the transition
190 // given the thrust at max_angle and thrustDemand
191 // (typically close to 1.0), change variable 'thrust' to
192 // be the proportion of the largest thrust change possible
193 // that occurs when going into inverted mode.
194 // Example: 'thrust' is 0.8 A quad has min_thrust set
195 // to 0.05 The difference is 0.75. The largest possible
196 // difference with this setup is 0.9 - 0.05 = 0.85, so
197 // the proportion is 0.75/0.85
198 // That is nearly a full throttle stroke.
199 // the 'thrust' variable is non-negative here
200 switch (stabSettings
.settings
.CruiseControlInvertedPowerOutput
) {
201 case STABILIZATIONSETTINGS_CRUISECONTROLINVERTEDPOWEROUTPUT_ZERO
:
202 // normal multi-copter case, stroke is max to zero
203 // technically max to constant min_thrust
205 thrust
= (thrust
- CruiseControlLimitThrust(0.0f
)) / stabSettings
.cruiseControl
.thrust_difference
;
207 case STABILIZATIONSETTINGS_CRUISECONTROLINVERTEDPOWEROUTPUT_NORMAL
:
208 // reversed but not boosted
209 // : CP heli case, stroke is max to -stick
210 // : thrust = (thrust - CruiseControlLimitThrust(-thrustDemand)) / stabSettings.cruiseControl.thrust_difference;
211 // else it is both unreversed and unboosted
212 // : simply turn off boost, stroke is max to +stick
213 // : thrust = (thrust - CruiseControlLimitThrust(thrustDemand)) / stabSettings.cruiseControl.thrust_difference;
214 thrust
= (thrust
- CruiseControlLimitThrust(
215 (stabSettings
.settings
.CruiseControlInvertedThrustReversing
216 == STABILIZATIONSETTINGS_CRUISECONTROLINVERTEDTHRUSTREVERSING_REVERSED
)
218 : thrustDemand
)) / stabSettings
.cruiseControl
.thrust_difference
;
220 case STABILIZATIONSETTINGS_CRUISECONTROLINVERTEDPOWEROUTPUT_BOOSTED
:
221 // if boosted and reversed
222 if (stabSettings
.settings
.CruiseControlInvertedThrustReversing
223 == STABILIZATIONSETTINGS_CRUISECONTROLINVERTEDTHRUSTREVERSING_REVERSED
) {
224 // CP heli case, stroke is max to min
225 thrust
= (thrust
- CruiseControlFactorToThrust(-CruiseControlAngleToFactor((float)stabSettings
.settings
.CruiseControlMaxAngle
), thrustDemand
)) / stabSettings
.cruiseControl
.thrust_difference
;
227 // else it is boosted and unreversed so the throttle doesn't change
229 // CP heli case, no transition, so stroke is zero
235 // 'thrust' is now the proportion of max stroke
236 // multiply this proportion of max stroke,
237 // times the max stroke time, to get this stroke time
238 // we only want half of this time before the transition
239 // (and half after the transition)
240 thrust
*= stabSettings
.cruiseControl
.half_power_delay
;
241 // 'thrust' is now the length of time for this stroke
242 // multiply that times angular rate to get the lead angle
243 thrust
*= fabsf(rate
);
244 // if the transition is within range we use it,
245 // else we just use the current calculated thrust
246 if ((float)stabSettings
.settings
.CruiseControlMaxAngle
- thrust
<= angle
247 && angle
<= (float)stabSettings
.settings
.CruiseControlMaxAngle
+ thrust
) {
248 // default to a little above max angle
249 angle
= (float)stabSettings
.settings
.CruiseControlMaxAngle
+ 0.01f
;
250 // if roll direction is downward
251 // then thrust value is taken from below max angle
252 // by the code that knows about the transition angle
257 } // if thrust > 0.7; else just use the angle we already calculated
258 cruisecontrol_factor
= CruiseControlAngleToFactor(angle
);
259 } else { // if thrust > 0 set factor from angle; else
260 cruisecontrol_factor
= 1.0f
;
263 if (angle
>= (float)stabSettings
.settings
.CruiseControlMaxAngle
) {
264 switch (stabSettings
.settings
.CruiseControlInvertedPowerOutput
) {
265 case STABILIZATIONSETTINGS_CRUISECONTROLINVERTEDPOWEROUTPUT_ZERO
:
266 cruisecontrol_factor
= 0.0f
;
268 case STABILIZATIONSETTINGS_CRUISECONTROLINVERTEDPOWEROUTPUT_NORMAL
:
269 cruisecontrol_factor
= 1.0f
;
271 case STABILIZATIONSETTINGS_CRUISECONTROLINVERTEDPOWEROUTPUT_BOOSTED
:
272 // no change, leave factor >= 1.0 alone
275 if (stabSettings
.settings
.CruiseControlInvertedThrustReversing
276 == STABILIZATIONSETTINGS_CRUISECONTROLINVERTEDTHRUSTREVERSING_REVERSED
) {
277 cruisecontrol_factor
= -cruisecontrol_factor
;
280 } // if previous_time_valid i.e. we've got a rate; else leave (angle and) factor alone
281 previous_time
= time
;
282 previous_time_valid
= true;
283 previous_angle
= angle_unmodified
;
287 float cruisecontrol_apply_factor(float raw
)
289 if (stabSettings
.settings
.CruiseControlMaxPowerFactor
> 0.0001f
) {
290 raw
= CruiseControlFactorToThrust(cruisecontrol_factor
, raw
);