2 * This file is part of Cleanflight.
4 * Cleanflight is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
9 * Cleanflight is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with Cleanflight. If not, see <http://www.gnu.org/licenses/>.
25 #include "build/debug.h"
26 #include "build/build_config.h"
28 #include "common/axis.h"
29 #include "common/filter.h"
30 #include "common/maths.h"
31 #include "programming/global_variables.h"
33 #include "config/config_reset.h"
34 #include "config/feature.h"
35 #include "config/parameter_group.h"
36 #include "config/parameter_group_ids.h"
38 #include "drivers/pwm_output.h"
39 #include "drivers/pwm_mapping.h"
40 #include "drivers/time.h"
41 #include "drivers/gimbal_common.h"
42 #include "drivers/headtracker_common.h"
44 #include "fc/config.h"
45 #include "fc/fc_core.h"
46 #include "fc/rc_controls.h"
47 #include "fc/rc_modes.h"
48 #include "fc/runtime_config.h"
49 #include "fc/controlrate_profile.h"
50 #include "fc/settings.h"
52 #include "flight/imu.h"
53 #include "flight/mixer.h"
54 #include "flight/pid.h"
55 #include "flight/servos.h"
61 #include "sensors/gyro.h"
63 PG_REGISTER_WITH_RESET_TEMPLATE(servoConfig_t
, servoConfig
, PG_SERVO_CONFIG
, 3);
65 PG_RESET_TEMPLATE(servoConfig_t
, servoConfig
,
66 .servoCenterPulse
= SETTING_SERVO_CENTER_PULSE_DEFAULT
,
67 .servoPwmRate
= SETTING_SERVO_PWM_RATE_DEFAULT
, // Default for analog servos
68 .servo_lowpass_freq
= SETTING_SERVO_LPF_HZ_DEFAULT
, // Default servo update rate is 50Hz, everything above Nyquist frequency (25Hz) is going to fold and cause distortions
69 .servo_protocol
= SETTING_SERVO_PROTOCOL_DEFAULT
,
70 .flaperon_throw_offset
= SETTING_FLAPERON_THROW_OFFSET_DEFAULT
,
71 .tri_unarmed_servo
= SETTING_TRI_UNARMED_SERVO_DEFAULT
,
72 .servo_autotrim_rotation_limit
= SETTING_SERVO_AUTOTRIM_ROTATION_LIMIT_DEFAULT
76 void Reset_servoMixers(servoMixer_t
*instance
)
78 for (int i
= 0; i
< MAX_SERVO_RULES
; i
++){
79 RESET_CONFIG(servoMixer_t
, &instance
[i
],
84 #ifdef USE_PROGRAMMING_FRAMEWORK
91 PG_REGISTER_ARRAY_WITH_RESET_FN(servoParam_t
, MAX_SUPPORTED_SERVOS
, servoParams
, PG_SERVO_PARAMS
, 3);
93 void pgResetFn_servoParams(servoParam_t
*instance
)
95 for (int i
= 0; i
< MAX_SUPPORTED_SERVOS
; i
++) {
96 RESET_CONFIG(servoParam_t
, &instance
[i
],
97 .min
= DEFAULT_SERVO_MIN
,
98 .max
= DEFAULT_SERVO_MAX
,
99 .middle
= DEFAULT_SERVO_MIDDLE
,
105 int16_t servo
[MAX_SUPPORTED_SERVOS
];
107 static uint8_t servoRuleCount
= 0;
108 static servoMixer_t currentServoMixer
[MAX_SERVO_RULES
];
111 //Was used to keep track of servo rules in all mixer_profile, In order to Apply mixer speed limit when rules turn off
112 static servoMixer_t currentServoMixer[MAX_SERVO_RULES*MAX_MIXER_PROFILE_COUNT];
113 static bool currentServoMixerActivative[MAX_SERVO_RULES*MAX_MIXER_PROFILE_COUNT]; // if true, the rule is used by current servo mixer
116 static bool servoOutputEnabled
;
118 static bool mixerUsesServos
;
119 static uint8_t minServoIndex
;
120 static uint8_t maxServoIndex
;
122 static biquadFilter_t servoFilter
[MAX_SUPPORTED_SERVOS
];
123 static bool servoFilterIsSet
;
125 static servoMetadata_t servoMetadata
[MAX_SUPPORTED_SERVOS
];
126 static rateLimitFilter_t servoSpeedLimitFilter
[MAX_SERVO_RULES
];
128 STATIC_FASTRAM pt1Filter_t rotRateFilter
;
129 STATIC_FASTRAM pt1Filter_t targetRateFilter
;
131 int16_t getFlaperonDirection(uint8_t servoPin
)
133 if (servoPin
== SERVO_FLAPPERON_2
) {
141 * Compute scaling factor for upper and lower servo throw
143 void servoComputeScalingFactors(uint8_t servoIndex
) {
144 servoMetadata
[servoIndex
].scaleMax
= (servoParams(servoIndex
)->max
- servoParams(servoIndex
)->middle
) / 500.0f
;
145 servoMetadata
[servoIndex
].scaleMin
= (servoParams(servoIndex
)->middle
- servoParams(servoIndex
)->min
) / 500.0f
;
148 void computeServoCount(void)
150 static bool firstRun
= true;
156 for (int j
= 0; j
< MAX_MIXER_PROFILE_COUNT
; j
++) {
157 for (int i
= 0; i
< MAX_SERVO_RULES
; i
++) {
159 if (mixerServoMixersByIndex(j
)[i
].rate
== 0){
162 if (mixerServoMixersByIndex(j
)[i
].targetChannel
< minServoIndex
) {
163 minServoIndex
= mixerServoMixersByIndex(j
)[i
].targetChannel
;
166 if (mixerServoMixersByIndex(j
)[i
].targetChannel
> maxServoIndex
) {
167 maxServoIndex
= mixerServoMixersByIndex(j
)[i
].targetChannel
;
169 mixerUsesServos
= true;
175 void servosInit(void)
177 // give all servos a default command
178 for (int i
= 0; i
< MAX_SUPPORTED_SERVOS
; i
++) {
179 servo
[i
] = servoParams(i
)->middle
;
186 loadCustomServoMixer();
188 // If there are servo rules after all, update variables
189 if (mixerUsesServos
) {
190 servoOutputEnabled
= true;
193 for (uint8_t i
= 0; i
< MAX_SUPPORTED_SERVOS
; i
++) {
194 servoComputeScalingFactors(i
);
198 int getServoCount(void)
200 if (mixerUsesServos
) {
201 return 1 + maxServoIndex
- minServoIndex
;
208 void loadCustomServoMixer(void)
211 memset(currentServoMixer
, 0, sizeof(currentServoMixer
));
213 // load custom mixer into currentServoMixer
214 for (int i
= 0; i
< MAX_SERVO_RULES
; i
++) {
216 if (customServoMixers(i
)->rate
== 0){
219 currentServoMixer
[servoRuleCount
] = *customServoMixers(i
);
220 servoSpeedLimitFilter
[servoRuleCount
].state
= 0;
225 static void filterServos(void)
227 if (servoConfig()->servo_lowpass_freq
) {
228 // Initialize servo lowpass filter (servos are calculated at looptime rate)
229 if (!servoFilterIsSet
) {
230 for (int i
= 0; i
< MAX_SUPPORTED_SERVOS
; i
++) {
231 biquadFilterInitLPF(&servoFilter
[i
], servoConfig()->servo_lowpass_freq
, getLooptime());
232 biquadFilterReset(&servoFilter
[i
], servo
[i
]);
234 servoFilterIsSet
= true;
237 for (int i
= 0; i
< MAX_SUPPORTED_SERVOS
; i
++) {
238 // Apply servo lowpass filter and do sanity cheching
239 servo
[i
] = (int16_t)lrintf(biquadFilterApply(&servoFilter
[i
], (float)servo
[i
]));
243 for (int i
= 0; i
< MAX_SUPPORTED_SERVOS
; i
++) {
244 servo
[i
] = constrain(servo
[i
], servoParams(i
)->min
, servoParams(i
)->max
);
248 void writeServos(void)
252 #if !defined(SITL_BUILD)
254 bool zeroServoValue
= false;
257 * in case of tricopters, there might me a need to zero servo output when unarmed
259 if (currentMixerConfig
.platformType
== PLATFORM_TRICOPTER
&& !ARMING_FLAG(ARMED
) && !servoConfig()->tri_unarmed_servo
) {
260 zeroServoValue
= true;
263 for (int i
= minServoIndex
; i
<= maxServoIndex
; i
++) {
264 if (zeroServoValue
) {
265 pwmWriteServo(servoIndex
++, 0);
267 pwmWriteServo(servoIndex
++, servo
[i
]);
273 void servoMixer(float dT
)
275 int16_t input
[INPUT_SOURCE_COUNT
]; // Range [-500:+500]
277 if (FLIGHT_MODE(MANUAL_MODE
)) {
278 input
[INPUT_STABILIZED_ROLL
] = rcCommand
[ROLL
];
279 input
[INPUT_STABILIZED_PITCH
] = rcCommand
[PITCH
];
280 input
[INPUT_STABILIZED_YAW
] = rcCommand
[YAW
];
282 // Assisted modes (gyro only or gyro+acc according to AUX configuration in Gui
283 input
[INPUT_STABILIZED_ROLL
] = axisPID
[ROLL
];
284 input
[INPUT_STABILIZED_PITCH
] = axisPID
[PITCH
];
285 input
[INPUT_STABILIZED_YAW
] = axisPID
[YAW
];
287 // Reverse yaw servo when inverted in 3D mode only for multirotor and tricopter
288 if (feature(FEATURE_REVERSIBLE_MOTORS
) && (rxGetChannelValue(THROTTLE
) < PWM_RANGE_MIDDLE
) &&
289 (currentMixerConfig
.platformType
== PLATFORM_MULTIROTOR
|| currentMixerConfig
.platformType
== PLATFORM_TRICOPTER
)) {
290 input
[INPUT_STABILIZED_YAW
] *= -1;
294 input
[INPUT_STABILIZED_ROLL_PLUS
] = constrain(input
[INPUT_STABILIZED_ROLL
], 0, 1000);
295 input
[INPUT_STABILIZED_ROLL_MINUS
] = constrain(input
[INPUT_STABILIZED_ROLL
], -1000, 0);
296 input
[INPUT_STABILIZED_PITCH_PLUS
] = constrain(input
[INPUT_STABILIZED_PITCH
], 0, 1000);
297 input
[INPUT_STABILIZED_PITCH_MINUS
] = constrain(input
[INPUT_STABILIZED_PITCH
], -1000, 0);
298 input
[INPUT_STABILIZED_YAW_PLUS
] = constrain(input
[INPUT_STABILIZED_YAW
], 0, 1000);
299 input
[INPUT_STABILIZED_YAW_MINUS
] = constrain(input
[INPUT_STABILIZED_YAW
], -1000, 0);
301 input
[INPUT_FEATURE_FLAPS
] = FLIGHT_MODE(FLAPERON
) ? servoConfig()->flaperon_throw_offset
: 0;
303 input
[INPUT_MAX
] = 500;
304 #ifdef USE_PROGRAMMING_FRAMEWORK
305 input
[INPUT_GVAR_0
] = constrain(gvGet(0), -1000, 1000);
306 input
[INPUT_GVAR_1
] = constrain(gvGet(1), -1000, 1000);
307 input
[INPUT_GVAR_2
] = constrain(gvGet(2), -1000, 1000);
308 input
[INPUT_GVAR_3
] = constrain(gvGet(3), -1000, 1000);
309 input
[INPUT_GVAR_4
] = constrain(gvGet(4), -1000, 1000);
310 input
[INPUT_GVAR_5
] = constrain(gvGet(5), -1000, 1000);
311 input
[INPUT_GVAR_6
] = constrain(gvGet(6), -1000, 1000);
312 input
[INPUT_GVAR_7
] = constrain(gvGet(7), -1000, 1000);
315 if (IS_RC_MODE_ACTIVE(BOXCAMSTAB
)) {
316 input
[INPUT_GIMBAL_PITCH
] = scaleRange(attitude
.values
.pitch
, -900, 900, -500, +500);
317 input
[INPUT_GIMBAL_ROLL
] = scaleRange(attitude
.values
.roll
, -1800, 1800, -500, +500);
319 input
[INPUT_GIMBAL_PITCH
] = 0;
320 input
[INPUT_GIMBAL_ROLL
] = 0;
323 input
[INPUT_STABILIZED_THROTTLE
] = mixerThrottleCommand
- 1000 - 500; // Since it derives from rcCommand or mincommand and must be [-500:+500]
325 input
[INPUT_MIXER_TRANSITION
] = isMixerTransitionMixing
* 500; //fixed value
327 // center the RC input value around the RC middle value
328 // by subtracting the RC middle value from the RC input value, we get:
329 // data - middle = input
330 // 2000 - 1500 = +500
332 // 1000 - 1500 = -500
333 #define GET_RX_CHANNEL_INPUT(x) (rxGetChannelValue(x) - PWM_RANGE_MIDDLE)
334 input
[INPUT_RC_ROLL
] = GET_RX_CHANNEL_INPUT(ROLL
);
335 input
[INPUT_RC_PITCH
] = GET_RX_CHANNEL_INPUT(PITCH
);
336 input
[INPUT_RC_YAW
] = GET_RX_CHANNEL_INPUT(YAW
);
337 input
[INPUT_RC_THROTTLE
] = GET_RX_CHANNEL_INPUT(THROTTLE
);
338 input
[INPUT_RC_CH5
] = GET_RX_CHANNEL_INPUT(AUX1
);
339 input
[INPUT_RC_CH6
] = GET_RX_CHANNEL_INPUT(AUX2
);
340 input
[INPUT_RC_CH7
] = GET_RX_CHANNEL_INPUT(AUX3
);
341 input
[INPUT_RC_CH8
] = GET_RX_CHANNEL_INPUT(AUX4
);
342 input
[INPUT_RC_CH9
] = GET_RX_CHANNEL_INPUT(AUX5
);
343 input
[INPUT_RC_CH10
] = GET_RX_CHANNEL_INPUT(AUX6
);
344 input
[INPUT_RC_CH11
] = GET_RX_CHANNEL_INPUT(AUX7
);
345 input
[INPUT_RC_CH12
] = GET_RX_CHANNEL_INPUT(AUX8
);
346 input
[INPUT_RC_CH13
] = GET_RX_CHANNEL_INPUT(AUX9
);
347 input
[INPUT_RC_CH14
] = GET_RX_CHANNEL_INPUT(AUX10
);
348 input
[INPUT_RC_CH15
] = GET_RX_CHANNEL_INPUT(AUX11
);
349 input
[INPUT_RC_CH16
] = GET_RX_CHANNEL_INPUT(AUX12
);
350 input
[INPUT_RC_CH17
] = GET_RX_CHANNEL_INPUT(AUX13
);
351 input
[INPUT_RC_CH18
] = GET_RX_CHANNEL_INPUT(AUX14
);
352 #ifdef USE_24CHANNELS
353 input
[INPUT_RC_CH19
] = GET_RX_CHANNEL_INPUT(AUX15
);
354 input
[INPUT_RC_CH20
] = GET_RX_CHANNEL_INPUT(AUX16
);
355 input
[INPUT_RC_CH21
] = GET_RX_CHANNEL_INPUT(AUX17
);
356 input
[INPUT_RC_CH22
] = GET_RX_CHANNEL_INPUT(AUX18
);
357 input
[INPUT_RC_CH23
] = GET_RX_CHANNEL_INPUT(AUX19
);
358 input
[INPUT_RC_CH24
] = GET_RX_CHANNEL_INPUT(AUX20
);
360 #undef GET_RX_CHANNEL_INPUT
362 #ifdef USE_HEADTRACKER
363 headTrackerDevice_t
*dev
= headTrackerCommonDevice();
364 if(dev
&& headTrackerCommonIsValid(dev
) && !IS_RC_MODE_ACTIVE(BOXGIMBALCENTER
)) {
365 input
[INPUT_HEADTRACKER_PAN
] = headTrackerCommonGetPanPWM(dev
) - PWM_RANGE_MIDDLE
;
366 input
[INPUT_HEADTRACKER_TILT
] = headTrackerCommonGetTiltPWM(dev
) - PWM_RANGE_MIDDLE
;
367 input
[INPUT_HEADTRACKER_ROLL
] = headTrackerCommonGetRollPWM(dev
) - PWM_RANGE_MIDDLE
;
369 input
[INPUT_HEADTRACKER_PAN
] = 0;
370 input
[INPUT_HEADTRACKER_TILT
] = 0;
371 input
[INPUT_HEADTRACKER_ROLL
] = 0;
374 input
[INPUT_HEADTRACKER_PAN
] = 0;
375 input
[INPUT_HEADTRACKER_TILT
] = 0;
376 input
[INPUT_HEADTRACKER_ROLL
] = 0;
380 simulatorData
.input
[INPUT_STABILIZED_ROLL
] = input
[INPUT_STABILIZED_ROLL
];
381 simulatorData
.input
[INPUT_STABILIZED_PITCH
] = input
[INPUT_STABILIZED_PITCH
];
382 simulatorData
.input
[INPUT_STABILIZED_YAW
] = input
[INPUT_STABILIZED_YAW
];
383 simulatorData
.input
[INPUT_STABILIZED_THROTTLE
] = input
[INPUT_STABILIZED_THROTTLE
];
386 for (int i
= 0; i
< MAX_SUPPORTED_SERVOS
; i
++) {
390 // mix servos according to rules
391 for (int i
= 0; i
< servoRuleCount
; i
++) {
392 const uint8_t target
= currentServoMixer
[i
].targetChannel
;
393 const uint8_t from
= currentServoMixer
[i
].inputSource
;
395 int16_t inputRaw
= input
[from
];
398 * Check if conditions for a rule are met, not all conditions apply all the time
400 #ifdef USE_PROGRAMMING_FRAMEWORK
401 if (!logicConditionGetValue(currentServoMixer
[i
].conditionId
)) {
406 * Apply mixer speed limit. 1 [one] speed unit is defined as 10us/s:
408 * 1 = 10us/s -> full servo sweep (from 1000 to 2000) is performed in 100s
409 * 10 = 100us/s -> full sweep (from 1000 to 2000) is performed in 10s
410 * 100 = 1000us/s -> full sweep in 1s
412 int16_t inputLimited
= (int16_t) rateLimitFilterApply4(&servoSpeedLimitFilter
[i
], inputRaw
, currentServoMixer
[i
].speed
* 10, dT
);
414 servo
[target
] += ((int32_t)inputLimited
* currentServoMixer
[i
].rate
) / 100;
417 for (int i
= 0; i
< MAX_SUPPORTED_SERVOS
; i
++) {
422 servo
[i
] = ((int32_t)servoParams(i
)->rate
* servo
[i
]) / 100L;
425 * Perform acumulated servo output scaling to match servo min and max values
426 * Important: is servo rate is > 100%, total servo output might be bigger than
430 servo
[i
] = (int16_t) (servo
[i
] * servoMetadata
[i
].scaleMax
);
432 servo
[i
] = (int16_t) (servo
[i
] * servoMetadata
[i
].scaleMin
);
436 * Add a servo midpoint to the calculation
438 servo
[i
] += servoParams(i
)->middle
;
441 * Constrain servo position to min/max to prevent servo damage
442 * If servo was saturated above min/max, that means that user most probably
443 * allowed the situation when smix weight sum for an output was above 100
445 servo
[i
] = constrain(servo
[i
], servoParams(i
)->min
, servoParams(i
)->max
);
449 * When not armed, apply servo low position to all outputs that include a throttle or stabilizet throttle in the mix
451 if (!ARMING_FLAG(ARMED
)) {
452 for (int i
= 0; i
< servoRuleCount
; i
++) {
453 const uint8_t target
= currentServoMixer
[i
].targetChannel
;
454 const uint8_t from
= currentServoMixer
[i
].inputSource
;
456 if (from
== INPUT_STABILIZED_THROTTLE
|| from
== INPUT_RC_THROTTLE
) {
457 servo
[target
] = motorConfig()->mincommand
;
463 #define SERVO_AUTOTRIM_TIMER_MS 2000
468 AUTOTRIM_SAVE_PENDING
,
470 } servoAutotrimState_e
;
472 void processServoAutotrimMode(void)
474 static servoAutotrimState_e trimState
= AUTOTRIM_IDLE
;
475 static timeMs_t trimStartedAt
;
477 static int16_t servoMiddleBackup
[MAX_SUPPORTED_SERVOS
];
478 static int32_t servoMiddleAccum
[MAX_SUPPORTED_SERVOS
];
479 static int32_t servoMiddleAccumCount
[MAX_SUPPORTED_SERVOS
];
481 if (isFwAutoModeActive(BOXAUTOTRIM
)) {
484 if (ARMING_FLAG(ARMED
)) {
485 for (int axis
= FD_ROLL
; axis
<= FD_YAW
; axis
++) {
486 for (int i
= 0; i
< servoRuleCount
; i
++) {
487 // Reset servo middle accumulator
488 const uint8_t target
= currentServoMixer
[i
].targetChannel
;
489 const uint8_t source
= currentServoMixer
[i
].inputSource
;
490 if (source
== axis
) {
491 servoMiddleBackup
[target
] = servoParams(target
)->middle
;
492 servoMiddleAccum
[target
] = 0;
493 servoMiddleAccumCount
[target
] = 0;
497 trimStartedAt
= millis();
498 trimState
= AUTOTRIM_COLLECTING
;
505 case AUTOTRIM_COLLECTING
:
506 if (ARMING_FLAG(ARMED
)) {
507 for (int axis
= FD_ROLL
; axis
<= FD_YAW
; axis
++) {
508 for (int i
= 0; i
< servoRuleCount
; i
++) {
509 const uint8_t target
= currentServoMixer
[i
].targetChannel
;
510 const uint8_t source
= currentServoMixer
[i
].inputSource
;
511 if (source
== axis
) {
512 servoMiddleAccum
[target
] += servo
[target
];
513 servoMiddleAccumCount
[target
]++;
518 if ((millis() - trimStartedAt
) > SERVO_AUTOTRIM_TIMER_MS
) {
519 for (int axis
= FD_ROLL
; axis
<= FD_YAW
; axis
++) {
520 for (int i
= 0; i
< servoRuleCount
; i
++) {
521 const uint8_t target
= currentServoMixer
[i
].targetChannel
;
522 const uint8_t source
= currentServoMixer
[i
].inputSource
;
523 if (source
== axis
) {
524 servoParamsMutable(target
)->middle
= servoMiddleAccum
[target
] / servoMiddleAccumCount
[target
];
528 trimState
= AUTOTRIM_SAVE_PENDING
;
529 pidResetErrorAccumulators(); //Reset Iterm since new midpoints override previously acumulated errors
533 trimState
= AUTOTRIM_IDLE
;
537 case AUTOTRIM_SAVE_PENDING
:
538 // Wait for disarm and save to EEPROM
539 if (!ARMING_FLAG(ARMED
)) {
540 saveConfigAndNotify();
541 trimState
= AUTOTRIM_DONE
;
550 // We are deactivating servo trim - restore servo midpoints
551 if (trimState
== AUTOTRIM_SAVE_PENDING
) {
552 for (int axis
= FD_ROLL
; axis
<= FD_YAW
; axis
++) {
553 for (int i
= 0; i
< servoRuleCount
; i
++) {
554 const uint8_t target
= currentServoMixer
[i
].targetChannel
;
555 const uint8_t source
= currentServoMixer
[i
].inputSource
;
556 if (source
== axis
) {
557 servoParamsMutable(target
)->middle
= servoMiddleBackup
[target
];
563 trimState
= AUTOTRIM_IDLE
;
567 #define SERVO_AUTOTRIM_FILTER_CUTOFF 1 // LPF cutoff frequency
568 #define SERVO_AUTOTRIM_CENTER_MIN 1300
569 #define SERVO_AUTOTRIM_CENTER_MAX 1700
570 #define SERVO_AUTOTRIM_UPDATE_SIZE 5
571 #define SERVO_AUTOTRIM_ATTITUDE_LIMIT 50 // 5 degrees
573 void processContinuousServoAutotrim(const float dT
)
575 static timeMs_t lastUpdateTimeMs
;
576 static servoAutotrimState_e trimState
= AUTOTRIM_IDLE
;
577 static uint32_t servoMiddleUpdateCount
;
579 const float rotRateMagnitudeFiltered
= pt1FilterApply4(&rotRateFilter
, fast_fsqrtf(vectorNormSquared(&imuMeasuredRotationBF
)), SERVO_AUTOTRIM_FILTER_CUTOFF
, dT
);
580 const float targetRateMagnitudeFiltered
= pt1FilterApply4(&targetRateFilter
, getTotalRateTarget(), SERVO_AUTOTRIM_FILTER_CUTOFF
, dT
);
582 if (ARMING_FLAG(ARMED
)) {
583 trimState
= AUTOTRIM_COLLECTING
;
584 if ((millis() - lastUpdateTimeMs
) > 500) {
585 const bool planeIsFlyingStraight
= rotRateMagnitudeFiltered
<= DEGREES_TO_RADIANS(servoConfig()->servo_autotrim_rotation_limit
);
586 const bool noRotationCommanded
= targetRateMagnitudeFiltered
<= servoConfig()->servo_autotrim_rotation_limit
;
587 const bool sticksAreCentered
= !areSticksDeflected();
588 const bool planeIsFlyingLevel
= ABS(attitude
.values
.pitch
+ DEGREES_TO_DECIDEGREES(getFixedWingLevelTrim())) <= SERVO_AUTOTRIM_ATTITUDE_LIMIT
589 && ABS(attitude
.values
.roll
) <= SERVO_AUTOTRIM_ATTITUDE_LIMIT
;
591 planeIsFlyingStraight
&&
592 noRotationCommanded
&&
593 planeIsFlyingLevel
&&
595 !FLIGHT_MODE(MANUAL_MODE
) &&
596 isGPSHeadingValid() // TODO: proper flying detection
598 // Plane is flying straight and level: trim servos
599 for (int axis
= FD_ROLL
; axis
<= FD_PITCH
; axis
++) {
600 // For each stabilized axis, add 5 units of I-term to all associated servo midpoints
601 const float axisIterm
= getAxisIterm(axis
);
602 if (fabsf(axisIterm
) > SERVO_AUTOTRIM_UPDATE_SIZE
) {
603 const int8_t ItermUpdate
= axisIterm
> 0.0f
? SERVO_AUTOTRIM_UPDATE_SIZE
: -SERVO_AUTOTRIM_UPDATE_SIZE
;
604 for (int i
= 0; i
< servoRuleCount
; i
++) {
605 #ifdef USE_PROGRAMMING_FRAMEWORK
606 if (!logicConditionGetValue(currentServoMixer
[i
].conditionId
)) {
610 const uint8_t target
= currentServoMixer
[i
].targetChannel
;
611 const uint8_t source
= currentServoMixer
[i
].inputSource
;
612 if (source
== axis
) {
613 // Convert axis I-term to servo PWM and add to midpoint
614 const float mixerRate
= currentServoMixer
[i
].rate
/ 100.0f
;
615 const float servoRate
= servoParams(target
)->rate
/ 100.0f
;
616 servoParamsMutable(target
)->middle
+= (int16_t)(ItermUpdate
* mixerRate
* servoRate
);
617 servoParamsMutable(target
)->middle
= constrain(servoParamsMutable(target
)->middle
, SERVO_AUTOTRIM_CENTER_MIN
, SERVO_AUTOTRIM_CENTER_MAX
);
620 pidReduceErrorAccumulators(ItermUpdate
, axis
);
623 servoMiddleUpdateCount
++;
626 lastUpdateTimeMs
= millis();
628 } else if (trimState
== AUTOTRIM_COLLECTING
) {
629 // We have disarmed, save midpoints to EEPROM
630 saveConfigAndNotify();
631 trimState
= AUTOTRIM_IDLE
;
635 DEBUG_SET(DEBUG_AUTOTRIM
, 0, servoParams(2)->middle
);
636 DEBUG_SET(DEBUG_AUTOTRIM
, 2, servoParams(3)->middle
);
637 DEBUG_SET(DEBUG_AUTOTRIM
, 4, servoParams(4)->middle
);
638 DEBUG_SET(DEBUG_AUTOTRIM
, 6, servoParams(5)->middle
);
639 DEBUG_SET(DEBUG_AUTOTRIM
, 1, servoMiddleUpdateCount
);
640 DEBUG_SET(DEBUG_AUTOTRIM
, 3, MAX(RADIANS_TO_DEGREES(rotRateMagnitudeFiltered
), targetRateMagnitudeFiltered
));
641 DEBUG_SET(DEBUG_AUTOTRIM
, 5, axisPID_I
[FD_ROLL
]);
642 DEBUG_SET(DEBUG_AUTOTRIM
, 7, axisPID_I
[FD_PITCH
]);
645 void processServoAutotrim(const float dT
) {
647 if (ARMING_FLAG(SIMULATOR_MODE_HITL
)) {
655 if (feature(FEATURE_FW_AUTOTRIM
)) {
656 processContinuousServoAutotrim(dT
);
658 processServoAutotrimMode();
662 bool isServoOutputEnabled(void)
664 return servoOutputEnabled
;
667 void setServoOutputEnabled(bool flag
)
669 servoOutputEnabled
= flag
;
672 bool isMixerUsingServos(void)
674 return mixerUsesServos
;