
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:
- ATTiny2313 (slightly overkill)
- LM317 voltage regulator (acting as constant current source)
- LM340 5V regulator
- 2N3904 NPN transistor
- 1KΩ 25 turn trimpot
- 10KΩ resistor
- 0.1μF cap
- 0.22μF cap
- 10 position DIP switch
- DC power jack
- 2 position screw terminal
- 3 x 2 header
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.



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.