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