Fixing LCD corruption, etc.
[arduino-intervalometer.git] / Intervalometer.pde
blob2320e4f5ab2ded5e034d5a390302d2afc688a16c
1 /*
2  *  Arduino Intervalometer
3  *  Tim Horton, 2008
4  */
6 enum {INTERVAL, BULB, INTERVALBULB, TRIGGER};
8 /*
9  *  TODOs
10  */
12 // Running/Shutter LEDs (through digital pots)
13 // Contrast adjustment (through digital pots/encoder)
14 // Easter egg!
15 // Speed up encoder more, later in the 'minute' range
16 // Slow down encoder in second range/normally
17 // Hour representation?
18 // Either need to support more digits, or arbitrarily limit durations
19 // Fix when trigger is triggered and someone (ROBB) stops it (and it keeps open)
20 // Why does trigger reset not get put back when we're done triggering!?
21 // External power supply?
24  *  Pin Assignment
25  */
27 int cameraShutter = 0;  // Pulse low for shutter release
29 int triggerReset =  1;  // Resets trigger latch on low pulse
30                                                 // Prevents trigger if held low
32 int encoderPinA =       2;      // HW interrupt
33 int encoderPinB =       3;      // HW interrupt
35 int lcdPower =          4;      // Pull low to power the LCD backlight
36 int lcdEnable =         7;      // LCD Enable - pin 6 on LCD module
37 int lcdClock =          8;      // Shift register clock
38 int lcdData =           9;      // Shift register data
40 int potSelect =         10; // SPI (SS) for digital pots
41 int potData =           11; // SPI (MOSI) for digital pots
42 int badSPIpin =         12;     // SPI (MISO) for nothing, unusable
43 int potClock =          13; // SPI (SCK) for digital pots
45 int triggerInput =  2; // Feedback for trigger reset
46 int encoderButton =     17; // Encoder pushbutton
47 int buttonA =           18; // Left pushbutton
48 int buttonB =           19; // Right pushbutton
50 /////////////////////////////
52 int writing = 0;
54 int currentMode = INTERVAL, selected = 0;
56 // Debouncing time variables
57 unsigned long lastToggleRunning = 0;
58 unsigned long lastModeUpdate = 0;
59 unsigned long lastSelectedUpdate = 0;
60 unsigned long lastShutter = 0;
62 // Update statuses
64 volatile int updateHeader = 1, updateEncoder = 1, running = 0;
66 // Exposure and Timelapse durations
67 volatile long exposureTime = 0, lapseTime = 0;
68 volatile int exposureRepresentation = 0, lapseRepresentation = 0;
71  *  Higher level hardware functions
72  */
74 void pulsePin(int pin, int value)
76         digitalWrite(pin, !value);
77         delay(1);
78         digitalWrite(pin, value);
79         delay(1);
80         digitalWrite(pin, !value);
81         delay(1);
84 void parallelShiftOut(int firstPin, int lastPin, int & value)
86         int i;
87         for(i = firstPin; i <= lastPin; i++)
88         {
89                 digitalWrite(i, value & 0x01);
90                 value >>= 1;
91         }
95  *  Digital Pot Control Functions
96  */
98 char spi_transfer(volatile char data)
100         SPDR = data;
101         while (!(SPSR & (1<<SPIF))) {};
102         return SPDR;
105 void digitalPotInit()
107         byte i;
108         byte clr;
109         pinMode(potData, OUTPUT);
110         pinMode(potClock,OUTPUT);
111         pinMode(potSelect,OUTPUT);
112         digitalWrite(potSelect,HIGH);
113         SPCR = (1<<SPE)|(1<<MSTR);
114         clr=SPSR;
115         clr=SPDR;
116         delay(10);
117         for (i=0;i<6;i++)
118                 write_pot(i,255);
121 byte write_pot(int address, int value)
123         digitalWrite(potSelect,LOW);
124         spi_transfer(address);
125         spi_transfer(value);
126         digitalWrite(potSelect,HIGH);
130  *  LCD Control Functions
131  */
133 void lcdDataWrite(byte a)
135         writing = 1;
136         shiftOut(lcdData, lcdClock, LSBFIRST, 0x20 + ((a >> 4) & 0xF));
137         digitalWrite(lcdEnable, HIGH);
138         delayMicroseconds(1);
139         digitalWrite(lcdEnable, LOW);
141         delay(1);
143         shiftOut(lcdData, lcdClock, LSBFIRST, 0x20 + (a & 0xF));
144         digitalWrite(lcdEnable, HIGH);
145         delayMicroseconds(1);
146         digitalWrite(lcdEnable, LOW);
147         
148         delay(1);
149         writing = 0;
152 void lcdCommandWrite(int a)
154         writing = 1;
155         shiftOut(lcdData, lcdClock, LSBFIRST, (a >> 4) & 0xF);
156         digitalWrite(lcdEnable, HIGH);
157         delayMicroseconds(1);
158         digitalWrite(lcdEnable, LOW);
160         delay(1);
162         shiftOut(lcdData, lcdClock, LSBFIRST, a & 0xF);
163         digitalWrite(lcdEnable, HIGH);
164         delayMicroseconds(1);
165         digitalWrite(lcdEnable, LOW);
166         
167         delay(1);
168         writing = 0;
171 void lcdNumberWrite(int nr)
173         int n1 = nr/100;
174         int n2 = (nr - n1 * 100) / 10;
175         
176         if(n2)
177                 lcdDataWrite('0' + n2);
178         
179         nr = nr - n1 * 100 - n2 * 10;
180         lcdDataWrite('0' + nr);
183 void lcdHome(int row)
185         lcdCommandWrite(0x02);
186         
187         delay(4);
188         
189         if(row == 1)
190                 lcdCommandWrite(0xC0);
191                 
192         delay(1);
195 void lcdClear()
197         lcdCommandWrite(0x01);
198         delay(4);
202  *  Hardware initialization functions
203  */
205 void lcdInit()
207         int i = 0;
208         
209         pinMode(lcdEnable, OUTPUT);
210         pinMode(lcdData, OUTPUT);
211         pinMode(lcdClock, OUTPUT);
212         
213         digitalWrite(lcdClock,LOW);
214         digitalWrite(lcdData,LOW);
215         digitalWrite(lcdEnable,LOW);
216         
217         // there's a chance that when we drop to not running 
218         // on top of the arduino bootloader, we'll need a somewhat
219         // significant delay here (called for in the spec, but the bl is slow)
221         lcdCommandWrite(0x03); delay(5);
222         lcdCommandWrite(0x03); delay(1);
223         lcdCommandWrite(0x03); delay(1);        
224         lcdCommandWrite(0x02); delay(4);
225         lcdCommandWrite(0x06); delay(1);
226         lcdCommandWrite(0x0C); delay(1);
227         lcdCommandWrite(0x01); delay(4);
228         lcdCommandWrite(0x80); delay(1);
229         
230         pinMode(lcdPower, OUTPUT);
231         digitalWrite(lcdPower, LOW);
234 void encoderInit()
236         pinMode(encoderPinA, INPUT); 
237         digitalWrite(encoderPinA, HIGH);
238         pinMode(encoderPinB, INPUT); 
239         digitalWrite(encoderPinB, HIGH);
241         attachInterrupt(0, doEncoderA, CHANGE);
242         attachInterrupt(1, doEncoderB, CHANGE);
245 void setup (void)
247         pinMode(cameraShutter, OUTPUT);
248         digitalWrite(cameraShutter, HIGH);
249         
250         pinMode(triggerReset, OUTPUT);
251         // keep the reset high except when in trigger mode, so we don't accidentally trigger!
252         digitalWrite(triggerReset, LOW);
254         digitalPotInit();
255         lcdInit();
256         encoderInit();
259 void incrementValue()
261         if(selected == 0)
262         {
263                 if(lapseRepresentation == 0)
264                         lapseTime++;
265                 else
266                         lapseTime += 60;
267         }
268         else
269         {
270                 if(exposureRepresentation == 0)
271                         exposureTime++;
272                 else
273                         exposureTime += 60;
274                 
275                 if(exposureTime > lapseTime)
276                         lapseTime = exposureTime;
277         }
280 void decrementValue()
282         if(selected == 0)
283         {
284                 if(lapseRepresentation == 0 || lapseTime == 60) // careful around transition; thanks, nate
285                         lapseTime--;
286                 else
287                         lapseTime -= 60;
288                 
289                 if(lapseTime < 0)
290                         lapseTime = 0;
291         }
292         else
293         {
294                 if(exposureRepresentation == 0 || exposureTime == 60)
295                         exposureTime--;
296                 else
297                         exposureTime -= 60;
298                 
299                 if(exposureTime < 0)
300                         exposureTime = 0;
301         }
304 void updateTimeRepresentations()
306         if(lapseTime >= 60)
307                 lapseRepresentation = 1;
308         else
309                 lapseRepresentation = 0;
311         if(exposureTime >= 60)
312                 exposureRepresentation = 1;
313         else
314                 exposureRepresentation = 0;
315         
316         updateEncoder = 1;
319 void doEncoderA()
321 //      if(running)
322 //              return;
323                 
324         noInterrupts();
325         delayMicroseconds(3000); // maximum bounce time, accd. to spec.
327         if (digitalRead(encoderPinA) == HIGH)
328         { 
329                 if (digitalRead(encoderPinB) == LOW)
330                         incrementValue();
331                 else
332                         decrementValue();
333         }
334         else                                       
335         {
336                 if (digitalRead(encoderPinB) == HIGH)
337                         incrementValue();
338                 else
339                         decrementValue();
340         }
341         
342         updateTimeRepresentations();
343         
344         updateEncoder = 1;
345         interrupts();
348 void doEncoderB()
350 //      if(running)
351 //              return;
352         
353         noInterrupts();
354         delayMicroseconds(3000);
355         if (digitalRead(encoderPinB) == HIGH)
356         {
357                 if (digitalRead(encoderPinA) == HIGH)
358                         incrementValue();
359                 else
360                         decrementValue();
361         }
362         else
363         {
364                 if (digitalRead(encoderPinA) == LOW)
365                         incrementValue();
366                 else
367                         decrementValue();
368         }
369         
370         updateTimeRepresentations();
371         
372         updateEncoder = 1;
373         interrupts();
376 void switchModes()
378         unsigned long diff = (millis() - lastModeUpdate);
379         
380         if(diff < 300) // careful about the overflow...
381         {
382                 lastModeUpdate = millis();
383                 return;
384         }
385         else
386                 lastModeUpdate = millis();
387         
388         currentMode++;
389         if(currentMode > 3)
390                 currentMode = 0;
391         
392         if(currentMode == BULB)
393                 selected = 1;
394         else
395                 selected = 0;
396         
397         if(currentMode == TRIGGER)
398                 digitalWrite(triggerReset, HIGH);
399         else
400                 digitalWrite(triggerReset, LOW);
401                 
402         
403         updateHeader = 1;
406 void switchSelected()
408         if(currentMode == INTERVALBULB)
409         {
410                 unsigned long diff = (millis() - lastSelectedUpdate);
411                 
412                 if(diff < 300) // careful about the overflow...
413                 {
414                         lastSelectedUpdate = millis();
415                         return;
416                 }
417                 else
418                         lastSelectedUpdate = millis();
420                 selected = !selected;
421         }
422         
423         updateEncoder = 1;
426 void toggleRunning()
428         unsigned long diff = (millis() - lastToggleRunning);
429         
430         if(diff < 300) // careful about the overflow...
431         {
432                 lastToggleRunning = millis();
433                 return;
434         }
435         else
436                 lastToggleRunning = millis();
437         
438         running = !running;
439         
440         updateEncoder = 1;
443 void drawTimecode(int secs, int rep)
445         if(rep == 0)
446         {
447                 lcdNumberWrite(secs);
448                 lcdDataWrite('"');
449         }
450         else
451         {
452                 lcdNumberWrite(secs/60);
453                 lcdDataWrite('\'');
454         }
457 int timecodeLength(int secs, int rep)
459         int count = 2;
460         
461         if(rep)
462                 secs = secs/60;
463         
464         int n1 = secs/100;
465         int n2 = (secs - n1 * 100) / 10;
467         if(n2)
468                 count++;
469         
470         return count;
473 char *modeHeader[4] = {"Interval", "            Bulb", "Interval    Bulb", "Trigger"};
475 int pot = 765;
476 int fadeUp = 1;
478 void loop(void)
480         if(fadeUp)
481         {
482                 write_pot(3, 20);
483                 write_pot(1, 255);
484                 write_pot(0, 255);
485                 write_pot(4,0);
486         }
487         
488         if(fadeUp)
489                 write_pot(5,--pot / 3.0);
490         if(pot == 50)
491                 fadeUp = 0;
492                 
493         if(updateHeader)
494         {
495                 lcdHome(0);
496                 
497                 int count;
498                 for (count = 0; modeHeader[currentMode][count] != 0; count++)
499                         lcdDataWrite(modeHeader[currentMode][count]);
500                 for (; count < 16; count++)
501                         lcdDataWrite(' ');
502                 
503                 updateHeader = 0;
504                 updateEncoder = 1;
505         }
506                 
507         if(updateEncoder)
508         {
509                 lcdHome(1);
510                 
511                 if(currentMode != BULB)
512                         drawTimecode(lapseTime, lapseRepresentation);
513                 
514                 int width = 0;
515                 
516                 if(currentMode != BULB)
517                         width += timecodeLength(lapseTime, lapseRepresentation);
518                 if(currentMode != INTERVAL && currentMode != TRIGGER)
519                         width += timecodeLength(exposureTime, exposureRepresentation);
520                         
521                 if(selected == 0)
522                 {
523                         lcdDataWrite(' '); width++;
524                         lcdDataWrite(127); width++;
525                 }
526                 else
527                 {
528                         width += 2;
529                 }
530                                 
531                 for(int i = 16; i > width; i--)
532                         lcdDataWrite(' ');
533                 
534                 if(selected == 1)
535                 {
536                         lcdDataWrite(126);
537                         lcdDataWrite(' ');
538                 }
539                 
540                 if(currentMode != INTERVAL && currentMode != TRIGGER)
541                         drawTimecode(exposureTime, exposureRepresentation);
542                 
543                 updateEncoder = 0;
544         }
545         
546         if(!digitalRead(buttonA))
547                 toggleRunning();
548         
549         if(running)
550         {
551                 digitalWrite(lcdPower, HIGH);
552                 
553                 if(currentMode == TRIGGER)
554                 {
555                         if(analogRead(triggerInput) < 100) // 100 might change with different resistors, make sure it works!
556                         {
557                                 digitalWrite(triggerReset, HIGH);
558                                 delay(100); // this should probably be at least the time of the delay from signal (in the 555)...
559                                 digitalWrite(triggerReset, LOW);
560                                 delay(10);
561                                 digitalWrite(triggerReset, HIGH);
562                         }
563                         
564                         return;
565                 }
566                 
567                 unsigned long diff = millis() - lastShutter;
568                 
569                 int adjustedLapseTime = lapseTime;
570                 if(currentMode != INTERVAL)
571                         adjustedLapseTime -= exposureTime;
572                 
573                 if(diff > (adjustedLapseTime * 1000)) // careful about the overflow...
574                 {
575                         digitalWrite(cameraShutter, LOW);
576                         
577                         if(currentMode == INTERVAL)
578                                 delay(100);
579                         else
580                                 delay(exposureTime * 1000); //delay for length of exposure
581                                 // biggest problem with this is that you can't stop a bulb (of either type)
582                                 // in the middle... you have to power off the intervalometer; same as you would have 
583                                 // to do with a camera, I suppose, so people might be used to it.
584                                 // however, we can get around this by looping and checking millis()...
585                                 
586                         digitalWrite(cameraShutter, HIGH);
587                         lastShutter = millis();
588                         
589                         if(currentMode == BULB)
590                                 running = 0;
591                 }
592         }
593         else
594         {
595                 digitalWrite(lcdPower, LOW);
596                 
597                 if(!digitalRead(buttonB))
598                         switchModes();
599                 
600                 if(!digitalRead(encoderButton))
601                         switchSelected();
602         }