Adding more servos
[inav.git] / src / main / flight / servos.c
blob5ac98f78215e59e0ad5d7d2c072a9d2a59db9941
1 /*
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/>.
18 #include <stdbool.h>
19 #include <stdint.h>
20 #include <stdlib.h>
21 #include <string.h>
23 #include "platform.h"
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"
57 #include "io/gps.h"
59 #include "rx/rx.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],
80 .targetChannel = 0,
81 .inputSource = 0,
82 .rate = 0,
83 .speed = 0
84 #ifdef USE_PROGRAMMING_FRAMEWORK
85 ,.conditionId = -1
86 #endif
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,
100 .rate = 100
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) {
134 return -1;
135 } else {
136 return 1;
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;
151 if (!firstRun) {
152 return;
154 minServoIndex = 255;
155 maxServoIndex = 0;
156 for (int j = 0; j < MAX_MIXER_PROFILE_COUNT; j++) {
157 for (int i = 0; i < MAX_SERVO_RULES; i++) {
158 // check if done
159 if (mixerServoMixersByIndex(j)[i].rate == 0){
160 break;
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;
172 firstRun = false;
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;
183 * load mixer
185 computeServoCount();
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;
203 else {
204 return 0;
208 void loadCustomServoMixer(void)
210 servoRuleCount = 0;
211 memset(currentServoMixer, 0, sizeof(currentServoMixer));
213 // load custom mixer into currentServoMixer
214 for (int i = 0; i < MAX_SERVO_RULES; i++) {
215 // check if done
216 if (customServoMixers(i)->rate == 0){
217 break;
219 currentServoMixer[servoRuleCount] = *customServoMixers(i);
220 servoSpeedLimitFilter[servoRuleCount].state = 0;
221 servoRuleCount++;
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)
250 filterServos();
252 #if !defined(SITL_BUILD)
253 int servoIndex = 0;
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);
266 } else {
267 pwmWriteServo(servoIndex++, servo[i]);
270 #endif
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];
281 } else {
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);
313 #endif
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);
318 } else {
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
331 // 1500 - 1500 = 0
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);
359 #endif
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;
368 } else {
369 input[INPUT_HEADTRACKER_PAN] = 0;
370 input[INPUT_HEADTRACKER_TILT] = 0;
371 input[INPUT_HEADTRACKER_ROLL] = 0;
373 #else
374 input[INPUT_HEADTRACKER_PAN] = 0;
375 input[INPUT_HEADTRACKER_TILT] = 0;
376 input[INPUT_HEADTRACKER_ROLL] = 0;
377 #endif
379 #ifdef USE_SIMULATOR
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];
384 #endif
386 for (int i = 0; i < MAX_SUPPORTED_SERVOS; i++) {
387 servo[i] = 0;
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)) {
402 inputRaw = 0;
404 #endif
406 * Apply mixer speed limit. 1 [one] speed unit is defined as 10us/s:
407 * 0 = no limiting
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++) {
420 * Apply servo rate
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
427 * min/max
429 if (servo[i] > 0) {
430 servo[i] = (int16_t) (servo[i] * servoMetadata[i].scaleMax);
431 } else {
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
465 typedef enum {
466 AUTOTRIM_IDLE,
467 AUTOTRIM_COLLECTING,
468 AUTOTRIM_SAVE_PENDING,
469 AUTOTRIM_DONE,
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)) {
482 switch (trimState) {
483 case AUTOTRIM_IDLE:
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;
500 else {
501 break;
503 // Fallthru
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
532 else {
533 trimState = AUTOTRIM_IDLE;
535 break;
537 case AUTOTRIM_SAVE_PENDING:
538 // Wait for disarm and save to EEPROM
539 if (!ARMING_FLAG(ARMED)) {
540 saveConfigAndNotify();
541 trimState = AUTOTRIM_DONE;
543 break;
545 case AUTOTRIM_DONE:
546 break;
549 else {
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;
590 if (
591 planeIsFlyingStraight &&
592 noRotationCommanded &&
593 planeIsFlyingLevel &&
594 sticksAreCentered &&
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)) {
607 continue;
609 #endif
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++;
625 // Reset timer
626 lastUpdateTimeMs = millis();
628 } else if (trimState == AUTOTRIM_COLLECTING) {
629 // We have disarmed, save midpoints to EEPROM
630 saveConfigAndNotify();
631 trimState = AUTOTRIM_IDLE;
634 // Debug
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) {
646 #ifdef USE_SIMULATOR
647 if (ARMING_FLAG(SIMULATOR_MODE_HITL)) {
648 return;
650 #endif
651 if(!STATE(AIRPLANE))
653 return;
655 if (feature(FEATURE_FW_AUTOTRIM)) {
656 processContinuousServoAutotrim(dT);
657 } else {
658 processServoAutotrimMode();
662 bool isServoOutputEnabled(void)
664 return servoOutputEnabled;
667 void setServoOutputEnabled(bool flag)
669 servoOutputEnabled = flag;
672 bool isMixerUsingServos(void)
674 return mixerUsesServos;