2 ******************************************************************************
3 * @addtogroup OpenPilotModules OpenPilot Modules
5 * @addtogroup CameraStab Camera Stabilization Module
6 * @brief Camera stabilization module
7 * Updates accessory outputs with values appropriate for camera stabilization
11 * @author The OpenPilot Team, http://www.openpilot.org Copyright (C) 2010.
12 * @brief Stabilize camera against the roll pitch and yaw of aircraft
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
34 * Output object: Accessory
36 * This module will periodically calculate the output values for stabilizing the camera
38 * UAVObjects are automatically generated by the UAVObjectGenerator from
39 * the object definition XML file.
41 * Modules have no API, all communication to other modules is done through UAVObjects.
42 * However modules may use the API exposed by shared libraries.
43 * See the OpenPilot wiki for more details.
44 * http://www.openpilot.org/OpenPilot_Application_Architecture
48 #include "openpilot.h"
50 #include "accessorydesired.h"
51 #include "attitudestate.h"
52 #include "mixersettings.h"
53 #include "actuatorcommand.h"
54 #include "camerastabsettings.h"
55 #include "cameradesired.h"
56 #include "hwsettings.h"
61 #define SAMPLE_PERIOD_MS 10
66 static bool gimbalOutputEnabled
= false;
68 static struct CameraStab_data
{
69 portTickType lastSysTime
;
70 float inputs
[CAMERASTABSETTINGS_INPUT_NUMELEM
];
73 float attitudeFiltered
[CAMERASTABSETTINGS_INPUT_NUMELEM
];
77 float ffLastAttitude
[CAMERASTABSETTINGS_INPUT_NUMELEM
];
78 float ffLastAttitudeFiltered
[CAMERASTABSETTINGS_INPUT_NUMELEM
];
79 float ffFilterAccumulator
[CAMERASTABSETTINGS_INPUT_NUMELEM
];
84 static void attitudeUpdated(UAVObjEvent
*ev
);
87 static void applyFeedForward(uint8_t index
, float dT
, float *attitude
, CameraStabSettingsData
*cameraStab
);
90 // this structure is equivalent to the UAVObjects for one mixer.
94 } __attribute__((packed
)) Mixer_t
;
98 * Initialise the module, called on startup
99 * \returns 0 on success or -1 if initialisation failed
101 int32_t CameraStabInitialize(void)
103 bool cameraStabEnabled
;
105 HwSettingsOptionalModulesData optionalModules
;
107 HwSettingsOptionalModulesGet(&optionalModules
);
109 #ifdef MODULE_CAMERASTAB_BUILTIN
110 cameraStabEnabled
= true;
111 optionalModules
.CameraStab
= HWSETTINGS_OPTIONALMODULES_ENABLED
;
112 HwSettingsOptionalModulesSet(&optionalModules
);
114 if (optionalModules
.CameraStab
== HWSETTINGS_OPTIONALMODULES_ENABLED
) {
115 cameraStabEnabled
= true;
117 cameraStabEnabled
= false;
121 if (cameraStabEnabled
) {
122 // allocate and initialize the static data storage only if module is enabled
123 csd
= (struct CameraStab_data
*)pios_malloc(sizeof(struct CameraStab_data
));
128 // initialize camera state variables
129 memset(csd
, 0, sizeof(struct CameraStab_data
));
130 csd
->lastSysTime
= xTaskGetTickCount();
132 AttitudeStateInitialize();
133 CameraDesiredInitialize();
136 .obj
= AttitudeStateHandle(),
139 .lowPriority
= false,
141 EventPeriodicCallbackCreate(&ev
, attitudeUpdated
, SAMPLE_PERIOD_MS
/ portTICK_RATE_MS
);
149 /* stub: module has no module thread */
150 int32_t CameraStabStart(void)
155 MODULE_INITCALL(CameraStabInitialize
, CameraStabStart
);
157 static void attitudeUpdated(UAVObjEvent
*ev
)
159 if (ev
->obj
!= AttitudeStateHandle()) {
163 if (!gimbalOutputEnabled
) {
164 MixerSettingsData mixerSettings
;
165 MixerSettingsGet(&mixerSettings
);
166 Mixer_t
*mixers
= (Mixer_t
*)&mixerSettings
.Mixer1Type
;
167 for (int ct
= 0; ct
< ACTUATORCOMMAND_CHANNEL_NUMELEM
; ct
++) {
168 uint8_t mixer_type
= mixers
[ct
].type
;
169 if ((mixer_type
>= MIXERSETTINGS_MIXER1TYPE_CAMERAROLLORSERVO1
) &&
170 (mixer_type
<= MIXERSETTINGS_MIXER1TYPE_CAMERAYAW
)) {
171 gimbalOutputEnabled
= true;
177 AccessoryDesiredData accessory
;
179 CameraStabSettingsData cameraStab
;
180 CameraStabSettingsGet(&cameraStab
);
182 // check how long since last update, time delta between calls in ms
183 portTickType thisSysTime
= xTaskGetTickCount();
184 float dT_millis
= (thisSysTime
> csd
->lastSysTime
) ?
185 (float)((thisSysTime
- csd
->lastSysTime
) * portTICK_RATE_MS
) :
186 (float)SAMPLE_PERIOD_MS
;
187 csd
->lastSysTime
= thisSysTime
;
189 // storage for elevon roll component before the pitch component has been generated
190 // we are guaranteed that the iteration order of i is roll pitch yaw
191 // that guarnteees this won't be used uninited, but the compiler doesn't know that
192 // so we init it or turn the warning/error off for each compiler
193 float elevon_roll
= 0.0f
;
196 for (uint8_t i
= 0; i
< CAMERASTABSETTINGS_INPUT_NUMELEM
; i
++) {
197 // read and process control input
198 if (CameraStabSettingsInputToArray(cameraStab
.Input
)[i
] != CAMERASTABSETTINGS_INPUT_NONE
) {
199 if (AccessoryDesiredInstGet(CameraStabSettingsInputToArray(cameraStab
.Input
)[i
] -
200 CAMERASTABSETTINGS_INPUT_ACCESSORY0
, &accessory
) == 0) {
202 switch (CameraStabSettingsStabilizationModeToArray(cameraStab
.StabilizationMode
)[i
]) {
203 case CAMERASTABSETTINGS_STABILIZATIONMODE_ATTITUDE
:
204 csd
->inputs
[i
] = accessory
.AccessoryVal
*
205 CameraStabSettingsInputRangeToArray(cameraStab
.InputRange
)[i
];
207 case CAMERASTABSETTINGS_STABILIZATIONMODE_AXISLOCK
:
208 input_rate
= accessory
.AccessoryVal
*
209 CameraStabSettingsInputRateToArray(cameraStab
.InputRate
)[i
];
210 if (fabsf(input_rate
) > cameraStab
.MaxAxisLockRate
) {
211 csd
->inputs
[i
] = boundf(csd
->inputs
[i
] + input_rate
* 0.001f
* dT_millis
,
212 -CameraStabSettingsInputRangeToArray(cameraStab
.InputRange
)[i
],
213 CameraStabSettingsInputRangeToArray(cameraStab
.InputRange
)[i
]);
222 // calculate servo output
226 case CAMERASTABSETTINGS_INPUT_ROLL
:
227 AttitudeStateRollGet(&attitude
);
229 case CAMERASTABSETTINGS_INPUT_PITCH
:
230 AttitudeStatePitchGet(&attitude
);
232 case CAMERASTABSETTINGS_INPUT_YAW
:
233 AttitudeStateYawGet(&attitude
);
239 #ifdef USE_GIMBAL_LPF
240 if (CameraStabSettingsResponseTimeToArray(cameraStab
.ResponseTime
)[i
]) {
241 float rt
= (float)CameraStabSettingsResponseTimeToArray(cameraStab
.ResponseTime
)[i
];
242 attitude
= csd
->attitudeFiltered
[i
] = ((rt
* csd
->attitudeFiltered
[i
]) + (dT_millis
* attitude
)) / (rt
+ dT_millis
);
247 if (CameraStabSettingsFeedForwardToArray(cameraStab
.FeedForward
)[i
]) {
248 applyFeedForward(i
, dT_millis
, &attitude
, &cameraStab
);
252 // bounding for elevon mixing occurs on the unmixed output
253 // to limit the range of the mixed output you must limit the range
254 // of both the unmixed pitch and unmixed roll
255 float output
= boundf((attitude
+ csd
->inputs
[i
]) / CameraStabSettingsOutputRangeToArray(cameraStab
.OutputRange
)[i
], -1.0f
, 1.0f
);
257 // set output channels
259 case CAMERASTABSETTINGS_INPUT_ROLL
:
260 // we are guaranteed that the iteration order of i is roll pitch yaw
261 // for elevon mixing we simply grab the value for later use
262 if (cameraStab
.GimbalType
== CAMERASTABSETTINGS_GIMBALTYPE_ROLLPITCHMIXED
) {
263 elevon_roll
= output
;
265 CameraDesiredRollOrServo1Set(&output
);
268 case CAMERASTABSETTINGS_INPUT_PITCH
:
269 // we are guaranteed that the iteration order of i is roll pitch yaw
270 // for elevon mixing we use the value we previously grabbed and set both s1 and s2
271 if (cameraStab
.GimbalType
== CAMERASTABSETTINGS_GIMBALTYPE_ROLLPITCHMIXED
) {
272 float elevon_pitch
= output
;
273 // elevon reversing works like this:
274 // first use the normal reversing facilities to get servo 1 roll working in the correct direction
275 // then use the normal reversing facilities to get servo 2 roll working in the correct direction
276 // then use these new reversing switches to reverse servo 1 and/or 2 pitch as needed
277 // if servo 1 pitch is reversed
278 if (cameraStab
.Servo1PitchReverse
== CAMERASTABSETTINGS_SERVO1PITCHREVERSE_TRUE
) {
279 // use (reversed pitch) + roll
280 output
= ((1.0f
- elevon_pitch
) + elevon_roll
) / 2.0f
;
283 output
= (elevon_pitch
+ elevon_roll
) / 2.0f
;
285 CameraDesiredRollOrServo1Set(&output
);
286 // if servo 2 pitch is reversed
287 if (cameraStab
.Servo2PitchReverse
== CAMERASTABSETTINGS_SERVO2PITCHREVERSE_TRUE
) {
288 // use (reversed pitch) - roll
289 output
= ((1.0f
- elevon_pitch
) - elevon_roll
) / 2.0f
;
292 output
= (elevon_pitch
- elevon_roll
) / 2.0f
;
294 CameraDesiredPitchOrServo2Set(&output
);
296 CameraDesiredPitchOrServo2Set(&output
);
299 case CAMERASTABSETTINGS_INPUT_YAW
:
300 CameraDesiredYawSet(&output
);
309 void applyFeedForward(uint8_t index
, float dT_millis
, float *attitude
, CameraStabSettingsData
*cameraStab
)
311 // compensate high feed forward values depending on gimbal type
312 float gimbalTypeCorrection
= 1.0f
;
314 switch (cameraStab
->GimbalType
) {
315 case CAMERASTABSETTINGS_GIMBALTYPE_GENERIC
:
316 case CAMERASTABSETTINGS_GIMBALTYPE_ROLLPITCHMIXED
:
319 case CAMERASTABSETTINGS_GIMBALTYPE_YAWROLLPITCH
:
320 if (index
== CAMERASTABSETTINGS_INPUT_ROLL
) {
322 AttitudeStatePitchGet(&pitch
);
323 gimbalTypeCorrection
= (cameraStab
->OutputRange
.Pitch
- fabsf(pitch
))
324 / cameraStab
->OutputRange
.Pitch
;
327 case CAMERASTABSETTINGS_GIMBALTYPE_YAWPITCHROLL
:
328 if (index
== CAMERASTABSETTINGS_INPUT_PITCH
) {
330 AttitudeStateRollGet(&roll
);
331 gimbalTypeCorrection
= (cameraStab
->OutputRange
.Roll
- fabsf(roll
))
332 / cameraStab
->OutputRange
.Roll
;
339 // apply feed forward
340 float accumulator
= csd
->ffFilterAccumulator
[index
];
341 accumulator
+= (*attitude
- csd
->ffLastAttitude
[index
]) *
342 (float)CameraStabSettingsFeedForwardToArray(cameraStab
->FeedForward
)[index
] * gimbalTypeCorrection
;
343 csd
->ffLastAttitude
[index
] = *attitude
;
344 *attitude
+= accumulator
;
346 float filter
= (float)((accumulator
> 0.0f
) ? CameraStabSettingsAccelTimeToArray(cameraStab
->AccelTime
)[index
] :
347 CameraStabSettingsDecelTimeToArray(cameraStab
->DecelTime
)[index
]) / dT_millis
;
351 accumulator
-= accumulator
/ filter
;
352 csd
->ffFilterAccumulator
[index
] = accumulator
;
353 *attitude
+= accumulator
;
355 // apply acceleration limit
356 float delta
= *attitude
- csd
->ffLastAttitudeFiltered
[index
];
357 float maxDelta
= (float)cameraStab
->MaxAccel
* 0.001f
* dT_millis
;
359 if (fabsf(delta
) > maxDelta
) {
360 // we are accelerating too hard
361 *attitude
= csd
->ffLastAttitudeFiltered
[index
] + ((delta
> 0.0f
) ? maxDelta
: -maxDelta
);
363 csd
->ffLastAttitudeFiltered
[index
] = *attitude
;
365 #endif // USE_GIMBAL_FF