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