Accurate LED Timing Circuit for Measuring Camera Response

LED timer eagle to board
A friend wanted to measure the linearity of his CCD camera, and asked for my help. He needed to measure linearity to 0.1% and wanted to do so by exposing the CCD sensor to a constant light source for varying time intervals. To decrease the effect of dark current, the smallest time interval needed to be extremely short, shorter than the shutter of the camera could achieve. Instead he asked if I could design a circuit to accurately turn on and off an LED, allowing the CCD sensor to be exposed for a much shorter interval than the shutter is capable of.

To measure the camera response to 0.1%, a range of 1000:1 is needed. This requires at least a 10 bit counter (2^10 = 1024). A constant current source is required to drive the LED so that its luminosity is constant. With those specifications in mind, I came up with a design.

Parts:

Schematic and Board:

This was my first time using Eagle, but thanks to Sparkfun’s tutorials it was easy. Links to Eagle files are at the end of the post. I used DorkbotPDX’s PCB service to order the boards. The price was amazing, only $10.85 (that includes shipping!) for three double-sided 1.6″x1.4″ PCBs.
LED timer schematic
board front board back
board populated front board populated back

Code:

/*Attiny2313 LED timer
 *V1.0
 *Jordan Horwich
 */

#include <avr/io.h>
#include <avr/interrupt.h>

uint16_t initialDelay = 0xffff; //initial delay before LED turns on. Used for convenience.
volatile int var = 0;
uint16_t top = 0x0000;

uint8_t input = 0x00;

uint8_t i;
uint16_t k;

int main(void)
{
  cli(); //disable global interrupts
  TCCR1B |= 1<<CS11 | 1<<CS10; //divide clock by 64
  TCCR1B |= 1<<WGM12; //put Timer/Counter1 in CTC mode
  OCR1A = initialDelay; //initial delay, default 2^16 cycles, or ~4 seconds
  TIMSK |= 1<<OCIE1A; //enable timer compare interrupt

  DDRD |= (1<<4); //set PortD Pin4 as an output
  PORTB |= (1<<7)|(1<<6)|(1<<5)|(1<<4)|(1<<3)|(1<<2)|(1<<1)|(1<<0); //enable internal pull-up resistor on DIP switch inputs
  PORTD |= (1<<6)|(1<<5); //enable internal pull-up resistor on DIP switch inputs

  //read and store DIP switch values
  i = ~PINB;
  k = (i<<2);
  if((PIND & _BV(PD6)) == 0) {
    k |= (1<<1);
  }
  if((PIND & _BV(PD5)) == 0) {
    k |= (1<<0);
  }
  top |= (k<<6); //scales 16-bits to 10-bits by shifting k 6 places left

  sei(); //enable global interupts

  while(1) {
  }
}

ISR(TIMER1_COMPA_vect) //Interrupt Service Routine
{
  PORTD ^= (1<<4); //xor toggles LED
  OCR1A = top;
  if(var == 1) {
    TCCR1B &= ~0x07;
  }
  else {
    var++;
  }
}

 
The 10 position DIP switch is used to input the desired LED on-time in binary. Each position represents a bit with bit-0 on the far right closest to the power jack. The current is set from 1mA to 500mA by adjusting the trimpot. As the code only reads the DIP switch once when the ATTiny first powers on, DIP switch is changed and then the reset button is pressed to change the time interval. This becomes very tedious if you need to cycle through all 1024 values. The code could be rewritten so that the time interval value is stored in the EEPROM and is automatically incremented after every reset. The ATTiny’s clocks and counters are set such that the shortest time interval is 0.004096 seconds, and the longest time interval is 4.194304 seconds (0.004096 * 1024).

Results:


The first interval tested in the video is 1111111111, or about 4.19 seconds. The next is 01111111 (~2.10 seconds), then 0011111111 (~1.05 seconds), then 0001111111 (~0.52 seconds), and I think you can figure out the rest.

Files:

See more pictures of this project on flickr.

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>