Cheap Alternative for Hard to Find CDS Light Sensor

CdS (Cadmium Sulfide) photo-resistors are commonly used for detecting light levels. Their resistance varies considerably depending on the intensity of light striking them. They are common, fairly cheap and easy to use. So what’s the problem? They are becoming hard to find. The reason is because of the RoHS directive. Since CdS cells contain cadmium, a toxic heavy metal, this important component is no longer stocked by major electronics distributors. The good news is that not only is there an excellent alternative, but it is more versatile and vastly cheaper.

If you guessed photo-diode you are close, but even better is the humble LED. If you are building an analog circuit, then you will be limited to photo-diodes and photo-transistors due to their greater sensitivity. If, however, you are building a micro-controller based circuit such as a PIC or Arduino, then you can use a simple LED to achieve the same end and even more.

Photo-diodes and LEDs

Both of these devices are essentially the same. Both are diodes enclosed in a translucent case. They will both permit a small amount of reverse current to flow through them when they are reversed biased. The difference is that the photo-diode is optimized for photo-sensitivity while the LED is optimized for its light emission when forward biased. Why then would anyone want to use the LED which is inferior as a photo-detector? There are several excellent reasons:Streaming Beauty and the Beast (2017)

  • You probably have lots of LEDs already in your parts boxes
  • They are really cheap – only a few pennies whereas photo-diodes cost 50 cents or more
  • You can make use of their spectrum filtering capabilities that photo-diodes have less choice over
  • You can both emit light and sense it at the same time (almost) which leads to a whole slew of applications such as varying output intensity with light levels, bi-directional communication with a single device, etc.

The reason we need to use a micro-controller with LEDs is because their reverse current is so small that we need to use a special property of LEDs in order to make them do double duty as a light sensor. Making use of this property requires a micro-controller.

Using LEDs for Light Sensing

Like many electronic components, LEDs have a small amount of parasitic capacitance associated with them. While normally such properties are a potential problem, in our case, we can use it to make it into a photo-detector. Here’s how.

LED as a photo-sensor

LED as a photo-sensor

Hook up the LED in reverse, connecting the cathode to the micro’s pin, and the anode to ground as shown in the diagram on the left. If the pin is set to output-high, it will charge the LED’s parasitic capacitor. It only takes a millisecond or less for this to happen, so the charging procedure is very fast.

Next, set the pin to input-low which allows the LED to discharge its capacitor. The rate of its discharge is proportional to the amount of light reaching the LED. Under direct lighting, it only takes a millisecond or so to discharge enough to trigger the input low. In dark conditions, it can take a few seconds for it to discharge enough to trigger the input pin.

By measuring the time it takes the LED to discharge, we can tell how much light it is detecting. The amazing thing is we can do this measurement with only one pin, and a digital pin at that! By connecting the cathode to another pin (with a series resistor to limit the current), we can also illuminate the diode when we are not using it to sense light. You can use this technique to not only make the LED’s brightness in proportion  to the ambient light level, but even use it for bi-directional communication.

There is another property of using the LED for measuring light levels, and that property is color spectrum filtering. What that means is that the LED responds to light only within a particular color spectrum. This property can be a problem or an opportunity depending on what you want to do. LEDs will only respond to light frequencies (i.e. colors) that are equal to or higher than that frequency that the LED emits at. Therefore, if you want to sense daylight levels, you should use a red or yellow LED. If you want to measure only the blue through ultra-violet spectrum, then you can use a blue LED for that measurement. You also need to take into account the color of the LEDs packaging. If you use a red LED within a red case, you will only measure red light. Ditto with any other color. To summarize, LEDs act as a high-pass light filter when detecting, but their package is a narrow band-pass light filter for that particular color. You can use this property to measure individual colors, but that is really best done with specialized light sensor chips. The LED is best for cheap, readily available ambient light sensing and communication. For ambient light sensing, red and yellow LEDs are best, and make sure you use ones in clear cases.

Putting It Into Practice

To put the theory into practice, you need just an LED and a micro-controller. For this article, we will use an Arduino-based micro. Here is a simple class written just for this purpose:

class AmbientLightSensor {
public:
  AmbientLightSensor(int ledPin) : mLedPin(ledPin), mMeasureAnalog(false) {}

  void setAnalogMeasurement(int thresholdLevel); // measure from an analog pin
  void setDigitalMeasurement(); // measure from a digital pin (default)

  int measure();

protected:
  int mLedPin;
  bool mMeasureAnalog;
  int mAnalogThresholdLevel; // (0 to 1023)

  void charge();
  void discharge();

  int measureUsingAnalogPin();
  int measureUsingDigitalPin();
};

void AmbientLightSensor::setAnalogMeasurement(int thresholdLevel)
{
  mAnalogThresholdLevel = thresholdLevel;
  mMeasureAnalog = true;
}

void AmbientLightSensor::setDigitalMeasurement()
{
  mMeasureAnalog = false;
}

void AmbientLightSensor::charge() {
  // Apply reverse voltage, charge up the pin and led capacitance
  pinMode(mLedPin, OUTPUT);
  digitalWrite(mLedPin, HIGH);
}

void AmbientLightSensor::discharge() {
  // Isolate the diode
  pinMode(mLedPin, INPUT);
  digitalWrite(mLedPin, LOW); // turn off internal pull-up resistor, see http://arduino.cc/en/Tutorial/DigitalPins
}

int AmbientLightSensor::measure() {
  charge();
  delay(1); // charge it up
  discharge();
  return (mMeasureAnalog)? measureUsingAnalogPin() : measureUsingDigitalPin();
}

int AmbientLightSensor::measureUsingDigitalPin() {
  long startTime = millis();
  // Time how long it takes the diode to bleed back down to a logic zero
  while ((millis() - startTime) < 2000) { // max time we allow is 2000 ms
    if ( digitalRead(mLedPin)==0) break;
  }
  return millis() - startTime;
}

int AmbientLightSensor::measureUsingAnalogPin() {
  long startTime = millis();
  // Time how long it takes the diode to bleed back down to a logic zero
  while ((millis() - startTime) < 2000) { // max time we allow is 2000 ms
    if ( analogRead(mLedPin) < mAnalogThresholdLevel) break;
  }
  return millis() - startTime;
}

To use the class, the code is simple. Here’s an example:

AmbientLightSensor led(12); // LED is hooked up to digital pin 12

int led2 = 9; // led to indicate darkness is hooked up to digital pin 9

void setup()
{}

void loop()
{
  int ledVal; ledVal = led.measure();
  if (ledVal > 300) // a decent level of darkness
    digitalWrite(led2, ON);
  else
    digitalWrite(led2, OFF);

  delay(200); // check every 0.2 secs
}

Other Concerns

When sensing low light levels, using a digital pin for discharge threshold detection can take a significant amount of time – several tenths of a second or more. For this reason, the AmbientLightSensor class also permits using an analog pin which uses analogWrite to detect the discharge threshold. With the digital pin, the threshold voltage is fixed by the physical characteristics of the device. By using an analog pin, we can detect a sufficient amount of discharge much more quickly by setting this voltage higher. To use an analog pin, the code is similar, but in your setup function insert this line:

led.setAnalogMeasurement(512); // maxes at about 300 (fully dark)

I have found an analogRead level of 512, which corresponds to 50% of Vcc limits the discharge time in the dark to about 300 milliseconds, which still giving a good range of sensitivity. You can of course, vary this level to suit your particular application.

When the LED is used solely as a photo-detector, you do not need a current limiting resistor. If you desire to use it also as a light emitter, then you will need to add one in. The resistor itself is only for the forward biased state. It does not affect the reverse-bias detection operation.

Conclusion

You are now armed with the tools you need to use an LED you have lying around to replace the need for a difficult-to-find CdS photo resistor. Even if you have no trouble sourcing CdS cells, it is now clear that LEDs are cheaper (by about 10 times) and easier to use. In most applications, you don’t even need an analog pin, but only a digital one. The code is simple as we have seen, and is easy to use and modify to suit your particular needs. Lastly, you have not only a simple, inexpensive solution, but the possibility of novel applications as well.

Please share your comments and applications for using LEDs for photo-detection in the comments below.

Cheap Alternative for Hard to Find CDS Light Sensor by Provide Your Own is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.

This entry was posted in Tech and tagged , , , . Section: . Bookmark the permalink. Both comments and trackbacks are currently closed.

12 Comments

  1. Posted December 15, 2011 at 12:31 am | Permalink

    This is a very interesting article, so I tried to follow your setup, but somewhere things get stuck. The “photo” LED is connected to pin 12, as indicator I used the on-board LED at pin 13. Because there was no response, I checked with Serial.write, but ledVal is empty, nothing happens.

    Besides, you might want to change the digitalWrite arguments to either HIGH or LOW instead of ON and OFF.

  2. Posted December 15, 2011 at 7:09 pm | Permalink

    Ah, Serial.println(ledVal) finally yields some results. With the LED I currently use I never get more than 20 when covering the LED, but when I adjust the threshold at least it does indeed work. I’ll experiment with other LEDs…

  3. Posted February 15, 2012 at 9:47 pm | Permalink

    A reader sent me these questions:

    You need to put a resistor of some kind inline with the LED to avoid
    overdriving it, correct? What value should be used for optimal
    results?

    My answer:

    1) You do not need a resistor if you are just measuring light levels. As long as you don’t forward bias the LED, no resistor is necessary. You don’t even need to worry about it (if you make a mistake) when developing your sketch. Since the AVR chips only output 40mA, I have driven LEDs from an output pin for short periods without harm. In the worse case, you might fry a cheap LED and have to replace it.

    2) If you do want to turn it on, then use whatever size resistor that provides the current you want. For most low power LEDs (red, yellow & green) that would be 220 ohms, which would give you 15mA. Also, FYI, the resistor does not affect the sensor operation.

    Using the same LED as both a sensor and an illumination has some nice applications. One such application is to change the LED’s brightness according to ambient light levels. Another is to use it as a touch-switch with a built-in indicator (it would only work in decently lit environments though).

  4. Peter
    Posted March 7, 2012 at 11:52 am | Permalink

    Scott, I would like to hire you to build a small prototype device such as the one you mention above. Could you contact me at phayes@exactlynet.com.

  5. Posted March 23, 2012 at 9:32 pm | Permalink

    For Mega Rev 3 (and equiv)

    if (ledVal > 10){ // adjust this number based on LED sensitivity
    digitalWrite(led2, LOW);
    } else{
    digitalWrite(led2, HIGH);
    }
    delay(200); // check every 0.2 secs

    Also, in the class you can lower the 2000 number to something much much lower.

  6. Posted March 28, 2012 at 2:07 pm | Permalink

    Guys I’m new to this field, only few days experimenting, what is the class, and where to put it in order to get the thing rolling?
    your quick responce to my email is highly appreciated!

    • Posted March 28, 2012 at 6:56 pm | Permalink

      Either create a file for the class and include it, or else just put it at the top of your sketch. See the Arduino docs on adding files to your sketch.

      • Posted March 31, 2012 at 1:56 pm | Permalink

        Thanks for quick responce, Looks like it works, but: I use ARDUINO MEGA2560, here is the scetch :

        class AmbientLightSensor {
        public:
        AmbientLightSensor(int ledPin) : mLedPin(ledPin), mMeasureAnalog(false) {}

        void setAnalogMeasurement(int thresholdLevel); // measure from an analog pin
        void setDigitalMeasurement(); // measure from a digital pin (default)

        int measure();

        protected:
        int mLedPin;
        bool mMeasureAnalog;
        int mAnalogThresholdLevel; // (0 to 1023)

        void charge();
        void discharge();

        int measureUsingAnalogPin();
        int measureUsingDigitalPin();
        };

        void AmbientLightSensor::setAnalogMeasurement(int thresholdLevel)
        {
        mAnalogThresholdLevel = thresholdLevel;
        mMeasureAnalog = true;
        }

        void AmbientLightSensor::setDigitalMeasurement()
        {
        mMeasureAnalog = false;
        }

        void AmbientLightSensor::charge() {
        // Apply reverse voltage, charge up the pin and led capacitance
        pinMode(mLedPin, OUTPUT);
        digitalWrite(mLedPin, HIGH);
        }

        void AmbientLightSensor::discharge() {
        // Isolate the diode
        pinMode(mLedPin, INPUT);
        digitalWrite(mLedPin, LOW); // turn off internal pull-up resistor, see http://arduino.cc/en/Tutorial/DigitalPins
        }

        int AmbientLightSensor::measure() {
        charge();
        delay(1); // charge it up
        discharge();
        return (mMeasureAnalog)? measureUsingAnalogPin() : measureUsingDigitalPin();
        }

        int AmbientLightSensor::measureUsingDigitalPin() {
        long startTime = millis();
        // Time how long it takes the diode to bleed back down to a logic zero
        while ((millis() – startTime) < 2000) { // max time we allow is 2000 ms
        if ( digitalRead(mLedPin)==0) break;
        }
        return millis() – startTime;
        }

        int AmbientLightSensor::measureUsingAnalogPin() {
        long startTime = millis();
        // Time how long it takes the diode to bleed back down to a logic zero
        while ((millis() – startTime) < 2000) { // max time we allow is 2000 ms
        if ( analogRead(mLedPin) < mAnalogThresholdLevel) break;
        }
        return millis() – startTime;
        }

        indication only works on yellow smd LED ounted on the arduino,
        However I did try to use analog pins and almost got success, but with different code from different source:

        [quote]
        [color=#7E7E7E]/* LED as light sensor Created By Sean Jonson Uses an IR LED [/color]
        [color=#7E7E7E](but it can be any kind)on analog pin 5 to turn off a regular [/color]
        [color=#7E7E7E]LED on digital 13 when there is not much light on the LED. [/color]
        [color=#7E7E7E]The circut: LED+ to analog pin 5 LED- to GND A second LED+ [/color]
        [color=#7E7E7E]to pin 13 A second LED- to GND */[/color]

        [color=#CC6600]int[/color] sensorPin = A2; [color=#7E7E7E]// select the input pin for the LED [/color]
        [color=#CC6600]int[/color] ledPin = 13; [color=#7E7E7E]// select the pin for the LED float [/color]
        [color=#CC6600]int[/color] sensorValue = 0; [color=#7E7E7E]// variable to store the value coming from the LED [/color]

        [color=#CC6600]void[/color] [color=#CC6600][b]setup[/b][/color]()

        [color=#CC6600]pinMode[/color](ledPin, [color=#006699]OUTPUT[/color]); [color=#7E7E7E]// declare the ledPin as an OUTPUT: [/color]
        [color=#CC6600][b]Serial[/b][/color].[color=#CC6600]begin[/color](9600); [color=#7E7E7E]// begin the serial to the computer [/color]

        [color=#CC6600]void[/color] [color=#CC6600][b]loop[/b][/color]()

        sensorValue = [color=#CC6600]analogRead[/color](sensorPin); [color=#7E7E7E]// read the value from the LED [/color]
        [color=#CC6600][b]Serial[/b][/color].[color=#CC6600]println[/color](sensorValue); [color=#7E7E7E]// serialy print the value from the LED [/color]
        [color=#CC6600]delay[/color](500);
        [color=#CC6600]if[/color](sensorValue < 190)
        {     [color=#7E7E7E]// if the LED "see's" less than 129 light, if you dont use the same LED im using you might want to tweek this number [/color]
        [color=#CC6600]digitalWrite[/color](ledPin, [color=#006699]HIGH[/color]);
                        [color=#7E7E7E]// turn on the light [/color]

        [color=#CC6600]else[/color]{ [color=#CC6600]digitalWrite[/color](ledPin, [color=#006699]LOW[/color]); [color=#7E7E7E]// turn it off [/color]

        [/quote]

        However, when I built 4 LEDs with 1 meter flat cable in order to mount it on the servo to indicate light source, wanted to replace photoresistor as it says in this topic, it did not work, probably because of long cable, does someone have a solution to this.
        remark: I simply do not have photoresistor, and noone does, unless I disasamble something expensive…

  7. tytower
    Posted November 25, 2012 at 4:47 pm | Permalink

    As this works on capacitance I have not been able to get it to work with about a 4 inch extension on the led. I want to mount it on the outside of a waterproof enclosure but two straight wires or twisted do not work

    I’m wondering if a small coax cable or something shielded might do the trick?

    • Posted December 6, 2012 at 10:55 am | Permalink

      You are right about the problems measuring small parasitic capacitance. I have found some difficulty myself when the Arduino was no longer connected to earth ground. The first thing I would try is that – connect your Arduino’s ground to earth ground. That settles the measurement quite a bit. Next, you could try averaging several samples to reduce the noise. You can also try coax as you suggest.

      If all else fails, see if you can change your design to use a photo-transistor. They don’t rely on parasitic capacitance, and will work in all situations. Good luck.

  8. Duane Johnson
    Posted December 15, 2013 at 11:41 pm | Permalink

    I made an async LED sensor library based on AmbientLightSensor, but implemented in an async manner for anyone interested: https://github.com/canadaduane/LEDSensor

  9. PIERRE1
    Posted July 21, 2016 at 12:50 am | Permalink

    hey scott! seen and enjoy your work, now my question goes like this. if one decides to generate electricity from the light ,does one still have to use the LED for that purpose and connection still thesame?

4 Trackbacks