summaryrefslogtreecommitdiff
path: root/ao_flight.c
blob: 5998f291c42881e7c361af105b03659c0be8deb9 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
/*
 * Copyright © 2009 Keith Packard <keithp@keithp.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; version 2 of the License.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
 */

#ifndef AO_FLIGHT_TEST
#include "ao.h"
#endif

/* Main flight thread. */

__pdata enum ao_flight_state	ao_flight_state;	/* current flight state */
__pdata uint16_t		ao_flight_tick;		/* time of last data */
__pdata int16_t			ao_flight_accel;	/* filtered acceleration */
__pdata int16_t			ao_flight_pres;		/* filtered pressure */
__pdata int16_t			ao_ground_pres;		/* startup pressure */
__pdata int16_t			ao_ground_accel;	/* startup acceleration */
__pdata int16_t			ao_min_pres;		/* minimum recorded pressure */
__pdata uint16_t		ao_launch_time;		/* time of launch detect */
__pdata int16_t			ao_main_pres;		/* pressure to eject main */

/*
 * track min/max data over a long interval to detect
 * resting
 */
__pdata uint16_t		ao_interval_end;
__pdata int16_t			ao_interval_cur_min_accel;
__pdata int16_t			ao_interval_cur_max_accel;
__pdata int16_t			ao_interval_cur_min_pres;
__pdata int16_t			ao_interval_cur_max_pres;
__pdata int16_t			ao_interval_min_accel;
__pdata int16_t			ao_interval_max_accel;
__pdata int16_t			ao_interval_min_pres;
__pdata int16_t			ao_interval_max_pres;

__data uint8_t ao_flight_adc;
__xdata int16_t ao_accel, ao_prev_accel, ao_pres;

#define AO_INTERVAL_TICKS	AO_SEC_TO_TICKS(5)

/* Accelerometer calibration
 *
 * We're sampling the accelerometer through a resistor divider which
 * consists of 5k and 10k resistors. This multiplies the values by 2/3.
 * That goes into the cc1111 A/D converter, which is running at 11 bits
 * of precision with the bits in the MSB of the 16 bit value. Only positive
 * values are used, so values should range from 0-32752 for 0-3.3V. The
 * specs say we should see 40mV/g (uncalibrated), multiply by 2/3 for what
 * the A/D converter sees (26.67 mV/g). We should see 32752/3300 counts/mV,
 * for a final computation of:
 *
 * 26.67 mV/g * 32767/3300 counts/mV = 264.8 counts/g
 *
 * Zero g was measured at 16000 (we would expect 16384)
 */

#define ACCEL_G		265
#define ACCEL_ZERO_G	16000
#define ACCEL_NOSE_UP	(ACCEL_ZERO_G - ACCEL_G * 2 /3)
#define ACCEL_BOOST	(ACCEL_NOSE_UP - ACCEL_G * 2)
#define ACCEL_LAND	(ACCEL_G / 10)

/*
 * Barometer calibration
 *
 * We directly sample the barometer. The specs say:
 *
 * Pressure range: 15-115 kPa
 * Voltage at 115kPa: 2.82
 * Output scale: 27mV/kPa
 * 
 * If we want to detect launch with the barometer, we need
 * a large enough bump to not be fooled by noise. At typical
 * launch elevations (0-2000m), a 200Pa pressure change cooresponds
 * to about a 20m elevation change. This is 5.4mV, or about 3LSB.
 * As all of our calculations are done in 16 bits, we'll actually see a change
 * of 16 times this though
 *
 * 27 mV/kPa * 32767 / 3300 counts/mV = 268.1 counts/kPa
 */

#define BARO_kPa	268
#define BARO_LAUNCH	(BARO_kPa / 5)	/* .2kPa */
#define BARO_APOGEE	(BARO_kPa / 10)	/* .1kPa */
#define BARO_MAIN	(BARO_kPa)	/* 1kPa */
#define BARO_LAND	(BARO_kPa / 20)	/* .05kPa */

/* We also have a clock, which can be used to sanity check things in
 * case of other failures
 */

#define BOOST_TICKS_MAX	AO_SEC_TO_TICKS(15)

/* This value is scaled in a weird way. It's a running total of accelerometer
 * readings minus the ground accelerometer reading. That means it measures
 * velocity, and quite accurately too. As it gets updated 100 times a second,
 * it's scaled by 100
 */
__data int32_t	ao_flight_vel;

/* convert m/s to velocity count */
#define VEL_MPS_TO_COUNT(mps) ((int32_t) ((int32_t) (mps) * (int32_t) 100 / (int32_t) ACCEL_G))

void
ao_flight(void)
{
	__pdata static uint8_t	nsamples = 0;
	
	ao_flight_adc = ao_adc_head;
	ao_prev_accel = 0;
	ao_accel = 0;
	ao_pres = 0;
	for (;;) {
		ao_sleep(&ao_adc_ring);
		while (ao_flight_adc != ao_adc_head) {
			ao_accel = ao_adc_ring[ao_flight_adc].accel;
			ao_pres = ao_adc_ring[ao_flight_adc].pres;
			ao_flight_tick = ao_adc_ring[ao_flight_adc].tick;
			ao_flight_vel += (int32_t) (((ao_accel + ao_prev_accel) >> 4) - (ao_ground_accel << 1));
			ao_prev_accel = ao_accel;
			ao_flight_adc = ao_adc_ring_next(ao_flight_adc);
		}
		ao_flight_accel -= ao_flight_accel >> 4;
		ao_flight_accel += ao_accel >> 4;
		ao_flight_pres -= ao_flight_pres >> 4;
		ao_flight_pres += ao_pres >> 4;
		
		if (ao_flight_pres < ao_min_pres)
			ao_min_pres = ao_flight_pres;

		if ((int16_t) (ao_flight_tick - ao_interval_end) >= 0) {
			ao_interval_max_pres = ao_interval_cur_max_pres;
			ao_interval_min_pres = ao_interval_cur_min_pres;
			ao_interval_max_accel = ao_interval_cur_max_accel;
			ao_interval_min_accel = ao_interval_cur_min_accel;
			ao_interval_end = ao_flight_tick + AO_INTERVAL_TICKS;
		}
			       
		switch (ao_flight_state) {
		case ao_flight_startup:
			if (nsamples < 100) {
				++nsamples;
				continue;
			}
			ao_ground_accel = ao_flight_accel;
			ao_ground_pres = ao_flight_pres;
			ao_min_pres = ao_flight_pres;
			ao_main_pres = ao_ground_pres - BARO_MAIN;
			ao_flight_vel = 0;
			
			ao_interval_end = ao_flight_tick;
			
			/* Go to launchpad state if the nose is pointing up */
			if (ao_flight_accel < ACCEL_NOSE_UP) {
				ao_flight_state = ao_flight_launchpad;
				ao_wakeup(DATA_TO_XDATA(&ao_flight_state));
			} else {
				ao_flight_state = ao_flight_idle;
				
				/* Turn on the Green LED in idle mode
				 * This also happens to bring the USB up for the TI board
				 */
				ao_led_on(AO_LED_GREEN);
				ao_timer_set_adc_interval(100);
				ao_wakeup(DATA_TO_XDATA(&ao_flight_state));
			}
			/* signal successful initialization by turning off the LED */
			ao_led_off(AO_LED_RED);
			break;
		case ao_flight_launchpad:

			/* pad to boost:
			 *
			 * accelerometer: > 2g
			 * barometer: > 20m vertical motion
			 */
			if (ao_flight_accel < ACCEL_BOOST || 
			    ao_flight_pres + BARO_LAUNCH < ao_ground_pres)
			{
				ao_flight_state = ao_flight_boost;
				ao_log_start();
				ao_wakeup(DATA_TO_XDATA(&ao_flight_state));
				break;
			}
			break;
		case ao_flight_boost:

			/* boost to coast:
			 *
			 * accelerometer: start to fall at > 1/4 G
			 * time: boost for more than 15 seconds
			 */
			if (ao_flight_accel > ao_ground_accel + (ACCEL_G >> 2) ||
			    (int16_t) (ao_flight_tick - ao_launch_time) > BOOST_TICKS_MAX)
			{
				ao_flight_state = ao_flight_coast;
				ao_wakeup(DATA_TO_XDATA(&ao_flight_state));
				break;
			}
			break;
		case ao_flight_coast:
			
			/* coast to apogee detect:
			 * 
			 * accelerometer: integrated velocity < 200 m/s
			 * barometer: fall at least 500m from max altitude
			 */
			if (ao_flight_vel < VEL_MPS_TO_COUNT(200) ||
			    ao_flight_pres - (5 * BARO_kPa) > ao_min_pres)
			{
				ao_flight_state = ao_flight_apogee;
				ao_wakeup(DATA_TO_XDATA(&ao_flight_state));
			}
			break;
		case ao_flight_apogee:

			/* apogee to drogue deploy:
			 *
			 * accelerometer: integrated velocity < 10m/s
			 * barometer: fall at least 10m
			 */
			if (ao_flight_vel < VEL_MPS_TO_COUNT(-10) ||
			    ao_flight_pres - BARO_APOGEE > ao_min_pres)
			{
				ao_ignite(ao_igniter_drogue);
				ao_flight_state = ao_flight_drogue;
				ao_wakeup(DATA_TO_XDATA(&ao_flight_state));
			}
			break; 
		case ao_flight_drogue:
			
			/* drogue to main deploy:
			 *
			 * accelerometer: abs(velocity) > 50m/s
			 * barometer: reach main deploy altitude
			 */
			if (ao_flight_vel < VEL_MPS_TO_COUNT(-50) ||
			    ao_flight_vel > VEL_MPS_TO_COUNT(50) ||
			    ao_flight_pres >= ao_main_pres)
			{
				ao_ignite(ao_igniter_main);
				ao_flight_state = ao_flight_main;
				ao_wakeup(DATA_TO_XDATA(&ao_flight_state));
			}
			/* fall through... */
		case ao_flight_main:

			/* drogue/main to land:
			 *
			 * accelerometer: value stable
			 * barometer: altitude stable
			 */
			if ((ao_interval_max_accel - ao_interval_min_accel) < ACCEL_LAND ||
			     (ao_interval_max_pres - ao_interval_min_pres) < BARO_LAND)
			{
				ao_flight_state = ao_flight_landed;
				ao_wakeup(DATA_TO_XDATA(&ao_flight_state));
			}
			break;
		case ao_flight_landed:
			ao_log_stop();
			break;
		}
	}
}

static __xdata struct ao_task	flight_task;

void
ao_flight_init(void)
{
	ao_flight_state = ao_flight_startup;
	ao_interval_min_accel = 0;
	ao_interval_max_accel = 0x7fff;
	ao_interval_min_pres = 0;
	ao_interval_max_pres = 0x7fff;
	ao_interval_end = AO_INTERVAL_TICKS;

	ao_add_task(&flight_task, ao_flight, "flight");
}