Broke something!
[arduino-intervalometer.git] / Intervalometer.pde
blob4bf1edee19e2d45882085d2acacd1da643816b90
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 =  16; // 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 int updateHeader = 1, updateEncoder = 1, running = 0;
66 // Exposure and Timelapse durations
67 volatile long exposureTime = 0, lapseTime = 0;
68 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;                    // Start the transmission
101         while (!(SPSR & (1<<SPIF)))     // Wait the end of the transmission
102         {
103         };
104         return SPDR;                    // return the received byte
107 void digitalPotInit()
109         byte i;
110         byte clr;
111         pinMode(potData, OUTPUT);
112         pinMode(potClock,OUTPUT);
113         pinMode(potSelect,OUTPUT);
114         digitalWrite(potSelect,HIGH);
115         SPCR = (1<<SPE)|(1<<MSTR);
116         clr=SPSR;
117         clr=SPDR;
118         delay(10);
119         for (i=0;i<6;i++)
120                 write_pot(i,255);
123 byte write_pot(int address, int value)
125         digitalWrite(potSelect,LOW);
126         //2 byte opcode
127         spi_transfer(address);
128         spi_transfer(value);
129         digitalWrite(potSelect,HIGH); //release chip, signal end transfer
133  *  LCD Control Functions
134  */
136 void lcdDataWrite(byte a)
138         writing = 1;
139         shiftOut(lcdData, lcdClock, LSBFIRST, 0x20 + ((a >> 4) & 0xF));
140         digitalWrite(lcdEnable, HIGH);
141         delayMicroseconds(1);
142         digitalWrite(lcdEnable, LOW);
144         delay(1);
146         shiftOut(lcdData, lcdClock, LSBFIRST, 0x20 + (a & 0xF));
147         digitalWrite(lcdEnable, HIGH);
148         delayMicroseconds(1);
149         digitalWrite(lcdEnable, LOW);
150         
151         delay(1);
152         writing = 0;
155 void lcdCommandWrite(int a)
157         writing = 1;
158         shiftOut(lcdData, lcdClock, LSBFIRST, (a >> 4) & 0xF);
159         digitalWrite(lcdEnable, HIGH);
160         delayMicroseconds(1);
161         digitalWrite(lcdEnable, LOW);
163         delay(1);
165         shiftOut(lcdData, lcdClock, LSBFIRST, a & 0xF);
166         digitalWrite(lcdEnable, HIGH);
167         delayMicroseconds(1);
168         digitalWrite(lcdEnable, LOW);
169         
170         delay(1);
171         writing = 0;
174 void lcdNumberWrite(int nr)
176         int n1 = nr/100;
177         int n2 = (nr - n1 * 100) / 10;
178         
179         if(n2)
180                 lcdDataWrite('0' + n2);
181         
182         nr = nr - n1 * 100 - n2 * 10;
183         lcdDataWrite('0' + nr);
186 void lcdHome(int row)
188         lcdCommandWrite(0x02);
189         
190         delay(4);
191         
192         if(row == 1)
193                 lcdCommandWrite(0xC0);
194                 
195         delay(1);
198 void lcdClear()
200         lcdCommandWrite(0x01);
201         delay(4);
205  *  Hardware initialization functions
206  */
208 void lcdInit()
210         int i = 0;
211         
212         pinMode(lcdEnable, OUTPUT);
213         pinMode(lcdData, OUTPUT);
214         pinMode(lcdClock, OUTPUT);
215         
216         digitalWrite(lcdClock,LOW);
217         digitalWrite(lcdData,LOW);
218         digitalWrite(lcdEnable,LOW);
219         
220         // there's a chance that when we drop to not running 
221         // on top of the arduino bootloader, we'll need a somewhat
222         // significant delay here (called for in the spec, but the bl is slow)
224         lcdCommandWrite(0x03); delay(5);
225         lcdCommandWrite(0x03); delay(1);
226         lcdCommandWrite(0x03); delay(1);        
227         lcdCommandWrite(0x02); delay(4);
228         lcdCommandWrite(0x06); delay(1);
229         lcdCommandWrite(0x0C); delay(1);
230         lcdCommandWrite(0x01); delay(4);
231         lcdCommandWrite(0x80); delay(1);
232         
233         pinMode(lcdPower, OUTPUT);
234         digitalWrite(lcdPower, LOW);
237 void encoderInit()
239         pinMode(encoderPinA, INPUT); 
240         digitalWrite(encoderPinA, HIGH);
241         pinMode(encoderPinB, INPUT); 
242         digitalWrite(encoderPinB, HIGH);
244         attachInterrupt(0, doEncoderA, CHANGE);
245         attachInterrupt(1, doEncoderB, CHANGE);
248 void setup (void)
250         pinMode(cameraShutter, OUTPUT);
251         digitalWrite(cameraShutter, HIGH);
252         
253         pinMode(triggerReset, OUTPUT);
254         // keep the reset high except when in trigger mode, so we don't accidentally trigger!
255         digitalWrite(triggerReset, LOW);
257         digitalPotInit();
258         lcdInit();
259         encoderInit();
262 void incrementValue()
264         if(selected == 0)
265         {
266                 if(lapseRepresentation == 0)
267                         lapseTime++;
268                 else
269                         lapseTime += 60;
270         }
271         else
272         {
273                 if(exposureRepresentation == 0)
274                         exposureTime++;
275                 else
276                         exposureTime += 60;
277                 
278                 if(exposureTime > lapseTime)
279                         lapseTime = exposureTime;
280         }
283 void decrementValue()
285         if(selected == 0)
286         {
287                 if(lapseRepresentation == 0 || lapseTime == 60) // careful around transition; thanks, nate
288                         lapseTime--;
289                 else
290                         lapseTime -= 60;
291                 
292                 if(lapseTime < 0)
293                         lapseTime = 0;
294         }
295         else
296         {
297                 if(exposureRepresentation == 0 || exposureTime == 60)
298                         exposureTime--;
299                 else
300                         exposureTime -= 60;
301                 
302                 if(exposureTime < 0)
303                         exposureTime = 0;
304         }
307 void updateTimeRepresentations()
309         if(lapseTime >= 60)
310                 lapseRepresentation = 1;
311         else
312                 lapseRepresentation = 0;
314         if(exposureTime >= 60)
315                 exposureRepresentation = 1;
316         else
317                 exposureRepresentation = 0;
318         
319         updateEncoder = 1;
322 void doEncoderA()
324 //      if(running)
325 //              return;
326                 
327         noInterrupts();
328         delayMicroseconds(3000); // maximum bounce time, accd. to spec.
330         if (digitalRead(encoderPinA) == HIGH)
331         { 
332                 if (digitalRead(encoderPinB) == LOW)
333                         incrementValue();
334                 else
335                         decrementValue();
336         }
337         else                                       
338         {
339                 if (digitalRead(encoderPinB) == HIGH)
340                         incrementValue();
341                 else
342                         decrementValue();
343         }
344         
345         updateTimeRepresentations();
346         
347         updateEncoder = 1;
348         interrupts();
351 void doEncoderB()
353 //      if(running)
354 //              return;
355         
356         noInterrupts();
357         delayMicroseconds(3000);
358         if (digitalRead(encoderPinB) == HIGH)
359         {
360                 if (digitalRead(encoderPinA) == HIGH)
361                         incrementValue();
362                 else
363                         decrementValue();
364         }
365         else
366         {
367                 if (digitalRead(encoderPinA) == LOW)
368                         incrementValue();
369                 else
370                         decrementValue();
371         }
372         
373         updateTimeRepresentations();
374         
375         updateEncoder = 1;
376         interrupts();
379 void switchModes()
381         unsigned long diff = (millis() - lastModeUpdate);
382         
383         if(diff < 300) // careful about the overflow...
384         {
385                 lastModeUpdate = millis();
386                 return;
387         }
388         else
389                 lastModeUpdate = millis();
390         
391         currentMode++;
392         if(currentMode > 3)
393                 currentMode = 0;
394         
395         if(currentMode == BULB)
396                 selected = 1;
397         else
398                 selected = 0;
399         
400         if(currentMode == TRIGGER)
401                 digitalWrite(triggerReset, HIGH);
402         else
403                 digitalWrite(triggerReset, LOW);
404                 
405         
406         updateHeader = 1;
409 void switchSelected()
411         if(currentMode == INTERVALBULB)
412         {
413                 unsigned long diff = (millis() - lastSelectedUpdate);
414                 
415                 if(diff < 300) // careful about the overflow...
416                 {
417                         lastSelectedUpdate = millis();
418                         return;
419                 }
420                 else
421                         lastSelectedUpdate = millis();
423                 selected = !selected;
424         }
425         
426         updateEncoder = 1;
429 void toggleRunning()
431         unsigned long diff = (millis() - lastToggleRunning);
432         
433         if(diff < 300) // careful about the overflow...
434         {
435                 lastToggleRunning = millis();
436                 return;
437         }
438         else
439                 lastToggleRunning = millis();
440         
441         running = !running;
442         
443         updateEncoder = 1;
446 void drawTimecode(int secs, int rep)
448         if(rep == 0)
449         {
450                 lcdNumberWrite(secs);
451                 lcdDataWrite('"');
452         }
453         else
454         {
455                 lcdNumberWrite(secs/60);
456                 lcdDataWrite('\'');
457         }
460 int timecodeLength(int secs, int rep)
462         int count = 2;
463         
464         if(rep)
465                 secs = secs/60;
466         
467         int n1 = secs/100;
468         int n2 = (secs - n1 * 100) / 10;
470         if(n2)
471                 count++;
472         
473         return count;
476 char *modeHeader[4] = {"Interval", "            Bulb", "Interval    Bulb", "Trigger"};
478 int pot = 765;
479 int fadeUp = 1;
481 void loop(void)
483         write_pot(3, 20);
484         write_pot(1, 255);
485         write_pot(0, 255);
486         
487         if(fadeUp)
488                 write_pot(5,--pot / 3.0);
489         if(pot == 50)
490                 fadeUp = 0;
491                 
492         write_pot(4,0);
493         
494         if(updateHeader)
495         {
496                 lcdHome(0);
497                 
498                 int count;
499                 for (count = 0; modeHeader[currentMode][count] != 0; count++)
500                         lcdDataWrite(modeHeader[currentMode][count]);
501                 for (; count < 16; count++)
502                         lcdDataWrite(' ');
503                 
504                 updateHeader = 0;
505                 updateEncoder = 1;
506         }
507                 
508         if(updateEncoder)
509         {
510                 lcdHome(1);
511                 
512                 if(currentMode != BULB)
513                         drawTimecode(lapseTime, lapseRepresentation);
514                 
515                 int width = 0;
516                 
517                 if(currentMode != BULB)
518                         width += timecodeLength(lapseTime, lapseRepresentation);
519                 if(currentMode != INTERVAL && currentMode != TRIGGER)
520                         width += timecodeLength(exposureTime, exposureRepresentation);
521                         
522                 if(selected == 0)
523                 {
524                         lcdDataWrite(' '); width++;
525                         lcdDataWrite(127); width++;
526                 }
527                 else
528                 {
529                         width += 2;
530                 }
531                                 
532                 for(int i = 16; i > width; i--)
533                         lcdDataWrite(' ');
534                 
535                 if(selected == 1)
536                 {
537                         lcdDataWrite(126);
538                         lcdDataWrite(' ');
539                 }
540                 
541                 if(currentMode != INTERVAL && currentMode != TRIGGER)
542                         drawTimecode(exposureTime, exposureRepresentation);
543                 
544                 updateEncoder = 0;
545         }
546         
547         //if(!digitalRead(buttonA))
548         //      toggleRunning();
549         
550         if(running)
551         {
552                 digitalWrite(lcdPower, HIGH);
553                 
554                 if(currentMode == TRIGGER)
555                 {
556                         if(!digitalRead(triggerInput)) // 100 might change with different resistors, make sure it works!
557                         {
558                                 digitalWrite(triggerReset, HIGH);
559                                 delay(100); // this should probably be at least the time of the delay from signal (in the 555)...
560                                 digitalWrite(triggerReset, LOW);
561                                 delay(10);
562                                 digitalWrite(triggerReset, HIGH);
563                         }
564                         
565                         return;
566                 }
567                 
568                 unsigned long diff = millis() - lastShutter;
569                 
570                 int adjustedLapseTime = lapseTime;
571                 if(currentMode != INTERVAL)
572                         adjustedLapseTime -= exposureTime;
573                 
574                 if(diff > (adjustedLapseTime * 1000)) // careful about the overflow...
575                 {
576                         digitalWrite(cameraShutter, LOW);
577                         
578                         if(currentMode == INTERVAL)
579                                 delay(100);
580                         else
581                                 delay(exposureTime * 1000); //delay for length of exposure
582                                 // biggest problem with this is that you can't stop a bulb (of either type)
583                                 // in the middle... you have to power off the intervalometer; same as you would have 
584                                 // to do with a camera, I suppose, so people might be used to it.
585                                 // however, we can get around this by looping and checking millis()...
586                                 
587                         digitalWrite(cameraShutter, HIGH);
588                         lastShutter = millis();
589                         
590                         if(currentMode == BULB)
591                                 running = 0;
592                 }
593         }
594         else
595         {
596                 digitalWrite(lcdPower, LOW);
597                 
598                 //if(!digitalRead(buttonB))
599                 //      switchModes();
600                 
601                 //if(!digitalRead(encoderButton))
602                 //      switchSelected();
603         }