Photoresistors, also known as Light-Dependent Resistors (LDR), are a class of resistors that change their resistance in relation to the amount of light falling on the sensor. LDRs are made from photoconductive materials, which conduct more electricity as the amount of light falling on them increases. Conductance is the inverse of resistance, so LDRs become less resistant as the amount of light increases, and more resistant as the amount of light decreases.
LDRs do not respond rapidly to changing light levels. Unlike photodiodes and phototransistors, which can detect a change in light in a matter of nanoseconds or microseconds, LDRs require a few milliseconds to detect a change. Thus, LDRs are typically used in situations where the overall amount of ambient light, and not response time, is important, such as camera light sensors, street lights, and even heart rate monitors. These applications work on a millisecond or greater order of magnitude, and thus do not require the rapid response seen in other light-sensing devices.
A typical configuration for measuring the value of a photoresistor is to place it in a voltage divider with another resistor and measure the voltage at the divider. Depending on if the LDR is connected to the voltage source or ground, the voltage at the divider will be either directly or inversely proportional to the amount of light striking the sensor. Having a capacitor between the voltage divider and ground will help keep the signal smooth but is not strictly necessary and increases the response time.
Photoresistor CharacterizationIt is necessary to characterize the LDR's response to different illumination levels before it can be used for measurements. This requires that some measure of brightness is made, whether it is in subjective terms such as "dark", "bright", and "brightest", or in absolute values measured by some other device. Conveniently, most people have access to a light-measurement device in the form of their cell phones because ambient light sensors (ALS) are key for automatic screen brightness management as well as some other functions. In order to perform the characterization described in this article, it is necessary to download an app that can read out the brightness of a light in lux. Both Google Play and the Apple Store contain various apps that allow users to access the data output from the sensors the phone manufacturers chose to include. Two such apps are Sensors Multitool (Android) and Lirum Device Info Lite (iPhone).
The following steps will describe the process of characterizing a photoresistor and generating equations describing its response to different light levels. These equations will then be applied to Arduino code.
Note that it's best to use white light sources because using a specific color may not provide the true response of the photoresistor. If an RGB LED is available, follow this guide to vary its brightness. An RGB LED is an excellent way to get a wide variety of data points for this characterization, as it has 255 brightness levels with an Arduino (although it's not necessary to use all 255 steps). A bright bike light will provide good data towards the bright end of the scale.
- Build the characterization circuit as shown in the schematic and breadboard diagrams below:
- Connect the OpenScope oscilloscope channel 1 (solid orange) cable to the voltage divider where the resistor and the LDR are in contact (J1 on the schematic). Ground the striped orange cable.
- In WaveForms Live, under Trigger select the OFF button. Under OscCh 1, for Offset type in "2.5V". In the bottom left corner, press the MATH button, and select MEAN. A measurement with an orange mark should appear next to the MATH button. Leave all the other settings at their defaults.
- In the sensor app, navigate to the light sensor data readout. It should provide the light intensity measurement in units of lux. The image below shows the Sensors Multitool screen for the light sensor data.
- Locate the ALS on the phone. It is most likely near the top of the phone screen and can be located by moving a finger around on the phone's surface and finding the spot where the light measurement drastically drops.
- Turn on the dimmest light source and place it directly on top of the phone's sensor. Find the location where the measurement is highest. The graph in Sensors Multitool is useful for this part, since it will show the brightness readout for the last 10 seconds. Once the peak is found, take the average of about 10-15 values from the sensor output. Enter the average into a "Lux" column in Excel.
- Press Run in WaveForms Live, place the still-lit light source directly on the photoresistor, and use the graph to find the peak value. Save the Mean measurement in the bottom-left corner into a "Volts" column in Excel.
- Repeat the previous two steps with as many different-intensity light sources as possible.
- After gathering sufficient data points, plot the Lux and Volts columns in the y- and x-axis respectively. Use a scatter-plot graph with smooth lines.
- Right click the y-axis and select Format Axis. Click the leftmost icon (called Axis Options), check the "Logarithmic scale" box, and leave the Base option at "10". The plot should now resemble the one below, although the exact shape will depend on the characteristics of the specific LDR being tested.
- Right-click on a data point and select Add Trendline....
- Select either the "Exponential" or "Polynomial" radio buttons in the FormatTrendline menu and check the "Display Equation on chart" and "Display R-squared" boxes. The menu options are shown in the image below. If a polynomial trendline is used, set the Order value to 6.
At this point, the data will need to be broken up into smaller subsets, each of the subsets with their own trendlines. The microcontroller that will be monitoring the LDR has no way of knowing what level of brightness a particular voltage reading signifies. It will be converting the values it collects into units of lux based on some equations that it is programmed with. If the equations do not strongly correlate to the data points gathered, then the microcontroller will perform an inaccurate conversion. The LDR's response to changing light levels is not the same across its resistance range, so several trendlines are better at describing its behavior rather than a single line.
The goal with each trendline is to make the R² values as close to 1 as possible. Depending on the particular data set used, either the exponential or the polynomial equations will generate the closest fits. The process of breaking up the data is subjective and may require a few tries to get the best result. Select the subsets based on what creates the best-fitting trendlines. For example, in the graph above, the curved portion on the left side would be a separate curve from the flat portion. The final characterization of the LDR used in this demonstration had 4 equations, each with an R² value greater than 0.994. The following steps will describe how to break up the data and generate equations for use with an Arduino (or any other programmable microcontroller).
- Note the letter labels for the "Lux" and "Volts" columns.
- Right-click on a data point on the chart and press the SelectData... button.
- In the dialog that pops up, under "Legend Entries (Series)" select Series1 and press Edit.
- Copy the "Series X values" line and press Cancel.
- Click Add in the "Select Data Source" dialog. Name the series something to indicate its position in the data set and paste the copied line into "Series X values". Change the last number on the line to something smaller, based on whatever patterns emerged.
- Copy the modified line into the "Series Y values" line and change the letters to match the lux column.
- Add a trendline to the new data set and check what the R² value is. Remember to try both the exponential and polynomial trendlines. If the R² value is still too small, the data range will need to be reduced.
- Repeat this process with the rest of the data, setting the start of the next data set at the end of the previous set. For this demonstration, the data was broken up as follows: data points 1-3, 4-19, 20-34, 35-37.
- Uncheck the box next to Series1 to hide the original full-span data set.
- Set all the trendlines to display their equations.
- Right click on an equation and select FormatTrendline Label....
- Click the leftmost icon (called the LabelOptions) and select "Number" from the Category drop-down menu. Set Decimalplaces to "8".
- Repeat the last 2 steps for each trendline equation.
- Select FormatTrendline... and under Forecast find the Forward and Backward values. Change these values on all of the trendlines until they overlap or come very close to overlapping. See the images below for an idea of how they should connect.
- Determine the voltage value (x-axis) at which each trendline ends, starting with the lowest-value one. This can be done by taking the forward (or backward) forecast value for that trendline and adding (or subtracting if taking the backward forecast) it to the voltage of the closest data point in that trendline. For example, in the first image above the voltage of the closest data point for the yellow trendline is 0.35V, and the forward forecast is 0.2, so the endpoint for the trendline is 0.55V. Alternatively, the closest data point for the blue trendline is 0.844V, the backwards forecast is 0.31, so the endpoint for the trendline would be 0.53V. The difference is negligible, so pick whichever direction seems closer to the curve of the data. The trendline intersection points will become the voltage thresholds at which the code will switch from one equation to another.
- Copy the equations into the Arduino code below: replace the bracketed arguments after each "lux =" with the equations (be sure to remove the brackets). If more than 4 equations are used, add more else if() commands.
- Replace all instances of "x" in the equations with "voltage".
- Replace any polynomial terms in the equations with pow(voltage, [VALUE OF POLYNOMIAL]) and the exponential terms with exp([VALUE IN EXPONENTIAL TERM] * voltage). For example, 3x³ would become 3*pow(voltage, 3), and e^(2.5x) would become exp(2.5 * voltage).
- Replace every instance of "[THRESHOLD]" in the code with the voltage threshold correlating to the relevant trendline equation.
//place at the start of the sketch to define analog in pin
const int luxInput = [ANALOG IN PIN]
//performs a reading of the photoresistor and converts the voltage to lux
float readBrightness(){
//store the potentiometer position as a float
float level = analogRead(luxInput);
//calculate analog voltage
float voltage = 5*level/1024;
//ensure the voltage isn't outside the acceptable range
if(voltage < 0){
voltage = 0;
}
else if(voltage > 5){
voltage = 5;
}
//initialize a lux value
float lux = 0;
//based on the voltage, select the equation that will best convert it to a lux value
if(voltage <= [THRESHOLD]){
lux = [ENTER LOWEST DATA SET EQUATION HERE];
}
else if(voltage <= [THRESHOLD]){
lux = [ENTER NEXT DATA SET EQUATION HERE];
}
else if(voltage <= [THRESHOLD]){
lux = [ENTER NEXT DATA SET EQUATION HERE];
}
else{
lux = [ENTER LAST DATA SET EQUATION HERE];
}
//ensure the lux value isn't less than 0
if(lux < 0 ){
lux = 0;
}
return lux;
}
The function readBrightness() can now be used to measure the amount of light falling on an LDR and return the value in the form of a float. It can be copied into any Arduino sketch, provided that the "[ANALOG IN PIN]" portion in the analogRead() command is replaced with the correct pin number. Also, be sure that none of the variable names used in this function are duplicated as global variables.
Samples from DemonstrationBelow is the data that was collected for this demonstration, the graph that was generated from the data after subset trendlines were applied, and the readBrightness() function with the LDR-specific equations and thresholds applied.
//performs a reading of the photoresistor and converts the voltage to lux
float readBrightness(){
//store the potentiometer position as a float
float level = analogRead(luxInput);
//calculate analog voltage
float voltage = 5*level/1024;
//make sure the voltage isn't outside the acceptable range
if(voltage < 0){
voltage = 0;
}
if(voltage > 5){
voltage = 5;
}
//initialize a lux value
float lux = 0;
//based on the voltage, select the equation that will best convert it to a lux value
if(voltage <= 0.534){
lux = 1286.47506604*pow(voltage,2) + 178.51342186*voltage - 1.70982528;
}
else if(voltage <= 2.9){
lux = 197.90983722*exp(1.62826391*voltage);
}
else if(voltage <= 4.1){
lux = -3785.88415527*pow(voltage,6) + 85678.15521655*pow(voltage,5) - 604387.00259741*pow(voltage,4) + 1410432.63046882*pow(voltage,3) + 1529553.55029751*pow(voltage,2) - 11122170.790418*voltage + 12440209.3288285;
}
else{
lux = 23.9858393*exp(2.28356737*voltage);
}
//ensure the lux value isn't less than 0
if(lux < 0 ){
lux = 0;
}
return lux;
}
Further Characterization StepsThe characterization can be taken one step further to make it more universal. The voltage values generated with the process given above are depend on the 220Ω resistor in the divider. By applying the voltage divider formula and solving for the upper resistor, the resistance of the LDR can be found for each lux value. The equation for calculating an unknown resistance, R1, in a voltage divider will be derived below, but trendline application and coding will be left to the reader.
Refer to the image below for a schematic of a voltage divider.
To find the voltage at the divider, Vout, first find the current, I, flowing though R2:
Substitute I and rearrange to move the resistor sum (R1+R2) out of the denominator:
Now solve for R1:
Substitute 5V for Vin and 220Ω for R2 and enter this equation into Excel. Vout will be the values in the "Volts" column. R1 is the LDR's resistance. The rest of the process is the similar to the one described in this article, but instead of correlating the voltage measurement with lux, the equations this process will yield will correlate the LDR's resistance with lux.
Comments