<< Click to Display Table of Contents >>

Navigation:  Bits and PCs Blog - 2025.11.19 >

Ambient Light Sensors - 2025.09.12

Previous pageReturn to chapter overviewNext page

Want to know if it's dark? Are the pubs open yet? Is it bedtime? To help answer these pressing questions, you could use an "ambient light sensor". Matt's Tip: For the aforementioned applications, a clock may be more accurate, since ambient light can be affected by climate change, lightning, fires and BBQs, etc.

ALS = Amyotrophic Lateral Sclerosis Ambient Light Sensor

Blurb

There are many intelligent digital ambient light sensor chips and modules available these days. These are all tiny SMD chips which are impossible to solder by hand, so you must buy a ready-made module (at 10x the price of the chip). The digital sensors usually have a 2-wire I²C interface (Inter-Integrated Circuit), with SCL (clock) and SDA (data) connections.

Most sensors contain two phototransistors or photodiodes. A "full spectrum" sensor for visible light plus infrared (IR) light, and an "infrared only" sensor which is mostly sensitive to infrared light. The lux value, as registered by the human eye, can be calculated by removing the IR component from the full spectrum sensor. Some chips do this internally. For others, you must do this in the code, which is not easy - most of the lux calculations I have seen in the [few] github libraries is inconsistent or wrong. Even if the chip calculates the lux value itself, it can still be "inconsistent".

Different light sources produce different ranges of light frequencies, which produce varying sensor readings depending on the characteristics of the phototransistor or photodiode. Halogen lamps, incandescent bulbs, LED lamps, lightening, sunlight and flames all have very different IR components and different light frequency ranges. The "angle of incidence" of the light source also has a significant effect. Mains-powered lights can flash at the A/C mains frequency (50 or 60 times a second) and LED lamps may be turned on/off thousands of times a second. Accurate and consistent lux readings are very difficult to obtain.

The sections below describe some of cheapest and common modules. There's also C++ class libraries which are in many some ways superior to the official libraries (in my opinion :-) Some of these modules contain discontinued chips, which is why you can buy them cheap! There do not seem to be many pre-soldered modules for the latest chips yet - but Mattlabs is working on it.

 

Digital sensors

BH1750 (Rohm) GY-302 Digital Light Sensor

LTR-507ALS (LiteOn) Optical Sensor

TSL2591 (Adafruit) Dynamic Range Light Sensor

VEML7700 and VEML6030 (Vishay) High Accuracy Ambient Light Sensors

Analog sensors

DFRobot Ambient Light Sensor SKU DFR0026

TEMT6000 Analog Ambient Light Sensor

 

BH1750 (Rohm) GY-302 Digital Light Sensor

bh1750-1

I recommend this one for price, accuracy and simplicity!

This is one of the cheapest modules (CHF3.90), and so far it seems to be the most accurate when compared to the readings from my low-cost Voltcraft LX-10 lux meter (*). But hold on - maybe my lux meter contains the same chip! It also seems to handle infrared light very well, it successfully ignores it and the readings are barely affected.

It's very easy to program too, once you figure out the Chinglish text in the data sheet. The lux range is 0..120000, the upper limit being affected by the programmable measurement time, see setMeasurementTime(uint mt).

The only drawbacks are that it cannot generate interrupts, and you have to use a timer to determine when the next reading is available. (Although you could set the data register to 0 and wait until it's non-zero when the reading has been taken - but that won't work if the lux reading is actually zero.) The code below contains methods to do that, see the example sketch. There's also a method that tests and displays the actual the conversion times (intuitively called getConversionTimes()), with an example in the sketch that displays the conversion times for all programmable measurement times (this is patched out because it's not fast).

The GY-302 module contains a 3.3V regulator, so you can run it from 5V or 3.3V. This BH1750 chip itself runs on 3.3V. Do NOT run it with 5V if connected to a 3.3V controller, like the Arduino Zero.

gy-302-schematic

I bought this module from bastelgarage.ch for CHF3.90 :-)
https://www.bastelgarage.ch/bh1750-gy-302-digital-light-sensor-light-intensity-sensor-with-i2c

(*) muman.ch is not affiliated with, bribed by, or influenced in any way, by Voltcraft or Bastelgarage. But we're open to offers :-)

 

Data Sheets

https://www.mouser.com/datasheet/2/348/bh1750fvi-e-186247.pdf

https://www.handsontec.com/dataspecs/sensor/BH1750%20Light%20Sensor.pdf

 

Mattlabs code

The github libraries that I found for this chip are not professional quality, so I recommend using the Mattlabs code.

  BH1750LightSensor.h   [Click to show/hide the code]

Here's an example sketch, which outputs various values to the Serial port.

  BH1750.ino   [Click to show/hide the code]

 

LTR-507ALS (LiteOn) Optical Sensor

soldered-logo liteon-logo

ltr507-1

The LTR-507 has a couple of problems (see below) and LiteOn is not advertising it anymore. Maybe that's why these modules are cheap. The more recent LiteOn chips are better, like the improved LTR-329 which will soon be reviewed below.

This 3.3V chip has two functions, an Ambient Light Sensor (ALS) and a Proximity Sensor (PS). It computes the (hopefully accurate) lux value internally, so you don't need any dubious code to do the job. It also has a fully programmable interrupt pin (INT), which can signal the MCU when the light level is outside, or inside, a programmed range. It has two sensors, one for visible and infrared light (channel 1, CH1), and another for only (mostly) infrared light (channel 2, CH2). The lux value (the light level visible to the human eye) is calculated from channel 1 by removing the infrared component of channel 2.

The chip contains 33 registers, making it rather complicated to program. For full details you must read the data sheet (several times), but reading my code and the copious comments will help a lot. This chip seems to have been discontinued by LiteOn (it's not on their website anymore), and the more recent chips (LTR-3xx etc) have fewer registers and seem to be easier to use. The more recent chips do not seem to be available on easy-to-use modules.

The ALS and PS features can be enabled (activated) or disabled (disabled = standby, the power-on default, which saves power if its battery operated). The ALS feature has a programmable gain, bit resolution and sampling rate, see configureALS(). intuitively, the PS feature is configured with configurePS(). The interrupt behaviour and threshold light levels can also be programmed. Always configure the interrupts BEFORE activating ALS or PS. See the comments in the code for details.

There are four gain settings (1x = 1 lux/count; 2x = 0.5 lux/count; 100x = 0.01 lux/count; 200x = 0.005 lux/count), which can be selected during operation to adjust the the lux value range according to the light level, and prevent saturation.

 

Saturation

Digital saturation occurs when the light intensity is too high for the internal calculation. Analog saturation occurs when the level is too high for analog-to-digital converter. With a high gain setting this can happen at quite low light levels.

With 1x gain, the lux reading goes up to the maximum 65535 and stays there. With 2x gain, the maximum is 32767 lux, with 100x gain it is 655, and 200x is 327. If the level goes too much above the max. level, it drops to zero, which is not good.

To complicate things, each of the two sensors has a different saturation level. The full spectrum sensor is more susceptible to visible light and the IR sensor saturates when there's high infrared light. The LTR-507 also has trouble with lux calculation if the IR component of the light is too high, see next section on high levels of infrared.

I think there's a problem with the chip's handling of saturation. If the level goes up to the maximum value according to the gain, it stays at that value until overflow occurs, then it sets the lux value to 0. As the light level increases, the lux reading begins to drop for a while before saturation is recognised and the reading is set to zero. This could be due to arithmetic overflow in the on-chip calculations. I think this is why they added the ALS_IRF_CUT_OFF register, see next section.

lux-sensor-saturation

If the lux value is 0, how do we know it is because of saturation or because there is no light and 0 lux is the correct value? If at the maximum level, how do we know if the internal calculation has overflowed? Unfortunately, the LTR-507 does not have an "overflow" or "saturation" bit for the lux value (it's only for the PS value). But the muman library has added features which handle both these situations...

If the raw lux reading is zero, it checks the channel 0 analog reading. If that is also zero then it assumes no light and returns 0 lux. But if the channel 0 reading is not zero, it means that overflow has occurred and it returns an impossible lux reading of -1.

If the raw lux value is the maximum value of 65535, the code checks the channel 0 value for overflow. I'm not sure exactly what value to check for, and it's affected by the gain setting. But I found that checking the most significant 4 bits (bits 19..16) for the value 1111 (0x0f) works well for all gain settings. If the MS 4 bits are all 1s, then it returns -1.

Both these checks are done in the readRawLux() method.

 

High levels of infrared light produce invalid lux readings

The LTR-507 sensor does not handle very high levels of infrared light well. The lux reading actually decreases for a while if the IR component becomes too high. They added a feature to handle this. You can configure a ratio between channel 1 and 2 with configureALSIRFCutoff(), which sets a maximum level for the infrared component. The default value for the ALS_IRF_CUT_OFF register is 0xD0 (208). If the IR levels are high and the ALS_IRF_CUT_OFF value is exceeded, the reading is set to 0 lux, and both analog channels are set to 0 too.

This feature is annoying because it sets the lux reading to 0 even for quite low light levels from an incandescent spot lamp (100W), and it's impossible to tell the difference between darkness and IR saturation. There is no way to turn off this feature.

I think the description in the data sheet is incorrect...

It says, "if ADCIR / ADCCLEAR > ALS_IRF_CUT_OFF, then ALS_DATA = 0".
I think this should be, "if (256 * ADCIR) / ADCCLEAR > ALS_IRF_CUT_OFF, then ALS_DATA = 0".

In my tests "* 256" worked very well. You can test this yourself using an incandescent light bulb, these emit a lot of infrared. (But avoid touching the live wire, see Disclaimer.)

Ref. data sheet section 6.16 ALS_IRF_CUT_OFF Register (0x97):
https://optoelectronics.liteon.com/upload/download/DS86-2013-0014/LTR-507ALS-01_FINAL%20DS.pdf#page=26

The ALS_IRF_CUT_OFF register does not seem to exist in later sensors from LiteOn.

 

Automatic gain selection

The code contains a unique function called autoSelectGain(long lux) which automatically sets the optimal gain (x1 .. x200) for the lux value returned by readLux(). It returns true if the gain was changed. If saturation or overflow has occurred (lux = -1) it will set the range to 0 (0..65535 lux, the default), so the next read will (hopefully) return a valid value. See the example sketch.

The code could be modified to switch between only two gain settings instead of four, e.g. 1x for high lux values (0..65535) and 200x for low lux values (0..326). This may make more sense.

 

I2C slave address

The address is selected by connecting the SEL pin to GND, VCC or leaving it floating. See JP3.
Note that the Address Selection section on the SolderedElectronics website is wrong!

SEL

I2C Address

GND

0x3A (default for my SolderedElectronics board)

VCC (3.3V)

0x3B

floating

0x23

 

Proximity sensor's external IR LED

The Proximity Sensor (PS) requires an external infrared LED to be connected. It flashes the IR LED at a programmed rate. You can program the pulse frequency, peak current and pulse count with configurePS(). The reflected IR light determines the distance of an object, depending on its IR reflectivity. The distance is returned as an arbitrary value by readProximity(). This value is not in mm or cm, it's just the ADC value, so it must be calibrated/converted if you need an actual distance.

Connect the IR LED between the VLED output and VCC (+3.3V). The LED is turned on when the VLED output is pulled to GND by the chip. The LED current is limited by peakCurrent, so you don't need a series resistor.

ir-led

The readProximity() method returns the proximity detection distance as the raw ADC CH2 value (it's not cm or mm). The value increases as the distance decreases. The detection range is about 5..20cm, depending on reflectivity. overflow is set if the reflector is too close (<5cm) and the ADC value has overflowed (>2047).

 

LTR-507ALS data sheet

https://optoelectronics.liteon.com/upload/download/DS86-2013-0014/LTR-507ALS-01_FINAL%20DS.pdf

Here's the overview of the module on the SolderedElectronics website. On the left of the window you will see a table-of-contents where you can view the Overview, Hardware Details, How It Works, and their own Arduino Library (but my library is better ;-)  
https://soldered.com/documentation/ltr-507/overview/

 

MattLabs code

The C++ code was written and tested on a 32-bit STM32 Nucleo board, but it should run on most Arduino-style boards with minimal changes. My version provides more features than the official library, and I think it's easier to understand thanks to the copious comments. The code uses uint and ulong, the standard abbreviations for unsigned int and unsigned long. On a 32-bit device these are both 32-bit values. On a 16-bit device, uint is 16 bits and ulong is 32 bits, but everything should work for both.

 

// for uint and ulong 

typedef unsigned int uint;

typedef unsigned long ulong;

This code detects saturation when the lux value overflows the gain setting, or if there's too much infrared light. If the lux register value is 0 (ALS_DATA = 0) and the ADC register CH1 is not zero, the code returns a negative lux value (-1) so you can tell the difference between an invalid reading and total darkness. The other libraries do not seem to detect this. This also returns -1 if the ALS_IRF_CUT_OFF level is exceeded.

  LTR507ALSOpticalSensor.h   [Click to show/hide the code]

Here's a full sketch, which outputs various values to the Serial port and indicates some of the "problems". Note that it uses sprintf() with float support, if you don't have this you can use these floating point routines.

  LTR507ALS.ino   [Click to show/hide code]

 

References

This is the SolderedElectronics library for this chip. The same code is used for the "official" Arduino library.
https://github.com/SolderedElectronics/Soldered-Digital-Light-Sensor-Arduino-Library/tree/main/src
https://docs.arduino.cc/libraries/soldered-ltr-507-arduino-library/

I did not find any other libraries for this chip and it's no longer advertised on their website. However, LiteOn has a lot of new light sensors, which seem very good...
https://optoelectronics.liteon.com/upload/media/service/Publications/2017OpticalSensor/2017OpticalSensor.pdf
https://optoelectronics.liteon.com/en-global/led/index/Detail/926

Some LiteOn sensors can even detect the light intensity for different colors. This one contains 5 sensors for different wavelengths
https://optoelectronics.liteon.com/upload/download/DS86-2018-0007/LTR-381RGB-01_Final_DS_V1.8.PDF

 

TSL2591 (Adafruit) Dynamic Range Light Sensor

ams-osram-logo

tsl2591-1

Like most others, this chip contains two sensors. A broad spectrum photodiode (analog channel CH1) which registers both visible and infrared light, and a photodiode that registers mostly infrared light (analog channel CH2).

It runs from 3.3V. The maximum reading is 88000 lux, and they say the minimum reading is 377 microlux (0.000377 lux). It can generate a fully programmable interrupt from light intensity, duration and persistence.

 

I2C slave address

The I2C address is fixed at 0x29.

 

Calculating the LUX value

To get the human visible light level in Lux, we must process the raw CH0 and CH1 values. Unlike the LiteOn versions, this is not done by the chip itself, so you have to write code to do this. But this is problematic...

The data sheet states, "The digital output can be input to a microprocessor where illuminance (ambient light level) in lux is derived using an empirical formula to approximate the human eye response." But the data sheet does not provide this empirical formula. Each library I found uses a different formula and they all produce different results. My code contains the equations from all the libraries I found, so you can choose which algorithm you want to use, or you can develop your own. I found that the Waveshare library had the best results which were the closest to the readings from my cheap Voltcraft LX-10 Lux meter (*).

The Waveshare library lux calculation is the same as described in the Taos application note, apart from the CPL (counts-per-lux) divisor.

Below is the Mattlabs source code, with comments and links to each library.

The "counts-per-lux" value (CPL) is calculated just once when the chip is configured, from the gain and the integration time, see computeCpl(uint gain, uint time).

The floating point lux value is calculated from the raw readings of the two channels by float computeLux(unit ch0, uint ch1). It's not an integer value because the equations require floating point, but they could be converted to integers by using a multiplier.

// Compute the approximate lux value from the raw ALS data of both channels
// returns -1 on overflow
float TSL2591LightSensor::computeLux(uint ch0, uint ch1)
{
	// overflow, return -1 (< 0)
	if (ch0 == 0xffff || ch1 == 0xffff) {
		return -1.0f;
		// OR you could return "not-a-number", a float NAN
		// you must use isnan(f) to check for this return value
		//return NAN;
	}

	// prevent divide-by-zero, assume 0 lux
	// (ch0 is visible light)
	if (ch0 == 0 || fcpl == 0.0f) {
		return 0.0f;
	}

	// convert raw readings to float, just once
	float fch0 = (float)ch0;	// ambient light + infra-red
	float fch1 = (float)ch1;	// mostly infra-red

	// Here are three different LUX calculations from the TSL2591 libraries
	// In comparison with a cheap Voltcraft LX-10 lux meter, the Waveshare 
	// formula is the closest match

	// This is the original calculation from the first Adafruit library.
	// NOTE: The latest Adafruit library uses a different formula (see below). 
	// It calculates two linear slopes which cross over each other. 
	// It does not know which slope to use, so it takes the higher reading.
	// https://github.com/adafruit/Adafruit_TSL2591_Library/blob/master/Adafruit_TSL2591.cpp#L278
	// See "Figure 6 – Excel Lux Equation using Multiple Points" in
	// https://look.ams-osram.com/m/2fbeab1da7b52219/original/AmbientLightSensors-AN000173.pdf
	// luxa = flourescent/incandescent/sunlight light
	// luxb = dim incandescent light or dim sunlight
	float luxa = (fch0 - (fch1 * 1.64f)) / fcpl;
	float luxb = ((fch0 * 0.59f) - (fch1 * 0.86f)) / fcpl;
	// choose the highest reading
	float lux1 = luxa > luxb ? luxa : luxb;

	// The new formula used by the Adafruit library - I think it's wrong
	// see https://github.com/adafruit/Adafruit_TSL2591_Library/issues/14
	// https://github.com/adafruit/Adafruit_TSL2591_Library/blob/master/Adafruit_TSL2591.cpp#L220
	// DIVIDE BY ZERO IF ch0 = 0 (I fixed this by checking ch0 above)
	float lux2 = ((fch0 - fch1) * (1.0f - (fch1 / fch0))) / fcpl;

	// The 'Waveshare' formula
	// https://github.com/waveshare/TSL2591X-Light-Sensor/blob/master/Arduino/TSL2591_Light_Sensor/TSL2591.cpp#L180
	// It is very similar to the original Adafruit library version above.
	// It's the example formula from "Using the Lux equation":
	// https://look.ams-osram.com/m/2868a9784356cfc6/original/AmbientLightSensors-AN000170.pdf
	// cpl = (atime * again) / (GA * DF) -> (atime * again) / 762.0
	//   GA = glass attenuation factor
	//   DF = device factor (experimentally derived, varies by device type), uses 53 
	// luxa = (ch0 - (2 * ch1)) / cpl;
	// luxb = ((0.6 * ch0) - ch1) / cpl;
	// THE WAVESHARE LIBRARY CODE SILENTLY SETS luxb TO 0! THIS IS VERY NAUGHTY!
	float lux3a = (fch0 - (2.0f * fch1)) / fcpl;
	float lux3b = ((0.6f * fch0) - fch1) / fcpl;
	float lux3 = lux3a > lux3b ? lux3a : lux3b;		// the Waveshare library code sets luxb to 0!

	#if 1
        // print all results
	char buf[100];
	sprintf(buf, "TSL2591 : lux1=%.03f  lux2=%.03f  lux3=%.03f", lux1, lux2, lux3);
	Serial.println(buf);
	#endif
 
	// on comparison with a cheap Voltcraft LX-10 lux meter, lux3 is the closest match
	float lux = lux3;

	// lux can go -ve with very low light levels
	if (lux < 0.0f)
		lux = 0.0f;

	return lux;
}

// Compute counts-per-lux for lux calculation
// this is stored in 'fcpl' to save re-calculating it every time
float TSL2591LightSensor::computeCpl(uint again, uint atime)
{
	const float fgains[4] = { 1.0f, 25.0f, 428.0f, 9876.0f };
	float fgain = fgains[again];
	float ftime = (float)((atime + 1) * 100);	// integration time in ms

	// cpl = (atime_ms * again) / (GA * DF)  //GA=7.698  DF=53??
	// GA is Glass Attenuation 
	// DF is Device Factor (53?)
	// Adafruit library uses 408
	// Waveshare library uses 762 <- we use this one

	return (fgain * ftime) / 762.0f;
}

 

Here's the algorithm from the TSL2571 data sheet (the TSL2591 data sheet does not contain the algorithm). This is the formula used by the Waveshare library.

The counts-per-lux value (CPL) needs to be calculated only once, when ATIME or AGAIN is changed. The first line of the equation (Lux1) covers fluorescent and incandescent light. The second line (Lux2) covers dimmed incandescent light. The final lux value is the maximum of Lux1, Lux2 or 0. GA is the "glass attenuation", 53 is the "device factor" (DF). The Waveshare formula uses 762 for (GA x DF).

CPL = (ATIME_ms × AGAINx) / (GA × 53)
Lux1 = (C0DATA - 2 × C1DATA) / CPL
Lux2 = (0.6 × C0DATA - C1DATA) / CPL
Lux = MAX(Lux1, Lux2, 0)

https://look.ams-osram.com/m/15d3c36953a9482e/original/TSL2571-DS000114.pdf#page=15

 

Here's a plot for three different light sources, for the TSL2771 chip, which is similar. It shows two linear plots for different light sources, which cross each other. That's probably why the library simply returns the largest value, because it does not know which plot to use.

tsl2771-lux-equation

Taken from
https://look.ams-osram.com/m/2fbeab1da7b52219/original/AmbientLightSensors-AN000173.pdf

 

Other people have had trouble with lux conversion, it's not just me. Here's some of the discussions I found...

https://forum.arduino.cc/t/question-about-gain-on-sensor/665392/17
https://github.com/adafruit/Adafruit_CircuitPython_TSL2591/issues/7
https://github.com/adafruit/Adafruit_TSL2591_Library/issues/14
https://forums.adafruit.com/viewtopic.php?t=85343&start=15

 

TSL2591 data sheets

https://look.ams-osram.com/m/c901de8e97608f8/original/TSL2591-DS000338.pdf

Link for the Adafruit module
https://learn.adafruit.com/adafruit-tsl2591

 

Mattlabs code

The C++ code was written and tested on a 32-bit STM32 Nucleo board, but it should run on most Arduino-style boards with minimal changes. My version provides more features than the official library, and I think it's easier to understand thanks to the copious comments. The code uses uint and ulong, the standard abbreviations for unsigned int and unsigned long. On a 32-bit device these are both 32-bit values. On a 16-bit device, uint is 16 bits and ulong is 32 bits, but everything should work for both.

The comments in the code should make it easy to understand, and there are nice descriptions of the registers in the data sheet.

 

  TSL2591LightSensor.h   [Click to show/hide the code]

Here's an example sketch, which outputs various values to the Serial port. Note that it uses sprintf() with float support, if you don't have this you can use these floating point routines.

  TSL2591.ino   [Click to show/hide the code]

 

Other libraries

The Adafruit library is a bit "flaky", the Waveshare is better (but not as good as mine ;-)

https://github.com/adafruit/Adafruit_TSL2591_Library/tree/master

https://github.com/waveshare/TSL2591X-Light-Sensor/tree/master

 

Useful ams-Osram/Taos Documents

Osram has many useful application notes about its ambient light sensors which you can find here
https://ams-osram.com/products/sensor-solutions/ambient-light-color-spectral-proximity-sensors/ams-tsl25911-ambient-light-sensor#tab/documents

These are great for learning about ALS sensors, for example:

Ambient light sensing (ALS)
https://look.ams-osram.com/m/5ad3bfdb6fc8c8d6/original/AmbientLightSensors-AN000172.pdf

Auto gain algoritm
https://look.ams-osram.com/m/436f822ba14feb1b/original/AmbientLightSensors-AN000171.pdf

Using the Lux equation
https://look.ams-osram.com/m/2868a9784356cfc6/original/AmbientLightSensors-AN000170.pdf

Developing a custom Lux Equation
https://look.ams-osram.com/m/2fbeab1da7b52219/original/AmbientLightSensors-AN000173.pdf

 

And now for something completely different - a few cheap-o (or expensive-o) analog sensors...

Note that a simple Light Dependent Resistor LDR is often just as good, see my comments below.

 

DFRobot Ambient Light Sensor SKU DFR0026

dfr026-1

Not recommended.

This module uses an analog phototransistor. It uses a "PT550 compatible" chip. Originally the PT550 was made by Sharp, but it's now discontinued. See the references links below. For some reason this module is more expensive than the excellent BH1750 I2C digital sensor.

The module's output can be connected directly to an analog input. It runs on 3.3 or 5V. The documentation says it reads up to 6000 lux, but my tests showed its maximum output voltage (saturation) occurred at about 1850 lux (LED lighting), and its output was not linear below 40 or above 1700 lux. Powering it with 3.3V or 5V did not affect the range.

The module's circuit uses a very simple voltage divider, either in 'common collector' or 'common emitter' mode, see the schematics below. The 10K series resistor R2 may be too high, limiting the max. current to 330uA at 3.3V. Is that OK?

To save a lot of messing about (unless you like that sort of thing), maybe use a Light Dependent Resistor (LDR) instead of this sodule. LDRs have a far wider range, and you can buy dozens of them for the price of this podule. See Using a cheap LDR.

 

dfr0026-range

 

One good point is that it is reasonably linear within the range 40..1800 lux, so you can use a simple re-scaler to convert analog input counts to lux. But only if the very limited lux range is suitable.

 

// Rescale from one linear range to another, e.g. from 0..1023 to 0..2000

// note: on a 32-bit machine use 'long long' (or int64_t) instead of 'long' to prevent multiplication overflow

int rescale(int value, int inmin, int inmax, int outmin, int outmax)

{

    return outmin + ((long)(value - inmin) * (long)(outmax - outmin)) / (inmax - inmin);

}

 

// Time Loops are real!

void loop() 

{

    int count = analogRead(A0);

    

    // just for fun, calculate the voltage in millivolts from the input reading

    // 10 bits, 0..1023, 3.3v device

    unsigned long mv = (3300L * count) / 1023;

 

    // count 156 = 40 lux, count 978 = 1820 lux

    int lux = rescale(count, 156, 978, 40, 1820);

    if (lux < 0)

        lux = 0;

 

    char buf[100];

    sprintf(buf, "count=%u millivolts=%lu lux=%d", count, mv, lux);

    Serial.println(buf);

 

    delay(200);

}

 

Note! The pin connections on your DFR0026 board may not match - and the circuits may be different too! (Mine were different, on both counts!)

There are several versions of this board with different connector pins and common collector or common emitter sensor wiring.

This is the schematic from the DFRobot website. It uses common emmitor mode and shows Pin 1 = VCC, Pin 2 = GND and Pin 3 = analog signal.

dfr0026-schematic

My DFRobot board uses common collector mode and has Pin 1 = analog signal S, Pin 2 = VCC and Pin 3 = GND!
(The bypass cap C1 is fitted but I forgot to draw it.)
But it doesn't really matter because we'll all be down the pub soon.

drf0026-ckt

 

The connection diagram on the website shows this: Pin 1 = analog signal S, Pin 2 = VCC and Pin 3 = GND. (On my cable, the S signal wire was blue, not green.)

drf0026-1

And I found this diagram, which again does not match the website's schematic. Bang! Thankfully, the board I have has the "new version" connector.

dfr0026-versions

DFRobot module

https://wiki.dfrobot.com/DFRobot_Ambient_Light_Sensor_SKU_DFR0026
 

Chinese PT550 data sheet

https://dfimg.dfrobot.com/enshop/image/data/DFR0026/XYC-PT5I850AC-A4xls.pdf

 

Original Sharp PT550 phototransistor data sheet

https://mm.digikey.com/Volume0/opasdata/d220001/medias/docus/1/pt550.pdf

 

TEMT6000 Analog Ambient Light Sensor

temt6000-iduino temt6000-sparkfun  

This is the most common analog sensor. It is better than the DFR0026, being a good quality Vishay TEMT6000 sensor, running on 3.3 or 5V, which can be connected directly to an analog input.

Its response is linear up to almost 3000 lux. You'll need a digital sensor (or LDR :-) to go higher.

Here's the code I used to get a pretty accurate lux reading with a 10-bit analog input. Note that it uses sprintf() with float support, if you don't have this you can use these floating point routines.

 

uint maxCount;

 

void setup()

{

  Serial.begin(115200);

  delay(1000);

  Serial.println("\n\n\rSTM32F103 Started...\n\n\r");

 

  pinMode(A0, INPUT);

  uint resolution = 10;

  analogReadResolution(resolution);

  maxCount = (1 << resolution) - 1;

}

 

 

void loop()

{

  int count = analogRead(A0);

 

  // millivolts, for reference only

  unsigned long mv = (3300L * count) / maxCount;

 

  // integer value, see above for rescale()

  int lux = rescale(count, 26, 600, 26, 2000);

  if (lux < 0)

    lux = 0;

 

  // float value from Excel linear equation

  // y = 3.4716x - 75.22

  float flux = 3.4716f * (float)count - 75.22f;

  if (flux < 0.0f)

    flux = 0.0f;

 

  char buf[100];

  sprintf(buf, "count=%u millivolts=%lu lux=%d  flux=%.02f", count, mv, lux, flux);

  Serial.println(buf);

 

  delay(200);

}

temt6000-ckt

Data sheets

https://www.vishay.com/docs/81579/temt6000.pdf

https://protosupplies.com/product/temt6000-ambient-light-sensor-module/

 

Routines to convert float to string and string to float (or double)

The floating point string conversion routines are large, and often controllers with small memories do not support them. Here are some methods to do the job for you. They are smaller and faster than the often-used dtostrf(). There are routines for converting doubles too, but you may not need them. If you have atof() you could use that, but it's quite big.

To keep the code as short as possible, the maximum value accepted is +-4294967000.0 (unsigned long minus rounding errors) if the value is larger it returns false with "NAN" in *psz.

The output format is 'n.n'. Exponents ('e+04' etc) are not handled. The max. number of decimal places is 6 for float and 8 for double. But note that float holds only 6 or 7 significant digits so the value may be truncated and rounded up.

 

bool floatToString(float value, char* psz, int decimalPlaces);
bool doubleToString(double value, char* psz, int decimalPlaces);
bool stringToFloat(const char* psz, float* result, char** pszEnd);
bool stringToDouble(const char* psz, double* result, char** pszEnd);

 

  Floating point routines   [Click to show/hide the code]

 

 

<are you down the pub yet? - ed>
<just give me 10 minutes to change out of my pyjamas - matt>