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