Switch from GPLv2 to GPLv2+
[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
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) && ao_height > AO_M_TO_HEIGHT(100)) ||
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 ((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                         if (ao_speed < 0
286 #if !HAS_ACCEL
287                             && (ao_sample_alt >= AO_MAX_BARO_HEIGHT || ao_error_h_sq_avg < 100)
288 #endif
289                                 )
290                         {
291 #if HAS_IGNITE
292                                 /* ignite the drogue charge */
293                                 ao_ignite(ao_igniter_drogue);
294 #endif
295
296 #if HAS_TELEMETRY
297                                 /* slow down the telemetry system */
298                                 ao_telemetry_set_interval(AO_TELEMETRY_INTERVAL_RECOVER);
299
300                                 /* Turn the RDF beacon back on */
301                                 ao_rdf_set(1);
302 #endif
303
304                                 /* and enter drogue state */
305                                 ao_flight_state = ao_flight_drogue;
306                                 ao_wakeup(DATA_TO_XDATA(&ao_flight_state));
307                         }
308 #if HAS_ACCEL
309                         else {
310                         check_re_boost:
311                                 ao_coast_avg_accel = ao_coast_avg_accel - (ao_coast_avg_accel >> 6) + (ao_accel >> 6);
312                                 if (ao_coast_avg_accel > AO_MSS_TO_ACCEL(20)) {
313                                         ao_boost_tick = ao_sample_tick;
314                                         ao_flight_state = ao_flight_boost;
315                                         ao_wakeup(DATA_TO_XDATA(&ao_flight_state));
316                                 }
317                         }
318 #endif
319
320                         break;
321                 case ao_flight_drogue:
322
323                         /* drogue to main deploy:
324                          *
325                          * barometer: reach main deploy altitude
326                          *
327                          * Would like to use the accelerometer for this test, but
328                          * the orientation of the flight computer is unknown after
329                          * drogue deploy, so we ignore it. Could also detect
330                          * high descent rate using the pressure sensor to
331                          * recognize drogue deploy failure and eject the main
332                          * at that point. Perhaps also use the drogue sense lines
333                          * to notice continutity?
334                          */
335                         if (ao_height <= ao_config.main_deploy)
336                         {
337 #if HAS_IGNITE
338                                 ao_ignite(ao_igniter_main);
339 #endif
340
341                                 /*
342                                  * Start recording min/max height
343                                  * to figure out when the rocket has landed
344                                  */
345
346                                 /* initialize interval values */
347                                 ao_interval_end = ao_sample_tick + AO_INTERVAL_TICKS;
348
349                                 ao_interval_min_height = ao_interval_max_height = ao_avg_height;
350
351                                 ao_flight_state = ao_flight_main;
352                                 ao_wakeup(DATA_TO_XDATA(&ao_flight_state));
353                         }
354                         break;
355
356                         /* fall through... */
357                 case ao_flight_main:
358
359                         /* main to land:
360                          *
361                          * barometer: altitude stable
362                          */
363
364                         if (ao_avg_height < ao_interval_min_height)
365                                 ao_interval_min_height = ao_avg_height;
366                         if (ao_avg_height > ao_interval_max_height)
367                                 ao_interval_max_height = ao_avg_height;
368
369                         if ((int16_t) (ao_sample_tick - ao_interval_end) >= 0) {
370                                 if (ao_interval_max_height - ao_interval_min_height <= AO_M_TO_HEIGHT(4))
371                                 {
372                                         ao_flight_state = ao_flight_landed;
373
374 #if HAS_ADC
375                                         /* turn off the ADC capture */
376                                         ao_timer_set_adc_interval(0);
377 #endif
378
379                                         ao_wakeup(DATA_TO_XDATA(&ao_flight_state));
380                                 }
381                                 ao_interval_min_height = ao_interval_max_height = ao_avg_height;
382                                 ao_interval_end = ao_sample_tick + AO_INTERVAL_TICKS;
383                         }
384                         break;
385 #if HAS_FLIGHT_DEBUG
386                 case ao_flight_test:
387 #if HAS_GYRO
388                         printf ("angle %4d pitch %7d yaw %7d roll %7d\n",
389                                 ao_sample_orient,
390                                 ((ao_sample_pitch << 9) - ao_ground_pitch) >> 9,
391                                 ((ao_sample_yaw << 9) - ao_ground_yaw) >> 9,
392                                 ((ao_sample_roll << 9) - ao_ground_roll) >> 9);
393 #endif
394                         flush();
395                         break;
396 #endif /* HAS_FLIGHT_DEBUG */
397                 default:
398                         break;
399                 }
400         }
401 }
402
403 #if HAS_FLIGHT_DEBUG
404 static inline int int_part(ao_v_t i)    { return i >> 4; }
405 static inline int frac_part(ao_v_t i)   { return ((i & 0xf) * 100 + 8) / 16; }
406
407 static void
408 ao_flight_dump(void)
409 {
410 #if HAS_ACCEL
411         ao_v_t  accel;
412
413         accel = ((ao_config.accel_plus_g - ao_sample_accel) * ao_accel_scale) >> 16;
414 #endif
415
416         printf ("sample:\n");
417         printf ("  tick        %d\n", ao_sample_tick);
418         printf ("  raw pres    %d\n", ao_sample_pres);
419 #if HAS_ACCEL
420         printf ("  raw accel   %d\n", ao_sample_accel);
421 #endif
422         printf ("  ground pres %d\n", ao_ground_pres);
423         printf ("  ground alt  %d\n", ao_ground_height);
424 #if HAS_ACCEL
425         printf ("  raw accel   %d\n", ao_sample_accel);
426         printf ("  groundaccel %d\n", ao_ground_accel);
427         printf ("  accel_2g    %d\n", ao_accel_2g);
428 #endif
429
430         printf ("  alt         %d\n", ao_sample_alt);
431         printf ("  height      %d\n", ao_sample_height);
432 #if HAS_ACCEL
433         printf ("  accel       %d.%02d\n", int_part(accel), frac_part(accel));
434 #endif
435
436
437         printf ("kalman:\n");
438         printf ("  height      %d\n", ao_height);
439         printf ("  speed       %d.%02d\n", int_part(ao_speed), frac_part(ao_speed));
440         printf ("  accel       %d.%02d\n", int_part(ao_accel), frac_part(ao_accel));
441         printf ("  max_height  %d\n", ao_max_height);
442         printf ("  avg_height  %d\n", ao_avg_height);
443         printf ("  error_h     %d\n", ao_error_h);
444         printf ("  error_avg   %d\n", ao_error_h_sq_avg);
445 }
446
447 static void
448 ao_gyro_test(void)
449 {
450         ao_flight_state = ao_flight_test;
451         ao_getchar();
452         ao_flight_state = ao_flight_idle;
453 }
454
455 uint8_t ao_orient_test;
456
457 static void
458 ao_orient_test_select(void)
459 {
460         ao_orient_test = !ao_orient_test;
461 }
462
463 __code struct ao_cmds ao_flight_cmds[] = {
464         { ao_flight_dump,       "F\0Dump flight status" },
465         { ao_gyro_test,         "G\0Test gyro code" },
466         { ao_orient_test_select,"O\0Test orientation code" },
467         { 0, NULL },
468 };
469 #endif
470
471 static __xdata struct ao_task   flight_task;
472
473 void
474 ao_flight_init(void)
475 {
476         ao_flight_state = ao_flight_startup;
477 #if HAS_FLIGHT_DEBUG
478         ao_cmd_register(&ao_flight_cmds[0]);
479 #endif
480         ao_add_task(&flight_task, ao_flight, "flight");
481 }