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