altos: Add pad support for new telefire versions
[fw/altos] / src / drivers / ao_pad.c
1 /*
2  * Copyright © 2012 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 #include <ao_pad.h>
20 #include <ao_74hc165.h>
21 #include <ao_radio_cmac.h>
22
23 static __xdata uint8_t ao_pad_ignite;
24 static __xdata struct ao_pad_command    command;
25 static __xdata struct ao_pad_query      query;
26 static __pdata uint8_t  ao_pad_armed;
27 static __pdata uint16_t ao_pad_arm_time;
28 static __pdata uint8_t  ao_pad_box;
29 static __xdata uint8_t  ao_pad_disabled;
30 static __pdata uint16_t ao_pad_packet_time;
31
32 #ifndef AO_PAD_RSSI_MINIMUM
33 #define AO_PAD_RSSI_MINIMUM     -90
34 #endif
35
36 #define DEBUG   1
37
38 #if DEBUG
39 static __pdata uint8_t  ao_pad_debug;
40 #define PRINTD(...) (ao_pad_debug ? (printf(__VA_ARGS__), 0) : 0)
41 #define FLUSHD()    (ao_pad_debug ? (flush(), 0) : 0)
42 #else
43 #define PRINTD(...)
44 #define FLUSHD()
45 #endif
46
47 static void
48 ao_siren(uint8_t v)
49 {
50 #ifdef AO_SIREN
51         ao_gpio_set(AO_SIREN_PORT, AO_SIREN_PIN, AO_SIREN, v);
52 #else
53 #if HAS_BEEP
54         ao_beep(v ? AO_BEEP_MID : 0);
55 #else
56         (void) v;
57 #endif
58 #endif
59 }
60
61 static void
62 ao_strobe(uint8_t v)
63 {
64 #ifdef AO_STROBE
65         ao_gpio_set(AO_STROBE_PORT, AO_STROBE_PIN, AO_STROBE, v);
66 #else
67         (void) v;
68 #endif
69 }
70
71 static void
72 ao_pad_run(void)
73 {
74         AO_PORT_TYPE    pins;
75
76         for (;;) {
77                 while (!ao_pad_ignite)
78                         ao_sleep(&ao_pad_ignite);
79                 /*
80                  * Actually set the pad bits
81                  */
82                 pins = 0;
83 #if AO_PAD_NUM > 0
84                 if (ao_pad_ignite & (1 << 0))
85                         pins |= (1 << AO_PAD_PIN_0);
86 #endif
87 #if AO_PAD_NUM > 1
88                 if (ao_pad_ignite & (1 << 1))
89                         pins |= (1 << AO_PAD_PIN_1);
90 #endif
91 #if AO_PAD_NUM > 2
92                 if (ao_pad_ignite & (1 << 2))
93                         pins |= (1 << AO_PAD_PIN_2);
94 #endif
95 #if AO_PAD_NUM > 3
96                 if (ao_pad_ignite & (1 << 3))
97                         pins |= (1 << AO_PAD_PIN_3);
98 #endif
99                 PRINTD("ignite pins 0x%x\n", pins);
100                 ao_gpio_set_bits(AO_PAD_PORT, pins);
101                 while (ao_pad_ignite) {
102                         ao_pad_ignite = 0;
103
104                         ao_delay(AO_PAD_FIRE_TIME);
105                 }
106                 ao_gpio_clr_bits(AO_PAD_PORT, pins);
107                 PRINTD("turn off pins 0x%x\n", pins);
108         }
109 }
110
111 #define AO_PAD_ARM_SIREN_INTERVAL       200
112
113 #ifndef AO_PYRO_R_PYRO_SENSE
114 #define AO_PYRO_R_PYRO_SENSE    100
115 #define AO_PYRO_R_SENSE_GND     27
116 #define AO_FIRE_R_POWER_FET     100
117 #define AO_FIRE_R_FET_SENSE     100
118 #define AO_FIRE_R_SENSE_GND     27
119 #endif
120
121 static void
122 ao_pad_monitor(void)
123 {
124         uint8_t                 c;
125         uint8_t                 sample;
126         __pdata uint8_t         prev = 0, cur = 0;
127         __pdata uint8_t         beeping = 0;
128         __xdata volatile struct ao_data *packet;
129         __pdata uint16_t        arm_beep_time = 0;
130
131         sample = ao_data_head;
132         for (;;) {
133                 __pdata int16_t                 pyro;
134                 ao_arch_critical(
135                         while (sample == ao_data_head)
136                                 ao_sleep((void *) DATA_TO_XDATA(&ao_data_head));
137                         );
138
139
140                 packet = &ao_data_ring[sample];
141                 sample = ao_data_ring_next(sample);
142
143                 pyro = packet->adc.pyro;
144
145 #define VOLTS_TO_PYRO(x) ((int16_t) ((x) * ((1.0 * AO_PYRO_R_SENSE_GND) / \
146                                             (1.0 * (AO_PYRO_R_SENSE_GND + AO_PYRO_R_PYRO_SENSE)) / 3.3 * AO_ADC_MAX)))
147
148
149 #define VOLTS_TO_FIRE(x) ((int16_t) ((x) * ((1.0 * AO_FIRE_R_SENSE_GND) / \
150                                             (1.0 * (AO_FIRE_R_SENSE_GND + AO_FIRE_R_FET_SENSE)) / 3.3 * AO_ADC_MAX)))
151
152                 /* convert ADC value to voltage in tenths, then add .2 for the diode drop */
153                 query.battery = (packet->adc.batt + 96) / 192 + 2;
154                 cur = 0;
155                 if (pyro > VOLTS_TO_PYRO(10)) {
156                         query.arm_status = AO_PAD_ARM_STATUS_ARMED;
157                         cur |= AO_LED_ARMED;
158                 } else if (pyro < VOLTS_TO_PYRO(5)) {
159                         query.arm_status = AO_PAD_ARM_STATUS_DISARMED;
160                         arm_beep_time = 0;
161                 } else {
162                         if ((ao_time() % 100) < 50)
163                                 cur |= AO_LED_ARMED;
164                         query.arm_status = AO_PAD_ARM_STATUS_UNKNOWN;
165                         arm_beep_time = 0;
166                 }
167                 if ((ao_time() - ao_pad_packet_time) > AO_SEC_TO_TICKS(2))
168                         cur |= AO_LED_RED;
169                 else if (ao_radio_cmac_rssi < AO_PAD_RSSI_MINIMUM)
170                         cur |= AO_LED_AMBER;
171                 else
172                         cur |= AO_LED_GREEN;
173
174                 for (c = 0; c < AO_PAD_NUM; c++) {
175                         int16_t         sense = packet->adc.sense[c];
176                         uint8_t status = AO_PAD_IGNITER_STATUS_UNKNOWN;
177
178                         /*
179                          *      pyro is run through a divider, so pyro = v_pyro * 27 / 127 ~= v_pyro / 20
180                          *      v_pyro = pyro * 127 / 27
181                          *
182                          *              v_pyro \
183                          *      100k            igniter
184                          *              output /
185                          *      100k           \
186                          *              sense   relay
187                          *      27k            /
188                          *              gnd ---
189                          *
190                          *              v_pyro \
191                          *      200k            igniter
192                          *              output /
193                          *      200k           \
194                          *              sense   relay
195                          *      22k            /
196                          *              gnd ---
197                          *
198                          *      If the relay is closed, then sense will be 0
199                          *      If no igniter is present, then sense will be v_pyro * 27k/227k = pyro * 127 / 227 ~= pyro/2
200                          *      If igniter is present, then sense will be v_pyro * 27k/127k ~= v_pyro / 20 = pyro
201                          */
202
203 #if AO_FIRE_R_POWER_FET
204                         if (sense <= pyro / 8) {
205                                 status = AO_PAD_IGNITER_STATUS_NO_IGNITER_RELAY_CLOSED;
206                                 if ((ao_time() % 100) < 50)
207                                         cur |= AO_LED_CONTINUITY(c);
208                         } else
209                         if (pyro / 8 * 3 <= sense && sense <= pyro / 8 * 5)
210                                 status = AO_PAD_IGNITER_STATUS_NO_IGNITER_RELAY_OPEN;
211                         else if (pyro / 8 * 7 <= sense) {
212                                 status = AO_PAD_IGNITER_STATUS_GOOD_IGNITER_RELAY_OPEN;
213                                 cur |= AO_LED_CONTINUITY(c);
214                         }
215 #else
216                         if (sense >= pyro / 8 * 5) {
217                                 status = AO_PAD_IGNITER_STATUS_GOOD_IGNITER_RELAY_OPEN;
218                                 cur |= AO_LED_CONTINUITY(c);
219                         } else {
220                                 status = AO_PAD_IGNITER_STATUS_NO_IGNITER_RELAY_OPEN;
221                         }
222 #endif
223                         query.igniter_status[c] = status;
224                 }
225                 if (cur != prev) {
226                         PRINTD("change leds from %02x to %02x\n",
227                                prev, cur);
228                         FLUSHD();
229                         ao_led_set(cur);
230                         prev = cur;
231                 }
232
233                 if (ao_pad_armed && (int16_t) (ao_time() - ao_pad_arm_time) > AO_PAD_ARM_TIME)
234                         ao_pad_armed = 0;
235
236                 if (ao_pad_armed) {
237                         ao_strobe(1);
238                         ao_siren(1);
239                         beeping = 1;
240                 } else if (query.arm_status == AO_PAD_ARM_STATUS_ARMED && !beeping) {
241                         if (arm_beep_time == 0) {
242                                 arm_beep_time = AO_PAD_ARM_SIREN_INTERVAL;
243                                 beeping = 1;
244                                 ao_siren(1);
245                         }
246                         --arm_beep_time;
247                 } else if (beeping) {
248                         beeping = 0;
249                         ao_siren(0);
250                         ao_strobe(0);
251                 }
252         }
253 }
254
255 void
256 ao_pad_disable(void)
257 {
258         if (!ao_pad_disabled) {
259                 ao_pad_disabled = 1;
260                 ao_radio_recv_abort();
261         }
262 }
263
264 void
265 ao_pad_enable(void)
266 {
267         ao_pad_disabled = 0;
268         ao_wakeup (&ao_pad_disabled);
269 }
270
271 #if HAS_74HC165
272 static uint8_t
273 ao_pad_read_box(void)
274 {
275         uint8_t         byte = ao_74hc165_read();
276         uint8_t         h, l;
277
278         h = byte >> 4;
279         l = byte & 0xf;
280         return h * 10 + l;
281 }
282 #else
283 #define ao_pad_read_box()       0
284 #endif
285
286 #ifdef PAD_BOX
287 #define ao_pad_read_box()       PAD_BOX
288 #endif
289
290 static void
291 ao_pad(void)
292 {
293         int16_t time_difference;
294         int8_t  ret;
295
296         ao_pad_box = 0;
297         ao_led_set(0);
298         for (;;) {
299                 FLUSHD();
300                 while (ao_pad_disabled)
301                         ao_sleep(&ao_pad_disabled);
302                 ret = ao_radio_cmac_recv(&command, sizeof (command), 0);
303                 PRINTD ("cmac_recv %d %d\n", ret, ao_radio_cmac_rssi);
304                 if (ret != AO_RADIO_CMAC_OK)
305                         continue;
306                 ao_pad_packet_time = ao_time();
307
308                 ao_pad_box = ao_pad_read_box();
309
310                 PRINTD ("tick %d box %d (me %d) cmd %d channels %02x\n",
311                         command.tick, command.box, ao_pad_box, command.cmd, command.channels);
312
313                 switch (command.cmd) {
314                 case AO_LAUNCH_ARM:
315                         if (command.box != ao_pad_box) {
316                                 PRINTD ("box number mismatch\n");
317                                 break;
318                         }
319
320                         if (command.channels & ~(AO_PAD_ALL_CHANNELS))
321                                 break;
322
323                         time_difference = command.tick - ao_time();
324                         PRINTD ("arm tick %d local tick %d\n", command.tick, ao_time());
325                         if (time_difference < 0)
326                                 time_difference = -time_difference;
327                         if (time_difference > 10) {
328                                 PRINTD ("time difference too large %d\n", time_difference);
329                                 break;
330                         }
331                         PRINTD ("armed\n");
332                         ao_pad_armed = command.channels;
333                         ao_pad_arm_time = ao_time();
334                         break;
335
336                 case AO_LAUNCH_QUERY:
337                         if (command.box != ao_pad_box) {
338                                 PRINTD ("box number mismatch\n");
339                                 break;
340                         }
341
342                         query.tick = ao_time();
343                         query.box = ao_pad_box;
344                         query.channels = AO_PAD_ALL_CHANNELS;
345                         query.armed = ao_pad_armed;
346                         PRINTD ("query tick %d box %d channels %02x arm %d arm_status %d igniter %d,%d,%d,%d\n",
347                                 query.tick, query.box, query.channels, query.armed,
348                                 query.arm_status,
349                                 query.igniter_status[0],
350                                 query.igniter_status[1],
351                                 query.igniter_status[2],
352                                 query.igniter_status[3]);
353                         ao_radio_cmac_send(&query, sizeof (query));
354                         break;
355                 case AO_LAUNCH_FIRE:
356                         if (!ao_pad_armed) {
357                                 PRINTD ("not armed\n");
358                                 break;
359                         }
360                         if ((uint16_t) (ao_time() - ao_pad_arm_time) > AO_SEC_TO_TICKS(20)) {
361                                 PRINTD ("late pad arm_time %d time %d\n",
362                                         ao_pad_arm_time, ao_time());
363                                 break;
364                         }
365                         PRINTD ("ignite\n");
366                         ao_pad_ignite = ao_pad_armed;
367                         ao_pad_arm_time = ao_time();
368                         ao_wakeup(&ao_pad_ignite);
369                         break;
370                 }
371         }
372 }
373
374 void
375 ao_pad_test(void)
376 {
377         uint8_t c;
378
379         printf ("Arm switch: ");
380         switch (query.arm_status) {
381         case AO_PAD_ARM_STATUS_ARMED:
382                 printf ("Armed\n");
383                 break;
384         case AO_PAD_ARM_STATUS_DISARMED:
385                 printf ("Disarmed\n");
386                 break;
387         case AO_PAD_ARM_STATUS_UNKNOWN:
388                 printf ("Unknown\n");
389                 break;
390         }
391
392         for (c = 0; c < AO_PAD_NUM; c++) {
393                 printf ("Pad %d: ", c);
394                 switch (query.igniter_status[c]) {
395                 case AO_PAD_IGNITER_STATUS_NO_IGNITER_RELAY_CLOSED:     printf ("No igniter. Relay closed\n"); break;
396                 case AO_PAD_IGNITER_STATUS_NO_IGNITER_RELAY_OPEN:       printf ("No igniter. Relay open\n"); break;
397                 case AO_PAD_IGNITER_STATUS_GOOD_IGNITER_RELAY_OPEN:     printf ("Good igniter. Relay open\n"); break;
398                 case AO_PAD_IGNITER_STATUS_UNKNOWN:                     printf ("Unknown\n"); break;
399                 }
400         }
401 }
402
403 void
404 ao_pad_manual(void)
405 {
406         uint8_t ignite;
407         int     repeat;
408         ao_cmd_white();
409         if (!ao_match_word("DoIt"))
410                 return;
411         ao_cmd_decimal();
412         if (ao_cmd_status != ao_cmd_success)
413                 return;
414         ignite = 1 << ao_cmd_lex_i;
415         ao_cmd_decimal();
416         if (ao_cmd_status != ao_cmd_success) {
417                 repeat = 1;
418                 ao_cmd_status = ao_cmd_success;
419         } else
420                 repeat = ao_cmd_lex_i;
421         while (repeat-- > 0) {
422                 ao_pad_ignite = ignite;
423                 ao_wakeup(&ao_pad_ignite);
424                 ao_delay(AO_PAD_FIRE_TIME>>1);
425         }
426 }
427
428 static __xdata struct ao_task ao_pad_task;
429 static __xdata struct ao_task ao_pad_ignite_task;
430 static __xdata struct ao_task ao_pad_monitor_task;
431
432 #if DEBUG
433 void
434 ao_pad_set_debug(void)
435 {
436         ao_cmd_decimal();
437         if (ao_cmd_status == ao_cmd_success)
438                 ao_pad_debug = ao_cmd_lex_i != 0;
439 }
440
441
442 static void
443 ao_pad_alarm_debug(void)
444 {
445         uint8_t which, value;
446         ao_cmd_decimal();
447         if (ao_cmd_status != ao_cmd_success)
448                 return;
449         which = ao_cmd_lex_i;
450         ao_cmd_decimal();
451         if (ao_cmd_status != ao_cmd_success)
452                 return;
453         value = ao_cmd_lex_i;
454         printf ("Set %s to %d\n", which ? "siren" : "strobe", value);
455         if (which)
456                 ao_siren(value);
457         else
458                 ao_strobe(value);
459 }
460 #endif
461
462 __code struct ao_cmds ao_pad_cmds[] = {
463         { ao_pad_test,  "t\0Test pad continuity" },
464         { ao_pad_manual,        "i <key> <n>\0Fire igniter. <key> is doit with D&I" },
465 #if DEBUG
466         { ao_pad_set_debug,     "D <0 off, 1 on>\0Debug" },
467         { ao_pad_alarm_debug,   "S <0 strobe, 1 siren> <0 off, 1 on>\0Set alarm output" },
468 #endif
469         { 0, NULL }
470 };
471
472 void
473 ao_pad_init(void)
474 {
475 #if AO_PAD_NUM > 0
476         ao_enable_output(AO_PAD_PORT, AO_PAD_PIN_0, AO_PAD_0, 0);
477 #endif
478 #if AO_PAD_NUM > 1
479         ao_enable_output(AO_PAD_PORT, AO_PAD_PIN_1, AO_PAD_1, 0);
480 #endif
481 #if AO_PAD_NUM > 2
482         ao_enable_output(AO_PAD_PORT, AO_PAD_PIN_2, AO_PAD_2, 0);
483 #endif
484 #if AO_PAD_NUM > 3
485         ao_enable_output(AO_PAD_PORT, AO_PAD_PIN_3, AO_PAD_3, 0);
486 #endif
487 #ifdef AO_STROBE
488         ao_enable_output(AO_STROBE_PORT, AO_STROBE_PIN, AO_STROBE, 0);
489 #endif
490 #ifdef AO_SIREN
491         ao_enable_output(AO_SIREN_PORT, AO_SIREN_PIN, AO_SIREN, 0);
492 #endif
493         ao_cmd_register(&ao_pad_cmds[0]);
494         ao_add_task(&ao_pad_task, ao_pad, "pad listener");
495         ao_add_task(&ao_pad_ignite_task, ao_pad_run, "pad igniter");
496         ao_add_task(&ao_pad_monitor_task, ao_pad_monitor, "pad monitor");
497 }