08302140650b4c57456a5265e2ad2c1468a388aa
[fw/altos] / src / core / ao_flight.c
1 /*
2  * Copyright © 2009 Keith Packard <keithp@keithp.com>
3  *
4  * This program 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; version 2 of the License.
7  *
8  * This program is distributed in the hope that it will be useful, but
9  * WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
11  * General Public License for more details.
12  *
13  * You should have received a copy of the GNU General Public License along
14  * with this program; if not, write to the Free Software Foundation, Inc.,
15  * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
16  */
17
18 #ifndef AO_FLIGHT_TEST
19 #include "ao.h"
20 #include <ao_log.h>
21 #endif
22
23 #if HAS_MPU6000
24 #include <ao_quaternion.h>
25 #endif
26
27 #ifndef HAS_ACCEL
28 #error Please define HAS_ACCEL
29 #endif
30
31 #ifndef HAS_GPS
32 #error Please define HAS_GPS
33 #endif
34
35 #ifndef HAS_USB
36 #error Please define HAS_USB
37 #endif
38
39 #ifndef HAS_TELEMETRY
40 #define HAS_TELEMETRY   HAS_RADIO
41 #endif
42
43 /* Main flight thread. */
44
45 __pdata enum ao_flight_state    ao_flight_state;        /* current flight state */
46 __pdata uint16_t                ao_boost_tick;          /* time of launch detect */
47 __pdata uint16_t                ao_motor_number;        /* number of motors burned so far */
48
49 #if HAS_SENSOR_ERRORS
50 /* Any sensor can set this to mark the flight computer as 'broken' */
51 __xdata uint8_t                 ao_sensor_errors;
52 #endif
53
54 /*
55  * track min/max data over a long interval to detect
56  * resting
57  */
58 static __data uint16_t          ao_interval_end;
59 static __data int16_t           ao_interval_min_height;
60 static __data int16_t           ao_interval_max_height;
61 #if HAS_ACCEL
62 static __data int16_t           ao_coast_avg_accel;
63 #endif
64
65 __pdata uint8_t                 ao_flight_force_idle;
66
67 /* We also have a clock, which can be used to sanity check things in
68  * case of other failures
69  */
70
71 #define BOOST_TICKS_MAX AO_SEC_TO_TICKS(15)
72
73 /* Landing is detected by getting constant readings from both pressure and accelerometer
74  * for a fairly long time (AO_INTERVAL_TICKS)
75  */
76 #define AO_INTERVAL_TICKS       AO_SEC_TO_TICKS(10)
77
78 #define abs(a)  ((a) < 0 ? -(a) : (a))
79
80 void
81 ao_flight(void)
82 {
83         ao_sample_init();
84         ao_flight_state = ao_flight_startup;
85         for (;;) {
86
87                 /*
88                  * Process ADC samples, just looping
89                  * until the sensors are calibrated.
90                  */
91                 if (!ao_sample())
92                         continue;
93
94                 switch (ao_flight_state) {
95                 case ao_flight_startup:
96
97                         /* Check to see what mode we should go to.
98                          *  - Invalid mode if accel cal appears to be out
99                          *  - pad mode if we're upright,
100                          *  - idle mode otherwise
101                          */
102 #if HAS_ACCEL
103                         if (ao_config.accel_plus_g == 0 ||
104                             ao_config.accel_minus_g == 0 ||
105                             ao_ground_accel < ao_config.accel_plus_g - ACCEL_NOSE_UP ||
106                             ao_ground_accel > ao_config.accel_minus_g + ACCEL_NOSE_UP ||
107                             ao_ground_height < -1000 ||
108                             ao_ground_height > 7000)
109                         {
110                                 /* Detected an accel value outside -1.5g to 1.5g
111                                  * (or uncalibrated values), so we go into invalid mode
112                                  */
113                                 ao_flight_state = ao_flight_invalid;
114
115 #if HAS_RADIO && PACKET_HAS_SLAVE
116                                 /* Turn on packet system in invalid mode on TeleMetrum */
117                                 ao_packet_slave_start();
118 #endif
119                         } else
120 #endif
121                                 if (!ao_flight_force_idle
122 #if HAS_ACCEL
123                                     && ao_ground_accel < ao_config.accel_plus_g + ACCEL_NOSE_UP
124 #endif
125                                         )
126                         {
127                                 /* Set pad mode - we can fly! */
128                                 ao_flight_state = ao_flight_pad;
129 #if HAS_USB && !HAS_FLIGHT_DEBUG && !HAS_SAMPLE_PROFILE
130                                 /* Disable the USB controller in flight mode
131                                  * to save power
132                                  */
133                                 ao_usb_disable();
134 #endif
135
136 #if !HAS_ACCEL && PACKET_HAS_SLAVE
137                                 /* Disable packet mode in pad state on TeleMini */
138                                 ao_packet_slave_stop();
139 #endif
140
141 #if HAS_TELEMETRY
142                                 /* Turn on telemetry system */
143                                 ao_rdf_set(1);
144                                 ao_telemetry_set_interval(AO_TELEMETRY_INTERVAL_PAD);
145 #endif
146 #if HAS_LED
147                                 /* signal successful initialization by turning off the LED */
148                                 ao_led_off(AO_LED_RED);
149 #endif
150                         } else {
151                                 /* Set idle mode */
152                                 ao_flight_state = ao_flight_idle;
153 #if HAS_SENSOR_ERRORS
154                                 if (ao_sensor_errors)
155                                         ao_flight_state = ao_flight_invalid;
156 #endif
157  
158 #if HAS_ACCEL && HAS_RADIO && PACKET_HAS_SLAVE
159                                 /* Turn on packet system in idle mode on TeleMetrum */
160                                 ao_packet_slave_start();
161 #endif
162
163 #if HAS_LED
164                                 /* signal successful initialization by turning off the LED */
165                                 ao_led_off(AO_LED_RED);
166 #endif
167                         }
168                         /* wakeup threads due to state change */
169                         ao_wakeup(DATA_TO_XDATA(&ao_flight_state));
170
171                         break;
172                 case ao_flight_pad:
173
174                         /* pad to boost:
175                          *
176                          * barometer: > 20m vertical motion
177                          *             OR
178                          * accelerometer: > 2g AND velocity > 5m/s
179                          *
180                          * The accelerometer should always detect motion before
181                          * the barometer, but we use both to make sure this
182                          * transition is detected. If the device
183                          * doesn't have an accelerometer, then ignore the
184                          * speed and acceleration as they are quite noisy
185                          * on the pad.
186                          */
187                         if (ao_height > AO_M_TO_HEIGHT(20)
188 #if HAS_ACCEL
189                             || (ao_accel > AO_MSS_TO_ACCEL(20) &&
190                                 ao_speed > AO_MS_TO_SPEED(5))
191 #endif
192                                 )
193                         {
194                                 ao_flight_state = ao_flight_boost;
195                                 ao_boost_tick = ao_sample_tick;
196
197                                 /* start logging data */
198                                 ao_log_start();
199
200 #if HAS_TELEMETRY
201                                 /* Increase telemetry rate */
202                                 ao_telemetry_set_interval(AO_TELEMETRY_INTERVAL_FLIGHT);
203
204                                 /* disable RDF beacon */
205                                 ao_rdf_set(0);
206 #endif
207
208 #if HAS_GPS
209                                 /* Record current GPS position by waking up GPS log tasks */
210                                 ao_gps_new = AO_GPS_NEW_DATA | AO_GPS_NEW_TRACKING;
211                                 ao_wakeup(&ao_gps_new);
212 #endif
213
214                                 ao_wakeup(DATA_TO_XDATA(&ao_flight_state));
215                         }
216                         break;
217                 case ao_flight_boost:
218
219                         /* boost to fast:
220                          *
221                          * accelerometer: start to fall at > 1/4 G
222                          *              OR
223                          * time: boost for more than 15 seconds
224                          *
225                          * Detects motor burn out by the switch from acceleration to
226                          * deceleration, or by waiting until the maximum burn duration
227                          * (15 seconds) has past.
228                          */
229                         if ((ao_accel < AO_MSS_TO_ACCEL(-2.5) && ao_height > AO_M_TO_HEIGHT(100)) ||
230                             (int16_t) (ao_sample_tick - ao_boost_tick) > BOOST_TICKS_MAX)
231                         {
232 #if HAS_ACCEL
233                                 ao_flight_state = ao_flight_fast;
234                                 ao_coast_avg_accel = ao_accel;
235 #else
236                                 ao_flight_state = ao_flight_coast;
237 #endif
238                                 ++ao_motor_number;
239                                 ao_wakeup(DATA_TO_XDATA(&ao_flight_state));
240                         }
241                         break;
242 #if HAS_ACCEL
243                 case ao_flight_fast:
244                         /*
245                          * This is essentially the same as coast,
246                          * but the barometer is being ignored as
247                          * it may be unreliable.
248                          */
249                         if (ao_speed < AO_MS_TO_SPEED(AO_MAX_BARO_SPEED))
250                         {
251                                 ao_flight_state = ao_flight_coast;
252                                 ao_wakeup(DATA_TO_XDATA(&ao_flight_state));
253                         } else
254                                 goto check_re_boost;
255                         break;
256 #endif
257                 case ao_flight_coast:
258
259                         /*
260                          * By customer request - allow the user
261                          * to lock out apogee detection for a specified
262                          * number of seconds.
263                          */
264                         if (ao_config.apogee_lockout) {
265                                 if ((ao_sample_tick - ao_boost_tick) <
266                                     AO_SEC_TO_TICKS(ao_config.apogee_lockout))
267                                         break;
268                         }
269
270                         /* apogee detect: coast to drogue deploy:
271                          *
272                          * speed: < 0
273                          *
274                          * Also make sure the model altitude is tracking
275                          * the measured altitude reasonably closely; otherwise
276                          * we're probably transsonic.
277                          */
278                         if (ao_speed < 0
279 #if !HAS_ACCEL
280                             && (ao_sample_alt >= AO_MAX_BARO_HEIGHT || ao_error_h_sq_avg < 100)
281 #endif
282                                 )
283                         {
284 #if HAS_IGNITE
285                                 /* ignite the drogue charge */
286                                 ao_ignite(ao_igniter_drogue);
287 #endif
288
289 #if HAS_TELEMETRY
290                                 /* slow down the telemetry system */
291                                 ao_telemetry_set_interval(AO_TELEMETRY_INTERVAL_RECOVER);
292
293                                 /* Turn the RDF beacon back on */
294                                 ao_rdf_set(1);
295 #endif
296
297                                 /* and enter drogue state */
298                                 ao_flight_state = ao_flight_drogue;
299                                 ao_wakeup(DATA_TO_XDATA(&ao_flight_state));
300                         }
301 #if HAS_ACCEL
302                         else {
303                         check_re_boost:
304                                 ao_coast_avg_accel = ao_coast_avg_accel - (ao_coast_avg_accel >> 6) + (ao_accel >> 6);
305                                 if (ao_coast_avg_accel > AO_MSS_TO_ACCEL(20)) {
306                                         ao_boost_tick = ao_sample_tick;
307                                         ao_flight_state = ao_flight_boost;
308                                         ao_wakeup(DATA_TO_XDATA(&ao_flight_state));
309                                 }
310                         }
311 #endif
312
313                         break;
314                 case ao_flight_drogue:
315
316                         /* drogue to main deploy:
317                          *
318                          * barometer: reach main deploy altitude
319                          *
320                          * Would like to use the accelerometer for this test, but
321                          * the orientation of the flight computer is unknown after
322                          * drogue deploy, so we ignore it. Could also detect
323                          * high descent rate using the pressure sensor to
324                          * recognize drogue deploy failure and eject the main
325                          * at that point. Perhaps also use the drogue sense lines
326                          * to notice continutity?
327                          */
328                         if (ao_height <= ao_config.main_deploy)
329                         {
330 #if HAS_IGNITE
331                                 ao_ignite(ao_igniter_main);
332 #endif
333
334                                 /*
335                                  * Start recording min/max height
336                                  * to figure out when the rocket has landed
337                                  */
338
339                                 /* initialize interval values */
340                                 ao_interval_end = ao_sample_tick + AO_INTERVAL_TICKS;
341
342                                 ao_interval_min_height = ao_interval_max_height = ao_avg_height;
343
344                                 ao_flight_state = ao_flight_main;
345                                 ao_wakeup(DATA_TO_XDATA(&ao_flight_state));
346                         }
347                         break;
348
349                         /* fall through... */
350                 case ao_flight_main:
351
352                         /* main to land:
353                          *
354                          * barometer: altitude stable
355                          */
356
357                         if (ao_avg_height < ao_interval_min_height)
358                                 ao_interval_min_height = ao_avg_height;
359                         if (ao_avg_height > ao_interval_max_height)
360                                 ao_interval_max_height = ao_avg_height;
361
362                         if ((int16_t) (ao_sample_tick - ao_interval_end) >= 0) {
363                                 if (ao_interval_max_height - ao_interval_min_height <= AO_M_TO_HEIGHT(4))
364                                 {
365                                         ao_flight_state = ao_flight_landed;
366
367                                         /* turn off the ADC capture */
368                                         ao_timer_set_adc_interval(0);
369
370                                         ao_wakeup(DATA_TO_XDATA(&ao_flight_state));
371                                 }
372                                 ao_interval_min_height = ao_interval_max_height = ao_avg_height;
373                                 ao_interval_end = ao_sample_tick + AO_INTERVAL_TICKS;
374                         }
375                         break;
376 #if HAS_FLIGHT_DEBUG
377                 case ao_flight_test:
378 #if HAS_GYRO
379                         printf ("angle %4d pitch %7d yaw %7d roll %7d\n",
380                                 ao_sample_orient,
381                                 ((ao_sample_pitch << 9) - ao_ground_pitch) >> 9,
382                                 ((ao_sample_yaw << 9) - ao_ground_yaw) >> 9,
383                                 ((ao_sample_roll << 9) - ao_ground_roll) >> 9);
384 #endif
385                         flush();
386                         break;
387 #endif /* HAS_FLIGHT_DEBUG */
388                 default:
389                         break;
390                 }
391         }
392 }
393
394 #if HAS_FLIGHT_DEBUG
395 static inline int int_part(int16_t i)   { return i >> 4; }
396 static inline int frac_part(int16_t i)  { return ((i & 0xf) * 100 + 8) / 16; }
397
398 static void
399 ao_flight_dump(void)
400 {
401 #if HAS_ACCEL
402         int16_t accel;
403
404         accel = ((ao_ground_accel - ao_sample_accel) * ao_accel_scale) >> 16;
405 #endif
406
407         printf ("sample:\n");
408         printf ("  tick        %d\n", ao_sample_tick);
409         printf ("  raw pres    %d\n", ao_sample_pres);
410 #if HAS_ACCEL
411         printf ("  raw accel   %d\n", ao_sample_accel);
412 #endif
413         printf ("  ground pres %d\n", ao_ground_pres);
414         printf ("  ground alt  %d\n", ao_ground_height);
415 #if HAS_ACCEL
416         printf ("  raw accel   %d\n", ao_sample_accel);
417         printf ("  groundaccel %d\n", ao_ground_accel);
418         printf ("  accel_2g    %d\n", ao_accel_2g);
419 #endif
420
421         printf ("  alt         %d\n", ao_sample_alt);
422         printf ("  height      %d\n", ao_sample_height);
423 #if HAS_ACCEL
424         printf ("  accel       %d.%02d\n", int_part(accel), frac_part(accel));
425 #endif
426
427
428         printf ("kalman:\n");
429         printf ("  height      %d\n", ao_height);
430         printf ("  speed       %d.%02d\n", int_part(ao_speed), frac_part(ao_speed));
431         printf ("  accel       %d.%02d\n", int_part(ao_accel), frac_part(ao_accel));
432         printf ("  max_height  %d\n", ao_max_height);
433         printf ("  avg_height  %d\n", ao_avg_height);
434         printf ("  error_h     %d\n", ao_error_h);
435         printf ("  error_avg   %d\n", ao_error_h_sq_avg);
436 }
437
438 static void
439 ao_gyro_test(void)
440 {
441         ao_flight_state = ao_flight_test;
442         ao_getchar();
443         ao_flight_state = ao_flight_idle;
444 }
445
446 uint8_t ao_orient_test;
447
448 static void
449 ao_orient_test_select(void)
450 {
451         ao_orient_test = !ao_orient_test;
452 }
453
454 __code struct ao_cmds ao_flight_cmds[] = {
455         { ao_flight_dump,       "F\0Dump flight status" },
456         { ao_gyro_test,         "G\0Test gyro code" },
457         { ao_orient_test_select,"O\0Test orientation code" },
458         { 0, NULL },
459 };
460 #endif
461
462 static __xdata struct ao_task   flight_task;
463
464 void
465 ao_flight_init(void)
466 {
467         ao_flight_state = ao_flight_startup;
468 #if HAS_FLIGHT_DEBUG
469         ao_cmd_register(&ao_flight_cmds[0]);
470 #endif
471         ao_add_task(&flight_task, ao_flight, "flight");
472 }