LEDs as Photo-diodes

Do a search for Forest M. Mims.

He has published a number of articles on using standard leds as photo diodes. I have used a reverse biased green led connected to an osciliscope to measure the duration of a photographic flash unit like this

9V
^
|
LED (reverse biased)
|
+--- Oscilliscope
|
1K resistor
|
Gnd

They almost certainly weren't LEDs.

They we're you know.
I have a page on it, in the workshop section
http://www.thebox.myzen.co.uk/Workshop/LED_Sensing.html

I stand corrected. Finally a use for reverse biased LEDs!

Reversed biased leds do work as photo-diodes. Find below code that provides a solution to controlling leds from a led light detector. This can be a multilevel dark detector too!

/* LED_LIGHT_CONTROL_IBLE
 * -----------------------
 * This program uses separate led's for light and sensing light.
 * Works with room ambient or daylight.
 * LED light sensing modified from Hunter's Instructible. Acheives sharp
 * cut off and on according to "int light = X". Could also fade lights
 * in/out on analog pins.
 *
 * Hunter Carlson
 * June 9 2009
 * Edits added Nov. 9, 2012 -fabelizer
 *
 * Requires at least 2 (or 3 or more) LEDs, one to analog pins 4 & 5, other
 * to D8 & GND through a resistor. I used a water clear superbright
 * red led for the sensor led, white for led1. 
 * See comments below for polarities and connections.
 */

int sense01 = 5;        // sensing LED anode connected to analog pin5
int sense02 = 4;        // sensing LED cathode connected to analog pin4
                        // you can switch out different leds on these
                        // pins to see which works best. Run the
                        // serial monitor and watch the changes.
                                                
int LED01 = 8;          // LED anode to dig pin8, cathode to 220R
                        // 220R to GND.
int LED02 = 9;          // LED anode to dig pin9, cathode to 220R
                        // 220R to GND.
                       
int val01 = 0;          // variable to store the value read from sense01
int val02 = 0;          // variable to store the value read from sense02

int light1 = 110;       // set light threshold for led1
int light2 = 80;        // set light threshold for led2

void setup()
{
  Serial.begin(9600);        // setup serial (comment out to save memory)
  pinMode(LED01, OUTPUT);    // led1 pin set to output
  pinMode(LED02, OUTPUT);    // led2 pin set to output
}

void loop()
{
  val01 = analogRead(sense01);      // read sense01 led
  val02 = analogRead(sense02);      // read sense02 led
  
   //debug print
  Serial.print(val01              // comment this section out to save memory
  Serial.println(val01-val02);    // result is printed and compared with light1 or 2 
  Serial.println();               // blank line between values
  
                                    // first led 'led1'
 if ((val01 - val02) >= light1) {   // check if light in area
      digitalWrite(LED01, LOW);     // if light enough, turn off led1
    } else {                      
      digitalWrite(LED01, HIGH);    // if dark enough, turn on led1
    }
    
                                    // second led 'led2'
 if ((val01 - val02) >= light2) {   // check if light in area
      digitalWrite(LED02, LOW);     // if light enough, turn off led2
    } else {                      
      digitalWrite(LED02, HIGH);    // if dark enough, turn on led2
    }
    
delay(100);                         // just to slow things down a bit

}

-fab

My page offers explanations plus links to the original research: LED Camera | Blinkenlight

You do not even need to reverse bias them. No matter in which direction you bypass them.
The reverse bypassing is only needed if you do not have an ADC but only digital inputs. With ADC inputs you can measure and find out that they work as photodiodes no matter how you bypass them.

Folks at Mistubishi started this. Google their article.

Keywords are Mitsubishi and iDropper.

Heres an example of somone using an arduino, and LEDs as both lights, and sensors. The top corner LEDs are also sensors to control what the cube does.

There is an instructible about it, and i think he did the whole 4x4x4 cube with no ICs (drivers or shift registers)

The approach proposed in the mistubishi paper is good for higher power led or at low lighting levels.

The charge transfer approach is good for regulator led or at high lighting levels. It is also simpler to implement and faster to run.

Just some links that seem pertinent

http://www.merl.com/areas/LEDcomm/ Original Mitsubishi article
http://arduino.cc/forum/index.php/topic,128556.0.html Thread I started ,didn't see this one

I could not find this ?

Hunter's Instructible. Acheives sharp

http://www.instructables.com/id/Bi-directional-LED-Sensing-Try-out/ Practical board and code setup
My code to play with this concept

const int led1 = A2;
const int led2 = A1;

int value1, value2;
int threshold1 = 120;
int threshold2 = 120;

void setup(){
  Serial.begin(9600);
  pinMode(led1,INPUT);
  pinMode(led2,INPUT);
  }

void loop(){
  value1 = analogRead(A2);
  value2 = analogRead(A1);
  if(value1 >= threshold1){
    Serial.print("Value 1 : ");
    Serial.println(value1);
    out(led1,led2,threshold1);
    pinMode(led2,INPUT);
   
  }
  else if(value2 >= threshold2){
    Serial.print("Value 2 : ");
    Serial.println(value2);
   
    out(led2,led1,threshold2);
    pinMode(led1,INPUT);
   
  }
}

void out(int a, int b, int threshold){
  pinMode(b,OUTPUT);
  //if (a==led2)
  //delay(30);
  //else
  //delay(60);
  int value = analogRead(a);
    if(value >= threshold){
      analogWrite(b,value);
         }
    else{analogWrite(b,0);    }
     Serial.print("Value Following: ");
    Serial.println(value2);
  }

Not sure what your code is trying do but this is what I would try:

//read reverse of a led's cathode
unsigned short led_adc(unsigned char pin) {
  //energize the pin
  digitalWrite(pin, HIGH);
  pinMode(pin, OUTPUT);

  //put some delays here
  NOP(); NOP(); NOP(); NOP(); NOP();

  //adc the pin
  pinMode(pin, INPUT);

  return analogRead(pin);
}

void loop(){
  //read A1
  Serial.print("Value 1 : ");
  Serial.println(led_adc(A1));
  //out(led1,led2,threshold1);
  //pinMode(led2,INPUT);
   
  //read A2
  Serial.print("Value 2 : ");
  Serial.println(led_adc(A2));
}

led_adc() energies the led capacitance and then adc the charge transfer. For high led capacitance, you should read a voltage very close to 1023; Lower led capacitance results in lower reading - more charges are transfered to Chold -> lower voltage across the led capacitor.

The charger transfer approach can be used as a way to measure small capacitance.

dhenry:
Not sure what your code is trying do but this is what I would try:If you build it you will see an LED recognises the flash of another LED by flashing itself in the same sequence . So if I press the button on LED1 ,on and off the LED2 flashes on and off
//put some delays here
NOP(); NOP(); NOP(); NOP(); NOP();This does not compile. When I add in what is required to compile it with a delay(400) I get alternating flashing LED's . So that's a bit vague , What are you trying to do and maybe I can help you
led_adc() energies the led capacitance and then adc the charge transfer. For high led capacitance, you should read a voltage very close to 1023; Lower led capacitance results in lower reading - more charges are transfered to Chold -> lower voltage across the led capacitor.

//read reverse of a led's cathode
#define NOP() asm("nop") //waste a tick
void setup(){
Serial.begin(9600);}
unsigned short led_adc(unsigned char pin) {
  //energize the pin
  digitalWrite(pin, HIGH);
  pinMode(pin, OUTPUT);

  //put some delays here
  NOP(); NOP(); NOP(); NOP(); NOP();
delay(400);
  //adc the pin
  pinMode(pin, INPUT);

  return analogRead(pin);
}

void loop(){
  //read A1
  Serial.print("Value 1 : ");
  Serial.println(led_adc(A1));
  //out(led1,led2,threshold1);
  //pinMode(led2,INPUT);
   
  //read A2
  Serial.print("Value 2 : ");
  Serial.println(led_adc(A2));
}

Add this:

#define NOP() asm("nop") //waste a tick

delay(400) is way too long. If you have to, use delay(1)

dhenry:
Add this:#define NOP() asm("nop") //waste a tick
delay(400) is way too long. If you have to, use delay(1)

Yes OK but I was trying to see just what you are getting at. By slowing it down some as I said it is just alternate flashing LED's . The serial monitor flashes values too fast to read anything definate.

So with a small delay , what is this going to demonstrate to me ?

I have experimented with using LEDs as light sensors and because I have an oscilloscope I can look at the voltage signal coming from the LED under my lab lighting conditions. This gave me the clue that there was significant ripple in the signal to be measured.

I have ground the anode. I have connected the cathode to an Arduino UNO analog input. My florescent lights produce a signal which when measured by the Arduino has an AC component which goes right to ground. In other words the fluorescents are flickering fully off but our eyes do not see it.

Because I am interested in measuring fast light events, I experimented with getting the Arduino ADC as fast as possible which entailed changing the ADC clock speed.

Once I did that the Arduino was sufficiently oversampling the ripple so that I could then smooth it with a digital filter. Caution if you under sample a parodic signal with your ADC you can not fixed it with a post filter.
I implemented an exponential moving average filter and empirically tested and set the alpha (like the time constant) to smooth the measured light.

See Moving average - Wikipedia

My code with LOTS of debug and experimenting code left in:

/*LEDasSensor
   Author: Forrest Erickson
   Date: 20210503
   Description: Experiments on Arduino Uno for sensing light with LED as a photo cell.
   Added a EMA (Exponential Moving Average) to the serial output.  
   In Ardunio, view serial ouput with Serial Ploter CTRL+SHIFT+L. 
   Ref: https://en.wikipedia.org/wiki/Moving_average#Cumulative_moving_average

   Results:
   Comment out all but the AnalogRead for Sensor 1 and the set LED1 code and set for ADC clock 1MHz
   But leaving in the map function.
   The onboard LED is pulsed around the AnalogRead reset to trigger an oscilliscope for measurement.
   The frequency of the main loop is 9.7 to 10.13 KHz. 
   By reducing the The output LED 
   Adding the print statement back in loop reduces the sample rate to 2.5 to 2.9 KHz.

   Based on:
   LED light sensing
   Hunter Carlson
   June 9 2009
   From: https://forum.arduino.cc/t/leds-as-photo-diodes/114569
*/

/*Set frequency. Set 4000, however the measures loop frequency is about 820Hz
 * 
 */

#define SAMPLEFREQ 4000                       //About 66.6 samples per cycle of 60 hz.
 
const int SAMPLEPERIOD = 1000000/SAMPLEFREQ;  //Microseconds. 

//Light sensor LEDs wired for
int senseReset = 0;     // A0 input tied to GND.
int sense01 = 5;        // sensing LED Cathode connected to analog5. Anode to GND.
int sense02 = 4;        // sensing LED Cathode connected to analog4. Anode to GND.

int LED01 = 9;          // 330 Ohm resistor, to LED Annode on dig9,
int LED01GND = 8;       // LED Cathode to be set to ground

int LED02 = 11;         // 330 Ohm resistor, to LED Annode on dig11,
int LED02GND = 12;      // LED Cathode to be set to ground

int val01 = 0;          // variable to store the value read from sense01
int val02 = 0;          // variable to store the value read from sense02
float val01EMA = 0;     // variable to store the exponential moving average sense01
float val02EMA = 0;     // variable to store the exponential moving average sense02


//Threshold for dark indication.
//Note there is no hysterisis on this comparision.
//int light = 900;        // set in !AS lab with red LED as sensor before adding read to A0 normalization.
//int light = 217;        // set light threshold
//int light = 150;        // set in !AS lab with red LED as sensor
//int light = 50;         // set in !AS lab with red LED as sensor
//int light = 35;         // set in !AS lab with red LED as sensor
int light = 10;         // set in !AS lab with red LED as sensor
//int light = 3;            // set in !AS lab with red LED as sensor

//An exponential moving average (EMA),
//float alpha = 0.99;
//float alpha = 0.25;
//float alpha = 0.125;
//float alpha = 0.0625;
//float alpha = 0.0375;     //P-P is 23 to 34 under lab lights.
float alpha = 0.01;
//float alpha = 0.001;        //P-P is 29 to 30 under lab lights.

//Timer setup for ADC
// defines for setting and clearing register bits
#ifndef cbi
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#endif
#ifndef sbi
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
#endif

void setup()
{
  pinMode(LED_BUILTIN, OUTPUT);
  digitalWrite(LED_BUILTIN, HIGH);  //LED goes high as setup() starts

  // set ADC prescale to 16 for faster conversion, 1MHz conversion. Measured at 25uS conversion
  sbi(ADCSRA,ADPS2) ;
  cbi(ADCSRA,ADPS1) ;
  cbi(ADCSRA,ADPS0) ;

//  Serial.begin(115200);          //  setup serial
//  Serial.begin(1000000);          //  setup serial
  Serial.begin(2000000);          //  setup serial  1.8K sampe / second at 1MHz ADC clock
  
  pinMode(LED01, OUTPUT);
  pinMode(LED02, OUTPUT);
  pinMode(LED01GND, OUTPUT);
  pinMode(LED02GND, OUTPUT);
  digitalWrite(LED01GND, LOW);    //Ground LED cathode pins.
  digitalWrite(LED02GND, LOW);

  // Get intial values for EMA
  analogRead(senseReset);     //Read ground pin to normalize by discharge ADC
  val01EMA = map(analogRead(sense01), 0 , 1024, 0, 5000);
  analogRead(senseReset);     //Read ground pin to normalize by discharge ADC
  val02EMA = map(analogRead(sense02), 0 , 1024, 0, 5000);

  digitalWrite(LED_BUILTIN, LOW); //LED goes low as end of setup() 
}// end setup()

/* Read the light (LED Voltages) and scale to 0 to 5000 mV.
 * Take EMA of readings.
 * Compair to a threshold value and turn on LEDs if measured light below threshold.
 * Wait till time to take next sample.
 */

void loop()
{
  digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN));  //Toggle built in LED so we can sample rate.
  analogRead(senseReset);     //Read ground pin to normalize by discharge ADC. About 120uS for read.
  digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN));  //Toggle built in LED so we can sample rate.
  val01 = map(analogRead(sense01), 0 , 1024, 0, 5000);    // read sense01 led and map to mV.
//  analogRead(senseReset);     //Read ground pin to normalize by discharge ADC
//  val02 = map(analogRead(sense02), 0 , 1024, 0, 5000);    // read sense02 led

  //Print out the EMA smoothed value.
  val01EMA = alpha * val01 + (1 - alpha) * val01EMA;
//  val01EMA = val01;         // No EMA

  //Serial print adds significant delay. Even at 2Mbaud the sample rate is limited to: 1.6 to 2.0 KS/Sec
  Serial.println(val01EMA);     // 4.3KS/Sec when commented and with ADC clock 1MHz.

  //LED 1
//  if (val01EMA >= light) {         
//    digitalWrite(LED01, LOW);     
//  } else {
//    digitalWrite(LED01, HIGH);    
//  }
  digitalWrite(LED01, !(val01EMA >= light));

//  val02EMA = alpha * val02 + (1 - alpha) * val02EMA;
//  Serial.println(val01EMA);

  //LED 2
  //About 26uS to write the LED
//  if (val02EMA >= light) {              
//    digitalWrite(LED02, LOW);     
//  } else {
//    digitalWrite(LED02, HIGH);    
//  }

//  digitalWrite(LED02, (val02EMA <= light));

  //delay(1); //Slow down data.
//  delayMicroseconds(SAMPLEPERIOD); //Slow down data. With out delay, and 1MHz clock sample freq 1.7KHz,

  
}//end loop()

I have placed this software and some images into Github at: GitHub - ForrestErickson/LEDasSensor: Experiments on Arduino Uno for sensing light with LED as a photo cell.

There is a lot that goes into this. Some semiconductors can be reactive to different things like radiation, light, etc. There are ICs which are reactive to light. The Raspberry Pi I think is an example.

The reason for why this works on LEDs is due to the photoelectric effect on PN junctions. If light is able to react the required section it will behave in some degree like a photodiode. Sometimes this is a byproduct of manufacturing. It may be intentional or unintentional.

In theory a LED could be a small solar cell. Will an LED be ideal for photodiode or solar cell applications? Probably not, but it is possible.

But surely you knew this already?

It all depends on the phosphors used, many are sold with "afterglow" to varying degrees, and would actually smooth out the zero crossings of the AC, but the discharge itself stops for a significant part of the cycle, being reignited from the heaters.

I was surprised that it seams to go to zero.

My now deceased neighbor used to make remark when ever given the chance that when florescent lights were introduce to machine shops injuries happened because people were fooled about the machine's motion. He says that the early generations lights were bad for that. That had me thinking the effect was reduced. To see the ADC measured value go to zero was a surprise.

But I know that when I watch the teeth on my table saw blade spin down it strobes backwards and forward. Hum. I should put a sharpie mark or three on my blades come to think of it.

I also see a distortion at the positive peaks that I also did not expect.