LED Camera

In the schematics page I mentioned the possibility to connect the common cathode of the LEDs to +5V. Here comes the reason why I introduced this into the design. This experiment shows how to turn LEDs into light sensors. Since the Blinkenlight Shield has 20 of them this turns the Blinkenlight Shield into a 20 pixel ā€žcameraā€œ.

Before we proceed change the jumper setup to connect the common cathode to 5V. It will not harm the LEDs. If you notice that the lights have gone out, great. That’s how it is supposed to happen. Just don’t forget to reset the jumper to the default setup after this experiment.

The initial observation is that LEDs act like photodiodes. But LEDs are optimized to emit light ā€“ they make crappy light detectors. That is the photo current will be very small. In addition the number of A/D pins is usually limited whereas we have lots of digital pins. Since (CMOS) digital input pins have extraordinary high input impedance there is another way which we will explore now. The idea is to reverse bias the diodes by switching the IO pins to output low. Then the LEDs will not conduct and behave like capacitors. Once charged we will switch the IO pins to high Z and wait what happens. If there is light there will be a very small photo current. This current will discharge the LED-capacitors. The more light the more current the faster. After discharging the capacitor long enough we can detect that the pin floated from low to high. Measuring the time to reach this transition indicates how much light reached the LED.

If you want to understand this technique in depth you may want to read this research paper http://www.merl.com/papers/docs/TR2003-35.pdf.

Here is the sketch

//
//	www.blinkenlight.net
//
//	Copyright 2011 Udo Klein
//
//	This program is free software: you can redistribute it and/or modify
//	it under the terms of the GNU General Public License as published by
//	the Free Software Foundation, either version 3 of the License, or
//	(at your option) any later version.
//
//	This program is distributed in the hope that it will be useful,
//	but WITHOUT ANY WARRANTY; without even the implied warranty of
//	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
//	GNU General Public License for more details.
//
//	You should have received a copy of the GNU General Public License
//	along with this program. If not, see http://www.gnu.org/licenses/


// Usage
//
// This sketch uses the Blinkenlight Shield as a light
// sensor. In order to make this work jumper the shield
// such the common cathode of the LEDs it connected
// to +5V.
//
// It will output hexadecimal digits that correspond
// to the amount of light captured by the LEDs.
// 0 = very bright light
// higher numbers = less light
//
//
// Theory of operation
//
// For each LED the following happens:

// 1) The PIN is pulled low thus reversing the LED.
//    Thus the LED will act like a capacitor and gets
//    charged.
// 2) We store the current milli second count in
//    start_millis for later use.
// 3) The PIN is put to high Z input and starts to
//    "float" with the voltage of the "LED cap".
// 4) If the LED captures light the "LED cap" will
//    discharge fast, otherwise it discharges slow.
// 5) As the cap discharges the input PIN will
//    float high.
// 6) Once the pin is detected to be high we will
//    compute elapsed_millis by subtracting
//    start_millis from the current milli second
//    count

// The loops are coded in such a way that this
// happens "in parallel". They are also coded
// in such a way that each pin gets some time
// to settle.


// used to store the start milli second count per pin
uint16_t start_millis[20];
// used to store the last computed milli second count when pin floated to high
uint16_t elapsed_millis[20];


uint8_t transform(uint16_t data) {
	// output transformation, used to map uint16_t to 1 hex digit
	// basically a logarithm to the base of 2
	uint8_t i=0;
	while (data) {
		data >>= 1;
		++i;
	}
	return i;
}

boolean pin_is_ok(uint8_t pin) {
	// used to determine which pins are good for light detection
	// pins 0,1 are spoiled by the serial port
	// pin 13 is spoiled by the Arduino's LED
	return (pin != 0) && (pin !=1) && (pin != 13);
}

void setup() {
	Serial.begin(115200);
	Serial.println("go");

	for (uint8_t pin = 0; pin < 20; ++pin) if (pin_is_ok(pin)) {
		pinMode(pin, OUTPUT);
		digitalWrite(pin, LOW);
		start_millis[pin] = millis();
		elapsed_millis[pin] = 0;
	}
	for (uint8_t pin = 0; pin < 20; ++pin) if (pin_is_ok(pin)) {
		pinMode(pin, INPUT);
	}
}

void loop() {
	for (uint8_t pin = 0; pin < 20; ++pin) if (pin_is_ok(pin)) {
		if (digitalRead(pin)) {
			pinMode(pin, OUTPUT);
			elapsed_millis[pin] = millis()-start_millis[pin];
			start_millis[pin] = millis();
			pinMode(pin, INPUT);
		}
	}
	for (uint8_t pin = 0; pin < 20; ++pin) if (pin_is_ok(pin)) {
		Serial.print(transform(elapsed_millis[pin]), HEX);
	}
	Serial.println();
}

The setup is a special case of the main loop. It sweeps over the pins and sets them to output 0V. Then it initializes the arrays to hold the reference milliseconds. After this the pins are set to input. Since they are already set to low this implies that the pullups will not be activated. We have now high Z input pins.
Notice that some pins are excluded. These pins are connected to the serial chip and the Arduino’s LED. They will not deliver any reasonable readings unless you have a board where these additional loads can be disconnected from the controller. In this case you may want to change the pin_is_ok function to return always true. All others must satisfy with a 17 pixel camera. Before you start to disconnect the serial connection don’t forget to think about how you want to transfer the data to your computer.

The main loop will now iterate the same thing over and over again. For each LED it will look if it has transitioned to high. If so it will pull it low by setting the pin to output. Then it computed the time since the last transition. Then it will put the pin into high Z mode again by setting it to input. Here I explicitly rely on the fact that pinMode commands are somewhat slow. Thus the LEDs will have quite some cycles for charging.
Once all LEDs are processed the results are pushed through the serial line. This must happen in a separate step in order to avoid jitter in the measurement timing.
You may wonder about the transform function. In theory and practice it can be ommited. I just introduced it to map the results into something that fits into 1 colum. Therefore I can use the serial monitor to visualize the output of this setup.

The video shows the “camera” in action. Unfortunately I did not notice that I had developed the sketch with the Arduino USB port to the right. So the output in the serial monitor is mirrored. Anyway the action is clearly visible.

12 Responses to LED Camera

  1. Pingback: Turning LEDs into a camera - Hack a Day

  2. Actually, your video shows the correct alignment for using a lens over the LED array, so you can image distant objects. šŸ˜‰

  3. davedarko says:

    Larson scanner ftw.

  4. I don’t even know how I ended up here, but I thought this post was
    great. I don’t know who you are but certainly you are going to a famous blogger if you
    are not already šŸ˜‰ Cheers!

  5. I made an async LED sensor library here. It supports the same functionality but wraps it up in a class with instances for LEDs. https://github.com/canadaduane/LEDSensor

  6. atroc says:

    Damn, this is nice!

  7. Phoenix Williams says:

    Another fun and related project is to use an RGB LED alternately as a light sensor and indicator, the colour indicating the previously-sampled light level, with the rate fast enough where LED appears to be on continuously.

  8. Mahesh Viswanathan says:

    your original example at http://playground.arduino.cc/Learning/LEDSensor is very elegant and works. i have a bike light with very bright bluish-white LEDs that have zero output in reverse bias. have you come across anything like this? red and green LEDs work fine.

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.