Add numbers file of parts/prices.
[arduino-intervalometer.git] / Intervalometer.pde
bloba18070bff7b552ea70dffd8ade4d6e028e06c2a0
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 // Hour representation?
17 // Either need to support more digits, or arbitrarily limit durations
18 // Test battery life - provide timeouts for display backlight? can atmega /sleep/?
19 // Fade up backlight/contrast on init!
22  *  Pin Assignment
23  */
25 // Analog Pins
27 int triggerInput =  2; // Feedback for trigger reset
29 int encoderButton =     3; // Encoder pushbutton
30 int buttonA =           4; // Left pushbutton
31 int buttonB =           5; // Right pushbutton
33 // Digital Pins
35 int cameraShutter = 0; // Pulse low for shutter release
37 int encoderPinA =       2; // HW interrupt
38 int encoderPinB =       3; // HW interrupt
40 int lcdEnable =         1;
41 int lcdDataBus[] =      { 4, 5, 6, 7 };
42 int lcdDataClock =      8;
43 int lcdDataInt =        9;
45 int triggerReset =  12;
47 int potSelect =         10; // SPI (SS) for digital pots
48 int potData =           11; // SPI (MOSI) for digital pots
49 int potClock =          13; // SPI (SCK) for digital pots
51 /////////////////////////////
53 int currentMode = INTERVAL, selected = 0;
55 // Debouncing time variables
56 unsigned long lastToggleRunning = 0;
57 unsigned long lastModeUpdate = 0;
58 unsigned long lastSelectedUpdate = 0;
60 unsigned long lastShutter = 0;
62 int updateHeader = 1, updateEncoder = 1;
63 int running = 0;
65 // Exposure and Timelapse durations
66 volatile long exposureTime = 0, lapseTime = 0;
67 int exposureRepresentation = 0, lapseRepresentation = 0;
70  *  Higher level hardware functions
71  */
73 void pulsePin(int pin, int value)
75         digitalWrite(pin, !value);
76         delay(1);
77         digitalWrite(pin, value);
78         delay(1);
79         digitalWrite(pin, !value);
80         delay(1);
83 void parallelShiftOut(int firstPin, int lastPin, int value)
85         int i;
86         for(i = firstPin; i <= lastPin; i++)
87         {
88                 digitalWrite(i, value & 0x01);
89                 value >>= 1;
90         }
94  *  LCD Control Functions
95  */
97 void lcdCommandWrite(int value)
99         int i = 0;
100         int value1 = value >> 4;
101         
102         parallelShiftOut(lcdDataBus[0], lcdDataInt, value1);
103         
104         pulsePin(lcdEnable, HIGH);
106         parallelShiftOut(lcdDataBus[0], lcdDataBus[3], value);
107         
108         value >>= 4;
109         parallelShiftOut(lcdDataClock, lcdDataInt, value);
110         
111         pulsePin(lcdEnable, HIGH);
114 void lcdDataWrite(int value)
116         int i = 0;
117         int value1 = value >> 4;
118         
119         digitalWrite(lcdDataInt, HIGH);
120         digitalWrite(lcdDataClock, LOW);
121         
122         parallelShiftOut(lcdDataBus[0], lcdDataBus[3], value1);
123         
124         pulsePin(lcdEnable, HIGH);
125         
126         digitalWrite(lcdDataInt, HIGH);
127         digitalWrite(lcdDataClock, LOW);
128         
129         parallelShiftOut(lcdDataBus[0], lcdDataBus[3], value);
130         
131         pulsePin(lcdEnable, HIGH);
134 void lcdNumberWrite(int nr)
136         int n1 = nr/100;
137         int n2 = (nr - n1 * 100) / 10;
138         
139         if(n2)
140                 lcdCommandWrite(560 + n2);
141         
142         nr = nr - n1 * 100 - n2 * 10;
143         lcdCommandWrite(560 + nr);
146 void lcdHome(int row)
148         lcdCommandWrite(0x02);
149         
150         delay(4);
151         
152         if(row == 1)
153                 lcdCommandWrite(0xC0);
154                 
155         delay(1);
158 void lcdClear()
160         lcdCommandWrite(0x01);
161         delay(4);
165  *  Hardware initialization functions
166  */
168 void lcdInit()
170         int i = 0;
171         
172         for (i = lcdEnable; i <= lcdDataInt; i++)
173                 pinMode(i, OUTPUT);
174         
175         // there's a chance that when we drop to not running 
176         // on top of the arduino bootloader, we'll need a somewhat
177         // significant delay here (called for in the spec, but the bl is slow)
179         lcdCommandWrite(0x03); delay(64);
180         lcdCommandWrite(0x03); delay(50);
181         lcdCommandWrite(0x03); delay(50);
182         lcdCommandWrite(0x02); delay(50);
183         lcdCommandWrite(0x2C); delay(20);
184         lcdCommandWrite(0x06); delay(20);
185         lcdCommandWrite(0x0C); delay(20);
186         lcdCommandWrite(0x01); delay(100);
187         lcdCommandWrite(0x80); delay(30);
190 void encoderInit()
192         pinMode(encoderPinA, INPUT); 
193         digitalWrite(encoderPinA, HIGH);
194         pinMode(encoderPinB, INPUT); 
195         digitalWrite(encoderPinB, HIGH);
197         attachInterrupt(0, doEncoderA, CHANGE);
198         attachInterrupt(1, doEncoderB, CHANGE);
201 void setup (void)
203         pinMode(cameraShutter, OUTPUT);
204         digitalWrite(cameraShutter, HIGH);
205         
206         pinMode(triggerReset, OUTPUT);
207         // keep the reset low except when in trigger mode, so we don't accidentally trigger!
208         digitalWrite(triggerReset, LOW);
210         lcdInit();
211         encoderInit();
214 void incrementValue()
216         if(selected == 0)
217         {
218                 if(lapseRepresentation == 0)
219                         lapseTime++;
220                 else
221                         lapseTime += 60;
222         }
223         else
224         {
225                 if(exposureRepresentation == 0)
226                         exposureTime++;
227                 else
228                         exposureTime += 60;
229                 
230                 if(exposureTime > lapseTime)
231                         lapseTime = exposureTime;
232         }
235 void decrementValue()
237         if(selected == 0)
238         {
239                 if(lapseRepresentation == 0 || lapseTime == 60) // careful around transition; thanks, nate
240                         lapseTime--;
241                 else
242                         lapseTime -= 60;
243                 
244                 if(lapseTime < 0)
245                         lapseTime = 0;
246         }
247         else
248         {
249                 if(exposureRepresentation == 0 || exposureTime == 60)
250                         exposureTime--;
251                 else
252                         exposureTime -= 60;
253                 
254                 if(exposureTime < 0)
255                         exposureTime = 0;
256         }
259 void updateTimeRepresentations()
261         if(lapseTime >= 60)
262                 lapseRepresentation = 1;
263         else
264                 lapseRepresentation = 0;
266         if(exposureTime >= 60)
267                 exposureRepresentation = 1;
268         else
269                 exposureRepresentation = 0;
270         
271         updateEncoder = 1;
274 void doEncoderA()
276         if(running)
277                 return;
278                 
279         noInterrupts();
280         delayMicroseconds(3000); // maximum bounce time, accd. to spec.
282         if (digitalRead(encoderPinA) == HIGH)
283         { 
284                 if (digitalRead(encoderPinB) == LOW)
285                         incrementValue();
286                 else
287                         decrementValue();
288         }
289         else                                       
290         {
291                 if (digitalRead(encoderPinB) == HIGH)
292                         incrementValue();
293                 else
294                         decrementValue();
295         }
296         
297         updateTimeRepresentations();
298         
299         updateEncoder = 1;
300         interrupts();
303 void doEncoderB()
305         if(running)
306                 return;
307         
308         noInterrupts();
309         delayMicroseconds(3000);
310         if (digitalRead(encoderPinB) == HIGH)
311         {
312                 if (digitalRead(encoderPinA) == HIGH)
313                         incrementValue();
314                 else
315                         decrementValue();
316         }
317         else
318         {
319                 if (digitalRead(encoderPinA) == LOW)
320                         incrementValue();
321                 else
322                         decrementValue();
323         }
324         
325         updateTimeRepresentations();
326         
327         updateEncoder = 1;
328         interrupts();
331 void switchModes()
333         unsigned long diff = (millis() - lastModeUpdate);
334         
335         if(diff < 300) // careful about the overflow...
336         {
337                 lastModeUpdate = millis();
338                 return;
339         }
340         else
341                 lastModeUpdate = millis();
342         
343         currentMode++;
344         if(currentMode > 3)
345                 currentMode = 0;
346         
347         if(currentMode == BULB)
348                 selected = 1;
349         else
350                 selected = 0;
351         
352         if(currentMode == TRIGGER)
353                 digitalWrite(triggerReset, HIGH);
354         else
355                 digitalWrite(triggerReset, LOW);
356                 
357         
358         updateHeader = 1;
361 void switchSelected()
363         if(currentMode == INTERVALBULB)
364         {
365                 unsigned long diff = (millis() - lastSelectedUpdate);
366                 
367                 if(diff < 300) // careful about the overflow...
368                 {
369                         lastSelectedUpdate = millis();
370                         return;
371                 }
372                 else
373                         lastSelectedUpdate = millis();
375                 selected = !selected;
376         }
377         
378         updateEncoder = 1;
381 char runningIndicators[2] = { '*', '+' };
382 char oldRunningIndicator;
384 void updateRunningIndicator()
386         char i = ((millis() / 250) % 2);
387         
388         if(i == oldRunningIndicator)
389                 return;
390         
391         lcdHome(1);
392         lcdCommandWrite(0xC8);
393         
394         if(running)
395                 lcdDataWrite(runningIndicators[i]);
396         else
397                 lcdDataWrite(' ');
398         
399         oldRunningIndicator = i;
402 void toggleRunning()
404         unsigned long diff = (millis() - lastToggleRunning);
405         
406         if(diff < 300) // careful about the overflow...
407         {
408                 lastToggleRunning = millis();
409                 return;
410         }
411         else
412                 lastToggleRunning = millis();
413         
414         running = !running;
415         
416         updateEncoder = 1;
419 void drawTimecode(int secs, int rep)
421         if(rep == 0)
422         {
423                 lcdNumberWrite(secs);
424                 lcdDataWrite('"');
425         }
426         else
427         {
428                 lcdNumberWrite(secs/60);
429                 lcdDataWrite('\'');
430         }
433 int timecodeLength(int secs, int rep)
435         int count = 2;
436         
437         if(rep)
438                 secs = secs/60;
439         
440         int n1 = secs/100;
441         int n2 = (secs - n1 * 100) / 10;
443         if(n2)
444                 count++;
445         
446         return count;
449 char *modeHeader[4] = {"Interval", "            Bulb", "Interval    Bulb", "Trigger"};
451 void loop(void)
453         if(updateHeader)
454         {
455                 lcdHome(0);
456                 
457                 int count;
458                 for (count = 0; modeHeader[currentMode][count] != 0; count++)
459                         lcdDataWrite(modeHeader[currentMode][count]);
460                 for (; count < 16; count++)
461                         lcdDataWrite(' ');
462                 
463                 updateHeader = 0;
464                 updateEncoder = 1;
465         }
466                 
467         if(updateEncoder)
468         {       
469                 lcdHome(1);
470                 
471                 if(currentMode != BULB)
472                         drawTimecode(lapseTime, lapseRepresentation);
473                 
474                 int width = 0;
475                 
476                 if(currentMode != BULB)
477                         width += timecodeLength(lapseTime, lapseRepresentation);
478                 if(currentMode != INTERVAL && currentMode != TRIGGER)
479                         width += timecodeLength(exposureTime, exposureRepresentation);
480                         
481                 if(selected == 0)
482                 {
483                         lcdDataWrite(' '); width++;
484                         lcdDataWrite(127); width++;
485                 }
486                 else
487                 {
488                         width += 2;
489                 }
490                                 
491                 for(int i = 16; i > width; i--)
492                         lcdDataWrite(' ');
493                 
494                 if(selected == 1)
495                 {
496                         lcdDataWrite(126);
497                         lcdDataWrite(' ');
498                 }
499                 
500                 if(currentMode != INTERVAL && currentMode != TRIGGER)
501                         drawTimecode(exposureTime, exposureRepresentation);
503                 updateRunningIndicator();
504                 
505                 updateEncoder = 0;
506         }
507         
508         if(analogRead(buttonA) == 0)
509                 toggleRunning();
510         
511         if(running)
512         {
513                 updateRunningIndicator();
514                 
515                 if(currentMode == TRIGGER)
516                 {
517                         if(analogRead(triggerInput) < 100) // 100 might change with different resistors, make sure it works!
518                         {
519                                 digitalWrite(triggerReset, HIGH);
520                                 delay(100); // this should probably be at least the time of the delay from signal (in the 555)...
521                                 digitalWrite(triggerReset, LOW);
522                                 delay(10);
523                                 digitalWrite(triggerReset, HIGH);
524                         }
525                         
526                         return;
527                 }
528                 
529                 unsigned long diff = millis() - lastShutter;
530                 
531                 int adjustedLapseTime = lapseTime;
532                 if(currentMode != INTERVAL)
533                         adjustedLapseTime -= exposureTime;
534                 
535                 if(diff > (adjustedLapseTime * 1000)) // careful about the overflow...
536                 {
537                         digitalWrite(cameraShutter, LOW);
538                         
539                         if(currentMode == INTERVAL)
540                                 delay(100);
541                         else
542                                 delay(exposureTime * 1000); //delay for length of exposure
543                                 // biggest problem with this is that you can't stop a bulb (of either type)
544                                 // in the middle... you have to power off the intervalometer; same as you would have 
545                                 // to do with a camera, I suppose, so people might be used to it.
546                                 // however, we can get around this by looping and checking millis()...
547                                 
548                         digitalWrite(cameraShutter, HIGH);
549                         lastShutter = millis();
550                         
551                         if(currentMode == BULB)
552                                 running = 0;
553                 }
554         }
555         else
556         {
557                 if(analogRead(buttonB) == 0)
558                         switchModes();
559                 
560                 if(analogRead(encoderButton) == 0)
561                         switchSelected();
562                 
563                 updateRunningIndicator();
564         }