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