Add schematic?
[arduino-intervalometer.git] / Intervalometer.pde
blobcf7d02f5db969a6d6f49ef2537ed5c53fcd22b53
1 /*
2  *  Arduino Intervalometer
3  *  Tim Horton, 2008
4  */
6 enum {INTERVAL, BULB, INTERVALBULB, TRIGGER};
8 // There's currently a bug in arduino12 which breaks interrupts.
11  *  TODOs
12  */
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?
22 // preferences: save if we want to use the LED, contrast, etc. in eeprom
25  *  Pin Assignment
26  */
28 /* Analog Pins */
30 int triggerInput =  2; // Feedback for trigger reset
32 /* Digital Pins */
34 int cameraShutter = 0;  // Pulse low for shutter release
36 int triggerReset =  1;  // Resets trigger latch on low pulse
37                                                 // Prevents trigger if held low
39 int encoderPinA =       2;      // HW interrupt
40 int encoderPinB =       3;      // HW interrupt
42 int lcdPower =          4;      // Pull low to power the LCD backlight
43 int lcdEnable =         7;      // LCD Enable - pin 6 on LCD module
44 int lcdClock =          8;      // Shift register clock
45 int lcdData =           9;      // Shift register data
47 int potSelect =         10; // SPI (SS) for digital pots
48 int potData =           11; // SPI (MOSI) for digital pots
49 int badSPIpin =         12;     // SPI (MISO) for nothing, unusable
50 int potClock =          13; // SPI (SCK) for digital pots
52 int encoderButton =     17; // Encoder pushbutton
53 int buttonA =           18; // Left pushbutton
54 int buttonB =           19; // Right pushbutton
57  *  Digital Pot Assignment
58  */
60 int ledRPot = 3;
61 int ledGPot = 1;
62 int ledBPot = 0;
64 int timerPot = 2;
65 int contrastPot = 5;
67 /////////////////////////////
69 volatile int writing = 0;
71 int currentMode = INTERVAL, selected = 0, changeSelected = 0;
73 // Debouncing time variables
74 unsigned long lastToggleRunning = 0;
75 unsigned long lastModeUpdate = 0;
76 unsigned long lastSelectedUpdate = 0;
77 unsigned long lastShutter = 0;
79 // Update statuses
81 volatile int updateHeader = 1, updateEncoder = 1, running = 0;
83 // Exposure and Timelapse durations
84 volatile long exposureTime = 0, lapseTime = 0;
85 volatile int exposureRepresentation = 0, lapseRepresentation = 0;
87 volatile int contrastValue = 1000;
90  *  Higher level hardware functions
91  */
93 void pulsePin(int pin, int value)
95         digitalWrite(pin, !value);
96         delayMicroseconds(1);
97         digitalWrite(pin, value);
98         delayMicroseconds(1);
99         digitalWrite(pin, !value);
100         delay(1);
103 void parallelShiftOut(int firstPin, int lastPin, int & value)
105         int i;
106         for(i = firstPin; i <= lastPin; i++)
107         {
108                 digitalWrite(i, value & 0x01);
109                 value >>= 1;
110         }
114  *  Digital Pot Control Functions
115  */
117 char spi_transfer(volatile char data)
119         SPDR = data;
120         while (!(SPSR & (1<<SPIF))) {};
121         return SPDR;
124 void digitalPotInit()
126         byte i;
127         byte clr;
128         pinMode(potData, OUTPUT);
129         pinMode(potClock,OUTPUT);
130         pinMode(potSelect,OUTPUT);
131         digitalWrite(potSelect,HIGH);
132         SPCR = (1<<SPE)|(1<<MSTR);
133         clr=SPSR;
134         clr=SPDR;
135         delay(10);
136         for (i=0;i<6;i++)
137                 write_pot(i,255);
140 byte write_pot(int address, int value)
142         digitalWrite(potSelect,LOW);
143         spi_transfer(address);
144         spi_transfer(value);
145         digitalWrite(potSelect,HIGH);
149  *  LCD Control Functions
150  */
152 void lcdDataWrite(byte a)
154         if(writing)
155                 return;
156                 
157         writing = 1;
158         shiftOut(lcdData, lcdClock, LSBFIRST, 0x20 + ((a >> 4) & 0xF));
159         pulsePin(lcdEnable, HIGH);
161         shiftOut(lcdData, lcdClock, LSBFIRST, 0x20 + (a & 0xF));
162         pulsePin(lcdEnable, HIGH);
164         delay(1);
165         
166         writing = 0;
169 void lcdCommandWrite(int a)
171         if(writing)
172                 return;
173                 
174         writing = 1;
175         shiftOut(lcdData, lcdClock, LSBFIRST, (a >> 4) & 0xF);
176         pulsePin(lcdEnable, HIGH);
178         shiftOut(lcdData, lcdClock, LSBFIRST, a & 0xF);
179         pulsePin(lcdEnable, HIGH);
180         
181         delay(1);
182         
183         writing = 0;
186 void lcdNumberWrite(int nr)
188         int n1 = nr/100;
189         int n2 = (nr - n1 * 100) / 10;
190         
191         if(n2)
192                 lcdDataWrite('0' + n2);
193         
194         nr = nr - n1 * 100 - n2 * 10;
195         lcdDataWrite('0' + nr);
198 void lcdHome(int row)
200         lcdCommandWrite(0x02);
201         
202         delay(4);
203         
204         if(row == 1)
205                 lcdCommandWrite(0xC0);
206                 
207         delay(1);
210 void lcdClear()
212         lcdCommandWrite(0x01);
213         delay(4);
217  *  Hardware initialization functions
218  */
220 void lcdInit()
222         int i = 0;
223         
224         pinMode(lcdEnable, OUTPUT);
225         pinMode(lcdData, OUTPUT);
226         pinMode(lcdClock, OUTPUT);
227         
228         digitalWrite(lcdClock,LOW);
229         digitalWrite(lcdData,LOW);
230         digitalWrite(lcdEnable,LOW);
231         
232         // there's a chance that when we drop to not running 
233         // on top of the arduino bootloader, we'll need a somewhat
234         // significant delay here (called for in the spec, but the bl is slow)
236         lcdCommandWrite(0x03); delay(5);
237         lcdCommandWrite(0x03); delay(1);
238         lcdCommandWrite(0x03); delay(1);        
239         lcdCommandWrite(0x02); delay(4);
240         lcdCommandWrite(0x06); delay(1);
241         lcdCommandWrite(0x0C); delay(1);
242         lcdCommandWrite(0x01); delay(4);
243         lcdCommandWrite(0x80); delay(1);
244         
245         pinMode(lcdPower, OUTPUT);
246         digitalWrite(lcdPower, HIGH);
249 void encoderInit()
251         pinMode(encoderPinA, INPUT); 
252         digitalWrite(encoderPinA, HIGH);
253         pinMode(encoderPinB, INPUT); 
254         digitalWrite(encoderPinB, HIGH);
256         attachInterrupt(0, doEncoderA, CHANGE);
257         attachInterrupt(1, doEncoderB, CHANGE);
260 void setup (void)
262         pinMode(cameraShutter, OUTPUT);
263         digitalWrite(cameraShutter, HIGH);
264         
265         pinMode(triggerReset, OUTPUT);
266         // keep the reset high except when in trigger mode, so we don't accidentally trigger!
267         digitalWrite(triggerReset, LOW);
269         digitalPotInit();
270         lcdInit();
271         encoderInit();
272         writeLED(0,0,128);
273         
274         write_pot(timerPot, 255);
277 void incrementValue()
279         if(changeSelected)
280         {
281                 contrastValue++;
282                 updateContrast();
283                 return;
284         }
285         
286         if(selected == 0)
287         {
288                 if(lapseRepresentation == 0)
289                         lapseTime++;
290                 else
291                         lapseTime += 60;
292         }
293         else
294         {
295                 if(exposureRepresentation == 0)
296                         exposureTime++;
297                 else
298                         exposureTime += 60;
299                 
300                 if(exposureTime > lapseTime)
301                         lapseTime = exposureTime;
302         }
305 void decrementValue()
307         if(changeSelected)
308         {
309                 contrastValue--;
310                 updateContrast();
311                 return;
312         }
313         
314         if(selected == 0)
315         {
316                 if(lapseRepresentation == 0 || lapseTime == 60) // careful around transition; thanks, nate
317                         lapseTime--;
318                 else
319                         lapseTime -= 60;
320                 
321                 if(lapseTime < 0)
322                         lapseTime = 0;
323         }
324         else
325         {
326                 if(exposureRepresentation == 0 || exposureTime == 60)
327                         exposureTime--;
328                 else
329                         exposureTime -= 60;
330                 
331                 if(exposureTime < 0)
332                         exposureTime = 0;
333         }
336 void updateTimeRepresentations()
338         if(lapseTime >= 60)
339                 lapseRepresentation = 1;
340         else
341                 lapseRepresentation = 0;
343         if(exposureTime >= 60)
344                 exposureRepresentation = 1;
345         else
346                 exposureRepresentation = 0;
347         
348         updateEncoder = 1;
351 void doEncoderA()
353         if(running || writing)
354                 return;
355                 
356         noInterrupts();
357         delayMicroseconds(3000); // maximum bounce time, accd. to spec.
359         if (digitalRead(encoderPinA) == HIGH)
360         { 
361                 if (digitalRead(encoderPinB) == LOW)
362                         incrementValue();
363                 else
364                         decrementValue();
365         }
366         else                                       
367         {
368                 if (digitalRead(encoderPinB) == HIGH)
369                         incrementValue();
370                 else
371                         decrementValue();
372         }
373         
374         updateTimeRepresentations();
375         
376         updateEncoder = 1;
377         interrupts();
380 void doEncoderB()
382         if(running || writing)
383                 return;
384         
385         noInterrupts();
386         delayMicroseconds(3000);
387         
388         if (digitalRead(encoderPinB) == HIGH)
389         {
390                 if (digitalRead(encoderPinA) == HIGH)
391                         incrementValue();
392                 else
393                         decrementValue();
394         }
395         else
396         {
397                 if (digitalRead(encoderPinA) == LOW)
398                         incrementValue();
399                 else
400                         decrementValue();
401         }
402         
403         updateTimeRepresentations();
404         
405         updateEncoder = 1;
406         interrupts();
409 void switchModes()
411         unsigned long diff = (millis() - lastModeUpdate);
412         
413         if(diff < 300) // careful about the overflow...
414                 return;
415         
416         lastModeUpdate = millis();
417         
418         currentMode++;
419         if(currentMode > 3)
420                 currentMode = 0;
421         
422         if(currentMode == BULB)
423                 selected = 1;
424         else
425                 selected = 0;
426         
427         if(currentMode == TRIGGER)
428                 digitalWrite(triggerReset, HIGH);
429         else
430                 digitalWrite(triggerReset, LOW);
431         
432         if(currentMode == TRIGGER)
433                 writeLED(200, -1, 0);
434         else if(currentMode == BULB)
435                 writeLED(0, -1, 128);
436         else if(currentMode == INTERVALBULB)
437                 writeLED(0, -1, 128);
438         else if(currentMode == INTERVAL)
439                 writeLED(0, -1, 128);
440                 
441         
442         updateHeader = 1;
445 void switchSelected()
447         if(currentMode == INTERVALBULB)
448         {
449                 unsigned long diff = (millis() - lastSelectedUpdate);
450                 
451                 if(diff < 300) // careful about the overflow...
452                         return;
453                         
454                 lastSelectedUpdate = millis();
456                 changeSelected = 1;
457         }
458         
459         updateEncoder = 1;
462 void toggleRunning()
464         unsigned long diff = (millis() - lastToggleRunning);
465         
466         if(diff < 300) // careful about the overflow...
467                 return;
468         
469         lastToggleRunning = millis();
470         
471         running = !running;
472         
473         if(running)
474         {
475                 writeLED(200, -1, -1);
476         }
477         else
478         {
479                 writeLED(0,-1,-1);
480                 if(currentMode == TRIGGER)
481                         writeLED(200, -1, -1);
482         }
483         
484         updateEncoder = 1;
487 void drawTimecode(int secs, int rep)
489         if(rep == 0)
490         {
491                 lcdNumberWrite(secs);
492                 lcdDataWrite('"');
493         }
494         else
495         {
496                 lcdNumberWrite(secs/60);
497                 lcdDataWrite('\'');
498         }
501 int timecodeLength(int secs, int rep)
503         int count = 2;
504         
505         if(rep)
506                 secs = secs/60;
507         
508         int n1 = secs/100;
509         int n2 = (secs - n1 * 100) / 10;
511         if(n2)
512                 count++;
513         
514         return count;
517 char *modeHeader[4] = {"Interval", "            Bulb", "Interval    Bulb", "Trigger"};
519 int fadeUp = 1;
521 void updateContrast()
523         write_pot(contrastPot, map(contrastValue, 0, 1000, 25, 60));
526 void writeLED(int r, int g, int b)
528         if(r > -1)
529                 write_pot(ledRPot, map(255-r, 0, 255, 15, 150));
530         if(g > -1)
531                 write_pot(ledGPot, map(255-g, 0, 255, 15, 150));
532         if(b > -1)
533                 write_pot(ledBPot, map(255-b, 0, 255, 15, 150));
536 void loop(void)
538         if(fadeUp)
539         {
540                 updateContrast();
541                 
542                 if((contrastValue -= 1) <= 0)
543                         fadeUp = 0;
544         }
545                 
546         if(updateHeader)
547         {
548                 lcdHome(0);
549                 
550                 int count;
551                 for (count = 0; modeHeader[currentMode][count] != 0; count++)
552                         lcdDataWrite(modeHeader[currentMode][count]);
553                 for (; count < 16; count++)
554                         lcdDataWrite(' ');
555                 
556                 updateHeader = 0;
557                 updateEncoder = 1;
558         }
559                 
560         if(updateEncoder)
561         {
562                 lcdHome(1);
563                 
564                 if(currentMode != BULB)
565                         drawTimecode(lapseTime, lapseRepresentation);
566                 
567                 int width = 0;
568                 
569                 if(currentMode != BULB)
570                         width += timecodeLength(lapseTime, lapseRepresentation);
571                 if(currentMode != INTERVAL && currentMode != TRIGGER)
572                         width += timecodeLength(exposureTime, exposureRepresentation);
573                         
574                 if(selected == 0)
575                 {
576                         lcdDataWrite(' '); width++;
577                         lcdDataWrite(127); width++;
578                 }
579                 else
580                 {
581                         width += 2;
582                 }
583                                 
584                 for(int i = 16; i > width; i--)
585                         lcdDataWrite(' ');
586                 
587                 if(selected == 1)
588                 {
589                         lcdDataWrite(126);
590                         lcdDataWrite(' ');
591                 }
592                 
593                 if(currentMode != INTERVAL && currentMode != TRIGGER)
594                         drawTimecode(exposureTime, exposureRepresentation);
595                 
596                 updateEncoder = 0;
597         }
598         
599         if(!digitalRead(buttonA))
600                 toggleRunning();
601         
602         if(running)
603         {
604                 digitalWrite(lcdPower, LOW);
605                 
606                 if(currentMode == TRIGGER)
607                 {
608                         if(analogRead(triggerInput) < 100) // 100 might change with different resistors, make sure it works!
609                         {
610                                 writeLED(-1, 128, -1);
611                                 digitalWrite(triggerReset, HIGH);
612                                 delay(100); // this should probably be at least the time of the delay from signal (in the 555)...
613                                 digitalWrite(triggerReset, LOW);
614                                 delay(10);
615                                 digitalWrite(triggerReset, HIGH);
616                                 writeLED(-1, 0, -1);
617                         }
618                         
619                         return;
620                 }
621                 
622                 unsigned long diff = millis() - lastShutter;
623                 
624                 int adjustedLapseTime = lapseTime;
625                 if(currentMode != INTERVAL)
626                         adjustedLapseTime -= exposureTime;
627                 
628                 if(diff > (adjustedLapseTime * 1000)) // careful about the overflow...
629                 {
630                         writeLED(-1, 128, -1);
631                         digitalWrite(cameraShutter, LOW);
632                         
633                         if(currentMode == INTERVAL)
634                                 delay(100);
635                         else
636                                 delay(exposureTime * 1000); //delay for length of exposure
637                                 // biggest problem with this is that you can't stop a bulb (of either type)
638                                 // in the middle... you have to power off the intervalometer; same as you would have 
639                                 // to do with a camera, I suppose, so people might be used to it.
640                                 // however, we can get around this by looping and checking millis()...
641                                 
642                         digitalWrite(cameraShutter, HIGH);
643                         lastShutter = millis();
644                         
645                         if(currentMode == BULB)
646                                 running = 0;
647                         
648                         writeLED(-1, 0, -1);
649                 }
650         }
651         else
652         {
653                 digitalWrite(lcdPower, HIGH);
654                 
655                 if(!digitalRead(buttonB))
656                         switchModes();
657                 
658                 if(!digitalRead(encoderButton))
659                         switchSelected();
660                 else if(changeSelected)
661                 {
662                         selected = !selected;
663                         changeSelected = 0;
664                         updateEncoder = 1;
665                 }
666         }