status update on changed ajar state
[svn42.git] / firmware / tuer.pde
1 #include <avr/io.h>
2 #include <avr/interrupt.h>
3
4 //********************************************************************//
5
6 #define HEARTBEAT_PIN 15 // blinking led indicating that system is active
7 #define HEARTBEAT_DURATION 10 // *10 ms, duration of heartbeat pulse
8 #define HEARTBEAT_DELAY 200   // *10 ms, 1/heartbeat-frequency
9 int heartbeat_cnt = 0;                    
10         
11 #define LEDS_ON 0xFC
12 #define LEDS_OFF 0x00
13
14 #define LEDS_GREEN_COMMON_PIN 16
15 #define LEDS_RED_COMMON_PIN 17 
16 #define LED_DELAY 50 // *2 ms, between led shifts
17 int led_delay_cnt = 0;
18 byte next_led = 0;
19
20 #define LIMIT_OPENED_PIN 18 // A4: limit switch for open
21 #define LIMIT_CLOSED_PIN 19 // A5: limit switch for close
22
23 #define AJAR_PIN 14 // input pin for reed relais (door ajar/shut)
24 boolean ajar_last_state = false;
25 boolean ajar_state_changed = false;
26
27 #define MANUAL_OPEN_PIN 12  // keys for manual open and close
28 #define MANUAL_CLOSE_PIN 13 // 
29 #define DEBOUNCE_DELAY 6250  // * 16us = 100ms
30 #define DEBOUNCE_IDLE 0     // currently no debouncing
31 #define DEBOUNCE_OPEN 1     // debouncing open key
32 #define DEBOUNCE_CLOSE 2    // debouncing close key
33 #define DEBOUNCE_FINISHED 4 // debouncing finished
34 byte debounce_state;
35 int debounce_cnt = 0;
36
37 #define IDLE 0      // close and open may be called
38 #define OPENING 1   // opening, only 's' command is allowed
39 #define CLOSING 2   // closing, onyl 's' command is allowed
40 #define WAIT 3      // wait some time after open or close and hold last step
41 #define ERROR 4     // an error occured
42
43 #define CMD_OPEN 'o'
44 #define CMD_CLOSE 'c'
45 #define CMD_TOGGLE 't'
46 #define CMD_STATUS 's'
47 #define CMD_RESET 'r'
48
49 #define STEPPER_OFF 0x30
50 byte current_state = IDLE;  // current state of internal state machine
51 byte next_step = 0;         // step counter 0 .. 3
52 #define MOVING_TIMEOUT 1600 // *2 ms, in case limit switches don't work stop and report an error
53 int timeout_cnt = 0;        // counts up to MOVING_TIMEOUT
54
55 //********************************************************************//
56
57 void init_limits()
58 {
59   pinMode(LIMIT_OPENED_PIN, INPUT);      // set pin to input
60   digitalWrite(LIMIT_OPENED_PIN, HIGH);  // turn on pullup resistors  
61
62   pinMode(LIMIT_CLOSED_PIN, INPUT);      // set pin to input
63   digitalWrite(LIMIT_CLOSED_PIN, HIGH);  // turn on pullup resistors  
64 }
65
66 boolean is_opened()
67 {
68   if(digitalRead(LIMIT_OPENED_PIN))
69      return false;
70      
71   return true;
72 }
73
74 boolean is_closed()
75 {
76   if(digitalRead(LIMIT_CLOSED_PIN))
77      return false;
78      
79   return true;
80 }
81
82 //**********//
83
84 void init_ajar()
85 {
86   pinMode(AJAR_PIN, INPUT);      // set pin to input
87   digitalWrite(AJAR_PIN, HIGH);  // turn on pullup resistors  
88   ajar_last_state = digitalRead(AJAR_PIN);
89 }
90
91 boolean get_ajar_status()  // shut = true, ajar = false
92 {
93   if(digitalRead(AJAR_PIN))
94      return false;
95      
96   return true;
97 }
98
99 //**********//
100
101 void init_manual()
102 {
103   pinMode(MANUAL_OPEN_PIN, INPUT);      // set pin to input
104   digitalWrite(MANUAL_OPEN_PIN, HIGH);  // turn on pullup resistors  
105
106   pinMode(MANUAL_CLOSE_PIN, INPUT);     // set pin to input
107   digitalWrite(MANUAL_CLOSE_PIN, HIGH); // turn on pullup resistors  
108
109   debounce_state = DEBOUNCE_IDLE;
110   debounce_cnt = DEBOUNCE_DELAY;
111 }
112
113 boolean manual_open_pressed()
114 {
115   if(digitalRead(MANUAL_OPEN_PIN))
116      return false;
117      
118   return true;
119 }
120
121 boolean manual_close_pressed()
122 {
123   if(digitalRead(MANUAL_CLOSE_PIN))
124      return false;
125      
126   return true;
127 }
128
129 void start_debounce_timer()  // this breaks millis() function, but who cares
130 {
131   debounce_cnt = DEBOUNCE_DELAY;
132
133   TCCR0A = 0;         // no prescaler, WGM = 0 (normal)
134   TCCR0B = 1<<CS00;   // 
135   OCR0A = 255;        // 1+255 = 256 -> 16us @ 16 MHz
136   //OCR0A = 255;        // 1+255 = 256 -> 12.8us @ 20 MHz
137   TCNT0 = 0;          // reseting timer
138   TIMSK0 = 1<<OCF0A;  // enable Interrupt
139
140 }
141
142 void stop_debounce_timer()
143 {
144   // timer0
145   TCCR0B = 0; // no clock source
146   TIMSK0 = 0; // disable timer interrupt
147 }
148
149 ISR(TIMER0_COMPA_vect)
150 {
151   if(((debounce_state & DEBOUNCE_OPEN) && manual_open_pressed()) ||
152      ((debounce_state & DEBOUNCE_CLOSE) && manual_close_pressed())) {
153     if(debounce_cnt) {
154       debounce_cnt--;
155       return;
156     }
157     debounce_state |= DEBOUNCE_FINISHED;
158   }
159   debounce_cnt = DEBOUNCE_DELAY;
160 }
161
162 boolean manual_open()
163 {
164   if(manual_open_pressed()) {
165     if(debounce_state & DEBOUNCE_CLOSE) {
166       stop_debounce_timer();
167       debounce_state = DEBOUNCE_IDLE;
168       return false;
169     }
170
171     if(debounce_state == DEBOUNCE_IDLE) {
172       debounce_state = DEBOUNCE_OPEN;
173       start_debounce_timer();
174     }
175     else if(debounce_state & DEBOUNCE_FINISHED) {
176       stop_debounce_timer();
177       debounce_state = DEBOUNCE_IDLE;
178       return true;
179     }
180   }
181   else if(debounce_state & DEBOUNCE_OPEN) {
182     stop_debounce_timer();
183     debounce_state = DEBOUNCE_IDLE;
184   }
185
186   return false;
187 }
188
189 boolean manual_close()
190 {
191   if(manual_close_pressed()) {
192     if(debounce_state & DEBOUNCE_OPEN) {
193       stop_debounce_timer();
194       debounce_state = DEBOUNCE_IDLE;
195       return false;
196     }
197
198     if(debounce_state == DEBOUNCE_IDLE) {
199       debounce_state = DEBOUNCE_CLOSE;
200       start_debounce_timer();
201     }
202     else if(debounce_state & DEBOUNCE_FINISHED) {
203       stop_debounce_timer();
204       debounce_state = DEBOUNCE_IDLE;
205       return true;
206     }
207   }
208   else if(debounce_state & DEBOUNCE_CLOSE) {
209     stop_debounce_timer();
210     debounce_state = DEBOUNCE_IDLE;
211   }
212
213   return false;
214 }
215
216 //********************************************************************//
217
218 void reset_stepper()
219 {
220   next_step = 0;
221   PORTB = STEPPER_OFF;
222   timeout_cnt = 0;
223 }
224
225 void init_stepper()
226 {
227   DDRB = 0x0F; // set PortB 3:0 as output
228   reset_stepper();
229 }
230
231 byte step_table(byte step)
232 {
233   switch(step) { // 0011 xxxx, manual keys pull-ups stay active
234   case 0: return 0x33;
235   case 1: return 0x36;
236   case 2: return 0x3C;
237   case 3: return 0x39;
238   }
239   return STEPPER_OFF;
240 }
241
242 //**********//
243
244 void reset_leds()
245 {
246   led_delay_cnt = 0;
247   next_led = 0;
248   PORTD = LEDS_OFF;
249   digitalWrite(LEDS_GREEN_COMMON_PIN, HIGH);
250   digitalWrite(LEDS_RED_COMMON_PIN, HIGH);
251 }
252
253 void init_leds()
254 {
255   DDRD = 0xFC;
256   pinMode(LEDS_GREEN_COMMON_PIN, OUTPUT);
257   pinMode(LEDS_RED_COMMON_PIN, OUTPUT);
258   reset_leds();
259 }
260
261 byte led_table(byte led)
262 {
263   switch(led) {  // xxxx xx00, leave RxD and TxD to 0
264   case 0: return 0x04;
265   case 1: return 0x08;
266   case 2: return 0x10;
267   case 3: return 0x20;
268   case 4: return 0x40;
269   case 5: return 0x80; 
270   }
271   return LEDS_OFF;
272 }
273
274 void leds_green()
275 {
276   digitalWrite(LEDS_GREEN_COMMON_PIN, LOW);
277   digitalWrite(LEDS_RED_COMMON_PIN, HIGH);
278 }
279
280 void leds_red()
281 {
282   digitalWrite(LEDS_GREEN_COMMON_PIN, HIGH);
283   digitalWrite(LEDS_RED_COMMON_PIN, LOW);
284 }
285
286 void leds_toggle()
287 {
288   if(digitalRead(LEDS_GREEN_COMMON_PIN) == HIGH) {
289     digitalWrite(LEDS_GREEN_COMMON_PIN, LOW);
290     digitalWrite(LEDS_RED_COMMON_PIN, HIGH);
291   }
292   else {
293     digitalWrite(LEDS_GREEN_COMMON_PIN, HIGH);
294     digitalWrite(LEDS_RED_COMMON_PIN, LOW);
295   }
296 }
297
298 //**********//
299
300 void start_step_timer()
301 {
302   // timer 1: 2 ms, between stepper output state changes
303   TCCR1A = 0;                    // prescaler 1:256, WGM = 4 (CTC)
304   TCCR1B = 1<<WGM12 | 1<<CS12;   // 
305   OCR1A = 124;        // (1+124)*256 = 32000 -> 2 ms @ 16 MHz
306   //OCR1A = 155;        // (1+155)*256 = 40000 -> 2 ms @ 20 MHz
307   TCNT1 = 0;          // reseting timer
308   TIMSK1 = 1<<OCIE1A; // enable Interrupt
309 }
310
311 void start_wait_timer()
312 {
313   // timer1: 250 ms, minimal delay between subsequent open/close
314   TCCR1A = 0;         // prescaler 1:256, WGM = 0 (normal)
315   TCCR1B = 1<<CS12;   // 
316   OCR1A = 15624;      // (1+15624)*256 = 4000000 -> 250 ms @ 16 MHz
317   //OCR1A = 19530;      // (1+19530)*256 = 5000000 -> 250 ms @ 20 MHz  
318   TCNT1 = 0;          // reseting timer
319   TIMSK1 = 1<<OCIE1A; // enable Interrupt
320 }
321
322 void start_error_timer()
323 {
324   // timer1: 500 ms, blinking leds with 1 Hz
325   TCCR1A = 0;                  // prescaler 1:256, WGM = 4 (CTC)
326   TCCR1B = 1<<WGM12 | 1<<CS12; // 
327   OCR1A = 31249;      // (1+31249)*256 = 8000000 -> 500 ms @ 16 MHz
328   //OCR1A = 39061;      // (1+39061)*256 = 10000000 -> 500 ms @ 20 MHz
329   TCNT1 = 0;          // reseting timer
330   TIMSK1 = 1<<OCIE1A; // enable Interrupt
331 }
332
333 void stop_timer() // stop the timer
334 {
335   // timer1
336   TCCR1B = 0; // no clock source
337   TIMSK1 = 0; // disable timer interrupt
338 }
339
340 ISR(TIMER1_COMPA_vect)
341 {
342       // check if limit switch is active
343   if((current_state == OPENING && is_opened()) ||
344      (current_state == CLOSING && is_closed())) 
345   {
346     stop_timer();
347     reset_leds();
348     if(current_state == OPENING)
349       leds_green();
350     else
351       leds_red();
352     PORTD = LEDS_ON;
353     current_state = WAIT;
354     start_wait_timer();
355     return;
356   }
357       
358   if(current_state == OPENING || current_state == CLOSING) {
359     timeout_cnt++;
360     if(timeout_cnt >= MOVING_TIMEOUT) {
361       reset_stepper();
362       stop_timer();
363       current_state = ERROR;
364       Serial.println("Error: open/close took too long!");
365       start_error_timer();
366       leds_green();
367       PORTD = LEDS_ON;
368     }
369   }
370
371   if(current_state == OPENING) { // next step (open)
372     PORTB = step_table(next_step);
373     next_step++;
374     if(next_step >= 4)
375       next_step = 0;
376   }
377   else if(current_state == CLOSING) { // next step (close)
378     PORTB = step_table(next_step);
379     if(next_step == 0)
380       next_step = 3;
381     else
382       next_step--;
383   }
384   else if(current_state == WAIT) { // wait after last open/close finished -> idle
385     stop_timer();
386     reset_stepper();
387     current_state = IDLE;
388     Serial.print("Status: ");
389     if(is_opened())
390       Serial.print("opened");
391     else if(is_closed())
392       Serial.print("closed");
393     Serial.print(", idle");
394     if(get_ajar_status())
395       Serial.println(", shut");
396     else
397       Serial.println(", ajar");
398     return;
399   }
400   else if(current_state == ERROR) {
401     leds_toggle();
402     return;
403   }
404   else { // timer is useless stop it
405     stop_timer();
406     return;
407   }
408
409   led_delay_cnt++;
410   if(led_delay_cnt >= LED_DELAY) {
411     led_delay_cnt = 0;    
412
413     PORTD = led_table(next_led);
414
415     if(current_state == OPENING) {
416       if(next_led == 0)
417         next_led = 5;
418       else
419         next_led--;
420     }
421     else if(current_state == CLOSING) {
422       next_led++;
423       if(next_led >= 6)
424         next_led = 0;
425     }
426   }
427 }
428
429 //********************************************************************//
430
431 void reset_heartbeat()
432 {
433   digitalWrite(HEARTBEAT_PIN, HIGH);
434   heartbeat_cnt = 0;
435 }
436
437 void heartbeat_on()
438 {
439   digitalWrite(HEARTBEAT_PIN, LOW);
440 }
441
442 void heartbeat_off()
443 {
444   digitalWrite(HEARTBEAT_PIN, HIGH);
445 }
446
447 void init_heartbeat()
448 {
449   pinMode(HEARTBEAT_PIN, OUTPUT);
450   reset_heartbeat();
451   // timer 2: ~10 ms, timebase for heartbeat signal
452   TCCR2A = 1<<WGM21;                    // prescaler 1:1024, WGM = 2 (CTC)
453   TCCR2B = 1<<CS22 | 1<<CS21 | 1<<CS20; // 
454   OCR2A = 155;        // (1+155)*1024 = 159744 -> ~10 ms @ 16 MHz
455   //OCR2A = 194;        // (1+194)*1024 = 199680 -> ~10 ms @ 20 MHz
456   TCNT2 = 0;          // reseting timer
457   TIMSK2 = 1<<OCIE2A; // enable Interrupt
458   heartbeat_on();
459 }
460
461 // while running this gets called every ~10ms
462 ISR(TIMER2_COMPA_vect)
463 {
464   boolean a = get_ajar_status();
465   heartbeat_cnt++;
466   if(heartbeat_cnt == HEARTBEAT_DURATION) {
467     heartbeat_off();
468     if(a != ajar_last_state) 
469       ajar_state_changed = true;
470     ajar_last_state = a;
471   } else if(heartbeat_cnt >= HEARTBEAT_DELAY) {
472     heartbeat_on();
473     heartbeat_cnt = 0;
474     if(a != ajar_last_state)
475       ajar_state_changed = true;
476     ajar_last_state = a;
477   }
478 }
479
480 //********************************************************************//
481
482 void reset_after_error()
483 {
484   stop_timer();
485   reset_leds();
486
487   leds_red();
488   if(is_closed()) {
489     current_state = IDLE;
490     PORTD = LEDS_ON;
491   }
492   else {
493     current_state = CLOSING;
494     start_step_timer();
495   }
496   Serial.println("Ok, closing now");
497 }
498
499 void start_open()
500 {
501   reset_stepper();
502   reset_leds();
503   leds_green();
504   current_state = OPENING;
505   start_step_timer();
506 }
507
508 void start_close()
509 {
510   reset_stepper();
511   reset_leds();
512   leds_red();
513   current_state = CLOSING;
514   start_step_timer();
515 }
516
517 void print_status()
518 {
519   Serial.print("Status: ");
520   if(is_opened())
521     Serial.print("opened");
522   else if(is_closed())
523     Serial.print("closed");
524   else
525     Serial.print("<->");
526
527   switch(current_state) {
528   case IDLE: Serial.print(", idle"); break;
529   case OPENING: Serial.print(", opening"); break;
530   case CLOSING: Serial.print(", closing"); break;
531   case WAIT: Serial.print(", waiting"); break;
532   default: Serial.print(", <undefined state>"); break;
533   }
534   if(get_ajar_status())
535     Serial.println(", shut");
536   else
537     Serial.println(", ajar");
538 }
539
540 //**********//
541
542 void setup()
543 {
544   init_limits();
545   init_ajar();
546   init_stepper();
547   init_leds();
548   init_heartbeat();
549
550   Serial.begin(9600);
551
552   current_state = IDLE;
553
554       // make sure door is locked after reset
555   leds_red();
556   if(is_closed())
557     PORTD = LEDS_ON;
558   else {
559     current_state = CLOSING;
560     start_step_timer();
561   }
562   Serial.println("init complete");
563 }
564
565 void loop()
566 {
567   if(Serial.available()) {
568     char command = Serial.read();
569
570     if(current_state == ERROR && command != CMD_RESET) {
571       Serial.println("Error: last open/close operation took too long!");
572     }
573     else if (command == CMD_RESET) {
574       reset_after_error();
575     }
576     else if (command == CMD_OPEN) {
577       if(current_state == IDLE) {
578         if(is_opened())
579           Serial.println("Already open");
580         else {
581           start_open();
582           Serial.println("Ok");
583         }
584       }
585       else
586         Serial.println("Error: Operation in progress");
587     }
588     else if (command == CMD_CLOSE) {
589       if(current_state == IDLE) {
590         if(is_closed())
591           Serial.println("Already closed");
592         else {
593           start_close();
594           Serial.println("Ok");
595         }
596       }
597       else
598         Serial.println("Error: Operation in progress");
599     }
600     else if (command == CMD_TOGGLE) {
601       if(current_state == IDLE) {
602         if(is_closed())
603           start_open();
604         else
605           start_close();
606         Serial.println("Ok");
607       }
608       else
609         Serial.println("Error: Operation in progress");
610     }
611     else if (command == CMD_STATUS)
612       print_status();
613     else
614       Serial.println("Error: unknown command");
615   }
616   if(manual_open() && !is_opened() && (current_state == IDLE || current_state == ERROR)) {
617     Serial.println("open forced manually");
618     start_open();
619   }
620   if(manual_close() && !is_closed() && (current_state == IDLE || current_state == ERROR)) {
621     Serial.println("close forced manually");
622     start_close();
623   }
624   if(current_state == IDLE) {
625     if(is_opened()) {
626       leds_green();
627       PORTD = LEDS_ON;
628     }
629     if(is_closed()) {
630       leds_red();
631       PORTD = LEDS_ON;
632     }
633   }
634   if(ajar_state_changed) {
635     ajar_state_changed = false;
636     print_status();
637   }
638 }