altos: Add ability to set arbitrary radio frequency
[fw/altos] / src / ao_config.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 #include "ao.h"
19
20 __xdata struct ao_config ao_config;
21 __pdata uint8_t ao_config_loaded;
22 __pdata uint8_t ao_config_dirty;
23 __xdata uint8_t ao_config_mutex;
24
25 #define AO_CONFIG_DEFAULT_MAIN_DEPLOY   250
26 #define AO_CONFIG_DEFAULT_RADIO_CHANNEL 0
27 #define AO_CONFIG_DEFAULT_CALLSIGN      "N0CALL"
28 #define AO_CONFIG_DEFAULT_ACCEL_ZERO_G  16000
29 #define AO_CONFIG_DEFAULT_APOGEE_DELAY  0
30 #define AO_CONFIG_DEFAULT_IGNITE_MODE   AO_IGNITE_MODE_DUAL
31 #define AO_CONFIG_DEFAULT_PAD_ORIENTATION       AO_PAD_ORIENTATION_ANTENNA_UP
32 #if HAS_EEPROM
33 #ifndef USE_INTERNAL_FLASH
34 #error Please define USE_INTERNAL_FLASH
35 #endif
36 #endif
37 #if USE_INTERNAL_FLASH
38 #define AO_CONFIG_DEFAULT_FLIGHT_LOG_MAX        ao_storage_config
39 #else
40 #define AO_CONFIG_DEFAULT_FLIGHT_LOG_MAX        ((uint32_t) 192 * (uint32_t) 1024)
41 #endif
42
43 #if HAS_EEPROM
44 static void
45 _ao_config_put(void)
46 {
47         ao_storage_setup();
48         ao_storage_erase(ao_storage_config);
49         ao_storage_write(ao_storage_config, &ao_config, sizeof (ao_config));
50         ao_log_write_erase(0);
51         ao_storage_flush();
52 }
53
54 void
55 ao_config_put(void)
56 {
57         ao_mutex_get(&ao_config_mutex);
58         _ao_config_put();
59         ao_mutex_put(&ao_config_mutex);
60 }
61 #endif
62
63 static void
64 _ao_config_get(void)
65 {
66         if (ao_config_loaded)
67                 return;
68 #if HAS_EEPROM
69         ao_storage_setup();
70         ao_storage_read(ao_storage_config, &ao_config, sizeof (ao_config));
71 #endif
72         if (ao_config.major != AO_CONFIG_MAJOR) {
73                 ao_config.major = AO_CONFIG_MAJOR;
74                 ao_config.minor = AO_CONFIG_MINOR;
75                 ao_config.main_deploy = AO_CONFIG_DEFAULT_MAIN_DEPLOY;
76                 ao_config.radio_channel = AO_CONFIG_DEFAULT_RADIO_CHANNEL;
77                 ao_config.accel_plus_g = 0;
78                 ao_config.accel_minus_g = 0;
79                 memset(&ao_config.callsign, '\0', sizeof (ao_config.callsign));
80                 memcpy(&ao_config.callsign, AO_CONFIG_DEFAULT_CALLSIGN,
81                        sizeof(AO_CONFIG_DEFAULT_CALLSIGN) - 1);
82                 ao_config.apogee_delay = AO_CONFIG_DEFAULT_APOGEE_DELAY;
83                 ao_config.radio_cal = ao_radio_cal;
84                 ao_config.flight_log_max = AO_CONFIG_DEFAULT_FLIGHT_LOG_MAX;
85                 ao_config.ignite_mode = AO_CONFIG_DEFAULT_IGNITE_MODE;
86                 ao_config.pad_orientation = AO_CONFIG_DEFAULT_PAD_ORIENTATION;
87                 ao_config.radio_setting = ao_radio_cal;
88                 ao_config_dirty = 1;
89         }
90         if (ao_config.minor < AO_CONFIG_MINOR) {
91                 /* Fixups for minor version 1 */
92                 if (ao_config.minor < 1)
93                         ao_config.apogee_delay = AO_CONFIG_DEFAULT_APOGEE_DELAY;
94                 /* Fixups for minor version 2 */
95                 if (ao_config.minor < 2) {
96                         ao_config.accel_plus_g = 0;
97                         ao_config.accel_minus_g = 0;
98                 }
99                 /* Fixups for minor version 3 */
100                 if (ao_config.minor < 3)
101                         ao_config.radio_cal = ao_radio_cal;
102                 /* Fixups for minor version 4 */
103                 if (ao_config.minor < 4)
104                         ao_config.flight_log_max = AO_CONFIG_DEFAULT_FLIGHT_LOG_MAX;
105                 /* Fixupes for minor version 5 */
106                 if (ao_config.minor < 5)
107                         ao_config.ignite_mode = AO_CONFIG_DEFAULT_IGNITE_MODE;
108                 if (ao_config.minor < 6)
109                         ao_config.pad_orientation = AO_CONFIG_DEFAULT_PAD_ORIENTATION;
110                 if (ao_config.minor < 7)
111                         ao_config.radio_setting = ao_config.radio_cal;
112                 ao_config.minor = AO_CONFIG_MINOR;
113                 ao_config_dirty = 1;
114         }
115         ao_config_loaded = 1;
116 }
117
118 void
119 ao_config_get(void)
120 {
121         ao_mutex_get(&ao_config_mutex);
122         _ao_config_get();
123         ao_mutex_put(&ao_config_mutex);
124 }
125
126 void
127 ao_config_callsign_show(void)
128 {
129         printf ("Callsign: \"%s\"\n", ao_config.callsign);
130 }
131
132 void
133 ao_config_callsign_set(void) __reentrant
134 {
135         uint8_t c;
136         static __xdata char callsign[AO_MAX_CALLSIGN + 1];
137
138         memset(callsign, '\0', sizeof callsign);
139         ao_cmd_white();
140         c = 0;
141         while (ao_cmd_lex_c != '\n') {
142                 if (c < AO_MAX_CALLSIGN)
143                         callsign[c++] = ao_cmd_lex_c;
144                 else
145                         ao_cmd_status = ao_cmd_lex_error;
146                 ao_cmd_lex();
147         }
148         if (ao_cmd_status != ao_cmd_success)
149                 return;
150         ao_mutex_get(&ao_config_mutex);
151         _ao_config_get();
152         memcpy(&ao_config.callsign, &callsign,
153                AO_MAX_CALLSIGN + 1);
154         ao_config_dirty = 1;
155         ao_mutex_put(&ao_config_mutex);
156         ao_config_callsign_show();
157 }
158
159 void
160 ao_config_radio_channel_show(void) __reentrant
161 {
162         uint32_t        freq = 434550L + ao_config.radio_channel * 100L;
163         uint16_t        mhz = freq / 1000L;
164         uint16_t        khz = freq % 1000L;
165
166         printf("Radio channel: %d (%d.%03dMHz)\n",
167                ao_config.radio_channel, mhz, khz);
168 }
169
170 void
171 ao_config_radio_channel_set(void) __reentrant
172 {
173         ao_cmd_decimal();
174         if (ao_cmd_status != ao_cmd_success)
175                 return;
176         ao_mutex_get(&ao_config_mutex);
177         _ao_config_get();
178         ao_config.radio_channel = ao_cmd_lex_i;
179         ao_config_dirty = 1;
180         ao_mutex_put(&ao_config_mutex);
181         ao_config_radio_channel_show();
182         ao_radio_recv_abort();
183 }
184
185 #if HAS_ADC
186
187 void
188 ao_config_main_deploy_show(void) __reentrant
189 {
190         printf("Main deploy: %d meters (%d feet)\n",
191                ao_config.main_deploy,
192                (int16_t) ((int32_t) ao_config.main_deploy * 328 / 100));
193 }
194
195 void
196 ao_config_main_deploy_set(void) __reentrant
197 {
198         ao_cmd_decimal();
199         if (ao_cmd_status != ao_cmd_success)
200                 return;
201         ao_mutex_get(&ao_config_mutex);
202         _ao_config_get();
203         ao_config.main_deploy = ao_cmd_lex_i;
204         ao_config_dirty = 1;
205         ao_mutex_put(&ao_config_mutex);
206         ao_config_main_deploy_show();
207 }
208
209 #if HAS_ACCEL
210 void
211 ao_config_accel_calibrate_show(void) __reentrant
212 {
213         printf("Accel cal +1g: %d -1g: %d\n",
214                ao_config.accel_plus_g, ao_config.accel_minus_g);
215 }
216
217 #define ACCEL_CALIBRATE_SAMPLES 1024
218 #define ACCEL_CALIBRATE_SHIFT   10
219
220 static int16_t
221 ao_config_accel_calibrate_auto(char *orientation) __reentrant
222 {
223         uint16_t        i;
224         int32_t         accel_total;
225         uint8_t         cal_adc_ring;
226
227         printf("Orient antenna %s and press a key...", orientation);
228         flush();
229         (void) getchar();
230         puts("\r\n"); flush();
231         puts("Calibrating..."); flush();
232         i = ACCEL_CALIBRATE_SAMPLES;
233         accel_total = 0;
234         cal_adc_ring = ao_sample_adc;
235         while (i) {
236                 ao_sleep(DATA_TO_XDATA(&ao_sample_adc));
237                 while (i && cal_adc_ring != ao_sample_adc) {
238                         accel_total += (int32_t) ao_adc_ring[cal_adc_ring].accel;
239                         cal_adc_ring = ao_adc_ring_next(cal_adc_ring);
240                         i--;
241                 }
242         }
243         return accel_total >> ACCEL_CALIBRATE_SHIFT;
244 }
245
246 void
247 ao_config_accel_calibrate_set(void) __reentrant
248 {
249         int16_t up, down;
250         ao_cmd_decimal();
251         if (ao_cmd_status != ao_cmd_success)
252                 return;
253         if (ao_cmd_lex_i == 0) {
254                 up = ao_config_accel_calibrate_auto("up");
255                 down = ao_config_accel_calibrate_auto("down");
256         } else {
257                 up = ao_cmd_lex_i;
258                 ao_cmd_decimal();
259                 if (ao_cmd_status != ao_cmd_success)
260                         return;
261                 down = ao_cmd_lex_i;
262         }
263         if (up >= down) {
264                 printf("Invalid accel: up (%d) down (%d)\n",
265                        up, down);
266                 return;
267         }
268         ao_mutex_get(&ao_config_mutex);
269         _ao_config_get();
270         ao_config.accel_plus_g = up;
271         ao_config.accel_minus_g = down;
272         ao_config_dirty = 1;
273         ao_mutex_put(&ao_config_mutex);
274         ao_config_accel_calibrate_show();
275 }
276 #endif /* HAS_ACCEL */
277
278 void
279 ao_config_apogee_delay_show(void) __reentrant
280 {
281         printf("Apogee delay: %d seconds\n",
282                ao_config.apogee_delay);
283 }
284
285 void
286 ao_config_apogee_delay_set(void) __reentrant
287 {
288         ao_cmd_decimal();
289         if (ao_cmd_status != ao_cmd_success)
290                 return;
291         ao_mutex_get(&ao_config_mutex);
292         _ao_config_get();
293         ao_config.apogee_delay = ao_cmd_lex_i;
294         ao_config_dirty = 1;
295         ao_mutex_put(&ao_config_mutex);
296         ao_config_apogee_delay_show();
297 }
298
299 #endif /* HAS_ADC */
300
301 void
302 ao_config_radio_cal_show(void) __reentrant
303 {
304         printf("Radio cal: %ld\n", ao_config.radio_cal);
305 }
306
307 void
308 ao_config_radio_cal_set(void) __reentrant
309 {
310         ao_cmd_decimal();
311         if (ao_cmd_status != ao_cmd_success)
312                 return;
313         ao_mutex_get(&ao_config_mutex);
314         _ao_config_get();
315         ao_config.radio_setting = ao_config.radio_cal = ao_cmd_lex_u32;
316         ao_config_dirty = 1;
317         ao_mutex_put(&ao_config_mutex);
318         ao_config_radio_cal_show();
319 }
320
321 #if HAS_EEPROM
322 void
323 ao_config_log_show(void) __reentrant
324 {
325         printf("Max flight log: %d kB\n", (int16_t) (ao_config.flight_log_max >> 10));
326 }
327
328 void
329 ao_config_log_set(void) __reentrant
330 {
331         uint16_t        block = (uint16_t) (ao_storage_block >> 10);
332         uint16_t        config = (uint16_t) (ao_storage_config >> 10);
333
334         ao_cmd_decimal();
335         if (ao_cmd_status != ao_cmd_success)
336                 return;
337         if (ao_log_present())
338                 printf("Storage must be empty before changing log size\n");
339         else if (block > 1024 && (ao_cmd_lex_i & (block - 1)))
340                 printf("Flight log size must be multiple of %d kB\n", block);
341         else if (ao_cmd_lex_i > config)
342                 printf("Flight log max %d kB\n", config);
343         else {
344                 ao_mutex_get(&ao_config_mutex);
345                 _ao_config_get();
346                 ao_config.flight_log_max = (uint32_t) ao_cmd_lex_i << 10;
347                 ao_config_dirty = 1;
348                 ao_mutex_put(&ao_config_mutex);
349                 ao_config_log_show();
350         }
351 }
352 #endif /* HAS_EEPROM */
353
354 #if HAS_IGNITE
355 void
356 ao_config_ignite_mode_show(void) __reentrant
357 {
358         printf("Ignite mode: %d\n", ao_config.ignite_mode);
359 }
360
361 void
362 ao_config_ignite_mode_set(void) __reentrant
363 {
364         ao_cmd_decimal();
365         if (ao_cmd_status != ao_cmd_success)
366                 return;
367         ao_mutex_get(&ao_config_mutex);
368         _ao_config_get();
369         ao_config.ignite_mode = ao_cmd_lex_i;
370         ao_config_dirty = 1;
371         ao_mutex_put(&ao_config_mutex);
372         ao_config_ignite_mode_show();
373 }
374 #endif
375
376 #if HAS_ACCEL
377 void
378 ao_config_pad_orientation_show(void) __reentrant
379 {
380         printf("Pad orientation: %d\n", ao_config.pad_orientation);
381 }
382
383 void
384 ao_config_pad_orientation_set(void) __reentrant
385 {
386         ao_cmd_decimal();
387         if (ao_cmd_status != ao_cmd_success)
388                 return;
389         ao_mutex_get(&ao_config_mutex);
390         _ao_config_get();
391         ao_cmd_lex_i &= 1;
392         if (ao_config.pad_orientation != ao_cmd_lex_i) {
393                 uint16_t t;
394                 t = ao_config.accel_plus_g;
395                 ao_config.accel_plus_g = 0x7fff - ao_config.accel_minus_g;
396                 ao_config.accel_minus_g = 0x7fff - t;
397         }
398         ao_config.pad_orientation = ao_cmd_lex_i;
399         ao_config_dirty = 1;
400         ao_mutex_put(&ao_config_mutex);
401         ao_config_pad_orientation_show();
402 }
403 #endif
404
405 void
406 ao_config_radio_setting_show(void) __reentrant
407 {
408         printf("Radio setting: %ld\n", ao_config.radio_setting);
409 }
410
411 void
412 ao_config_radio_setting_set(void) __reentrant
413 {
414         ao_cmd_decimal();
415         if (ao_cmd_status != ao_cmd_success)
416                 return;
417         ao_mutex_get(&ao_config_mutex);
418         _ao_config_get();
419         ao_config.radio_setting = ao_cmd_lex_u32;
420         ao_config_dirty = 1;
421         ao_mutex_put(&ao_config_mutex);
422         ao_config_radio_setting_show();
423         ao_radio_recv_abort();
424 }
425
426 struct ao_config_var {
427         __code char     *str;
428         void            (*set)(void) __reentrant;
429         void            (*show)(void) __reentrant;
430 };
431
432 static void
433 ao_config_help(void) __reentrant;
434
435 static void
436 ao_config_show(void) __reentrant;
437
438 static void
439 ao_config_write(void) __reentrant;
440
441 __code struct ao_config_var ao_config_vars[] = {
442 #if HAS_ADC
443         { "m <meters>\0Main deploy (in meters)",
444           ao_config_main_deploy_set,    ao_config_main_deploy_show, },
445         { "d <delay>\0Apogee delay (in seconds)",
446           ao_config_apogee_delay_set,   ao_config_apogee_delay_show },
447 #endif /* HAS_ADC */
448         { "r <channel>\0Radio channel (freq = 434.550 + chan * .1)",
449           ao_config_radio_channel_set,  ao_config_radio_channel_show },
450         { "c <call>\0Callsign (8 char max)",
451           ao_config_callsign_set,       ao_config_callsign_show },
452 #if HAS_ACCEL
453         { "a <+g> <-g>\0Accel calib (0 for auto)",
454           ao_config_accel_calibrate_set,ao_config_accel_calibrate_show },
455 #endif /* HAS_ACCEL */
456         { "f <cal>\0Radio calib (cal = rf/(xtal/2^16))",
457           ao_config_radio_cal_set,      ao_config_radio_cal_show },
458 #if HAS_EEPROM
459         { "l <size>\0Flight log size in kB",
460           ao_config_log_set,            ao_config_log_show },
461 #endif
462 #if HAS_IGNITE
463         { "i <0 dual, 1 apogee, 2 main>\0Set igniter mode",
464           ao_config_ignite_mode_set,    ao_config_ignite_mode_show },
465 #endif
466 #if HAS_ACCEL
467         { "o <0 antenna up, 1 antenna down>\0Set pad orientation",
468           ao_config_pad_orientation_set,ao_config_pad_orientation_show },
469 #endif
470         { "R <setting>\0Radio freq control (freq = 434.550 * setting/cal)",
471           ao_config_radio_setting_set,  ao_config_radio_setting_show },
472         { "s\0Show",
473           ao_config_show,               ao_config_show },
474 #if HAS_EEPROM
475         { "w\0Write to eeprom",
476           ao_config_write,              ao_config_write },
477 #endif
478         { "?\0Help",
479           ao_config_help,               ao_config_help },
480         { 0, 0, 0 }
481 };
482
483 void
484 ao_config_set(void)
485 {
486         char    c;
487         uint8_t cmd;
488         void (*__xdata func)(void) __reentrant;
489
490         ao_cmd_white();
491         c = ao_cmd_lex_c;
492         ao_cmd_lex();
493         func = 0;
494         for (cmd = 0; ao_config_vars[cmd].str != NULL; cmd++)
495                 if (ao_config_vars[cmd].str[0] == c) {
496                         func = ao_config_vars[cmd].set;
497                         break;
498                 }
499         if (func)
500                 (*func)();
501         else
502                 ao_cmd_status = ao_cmd_syntax_error;
503 }
504
505 static void
506 ao_config_help(void) __reentrant
507 {
508         uint8_t cmd;
509         for (cmd = 0; ao_config_vars[cmd].str != NULL; cmd++)
510                 printf("%-20s %s\n",
511                        ao_config_vars[cmd].str,
512                        ao_config_vars[cmd].str+1+strlen(ao_config_vars[cmd].str));
513 }
514
515 static void
516 ao_config_show(void) __reentrant
517 {
518         uint8_t cmd;
519         printf("Config version: %d.%d\n",
520                ao_config.major, ao_config.minor);
521         for (cmd = 0; ao_config_vars[cmd].str != NULL; cmd++)
522                 if (ao_config_vars[cmd].show != ao_config_vars[cmd].set)
523                         (*ao_config_vars[cmd].show)();
524 }
525
526 #if HAS_EEPROM
527 static void
528 ao_config_write(void) __reentrant
529 {
530         uint8_t saved = 0;
531         ao_mutex_get(&ao_config_mutex);
532         if (ao_config_dirty) {
533                 _ao_config_put();
534                 ao_config_dirty = 0;
535                 saved = 1;
536         }
537         ao_mutex_put(&ao_config_mutex);
538         if (saved)
539                 puts("Saved");
540         else
541                 puts("Nothing to save");
542 }
543 #endif
544
545 __code struct ao_cmds ao_config_cmds[] = {
546         { ao_config_set,        "c <var> <value>\0Set config variable (? for help, s to show)" },
547         { 0, NULL },
548 };
549
550 void
551 ao_config_init(void)
552 {
553         ao_cmd_register(&ao_config_cmds[0]);
554 }