Digital Lux Meter using Arduino - Measuring Light Intensity

In this blog post I will show how you can build a Digital Lux Meter with Arduino designed to measure light intensity. It uses an LDR (Light Dependent Resistor) as the primary sensor, an LM358 Op-Amp for signal conditioning, and an Arduino Uno to process the data and display it on an OLED screen.

Applications

The Digital Lux Meter is a versatile tool used across various industries to ensure optimal lighting conditions, ranging from agricultural monitoring, where it helps maintain the specific light intensity required for greenhouse crops, to workplace ergonomics, ensuring offices and schools meet safety standards to prevent eye strain. In the field of photography and cinematography, it allows creators to balance exposure and achieve consistent scene lighting, while in energy management, it can be integrated into "smart home" systems to automate lighting based on natural ambient levels, significantly reducing electricity consumption. 

Furthermore, for electronics hobbyists and developers, this project serves as a practical application of signal conditioning using op-amps, providing a high-precision method for testing LED brightness or calibrating light-sensitive automated systems.

Hardware and Circuit Schematic

The Arduino based Lux meter circuit diagram is as shown below:

lux meter circuit diagram

Now I will explain how this lux meter circuit works.

How the Circuit Works

1. The Sensor Stage (LDR + Voltage Divider)

The LDR1 and the potentiometer RV2 form a variable voltage divider.

  • LDR1: Its resistance decreases as light intensity increases.

  • RV2: This acts as a sensitivity adjustment. By changing its resistance, you change the "baseline" voltage that goes into the Op-Amp.

2. The Signal Conditioning (LM358 Op-Amp)

The LM358 is configured as a Non-Inverting Amplifier.

  • Input: The voltage from the LDR divider enters Pin 3 (Non-inverting input).

  • Gain Control: RV1 potentiometer is connected in the feedback loop. By adjusting RV1, you control the Gain (amplification factor) of the circuit. This allows you to stretch or shrink the voltage range that the Arduino sees at pin A0.

3. The Processing & Display (Arduino + OLED)

  • The Arduino reads the analog voltage from the Op-Amp output (Pin 1) via pin A0.

  • The code converts this 0–5V analog signal into a digital value (0–1023) and then maps it to a "Illuminance" value (0–1000 lm).

  • The SSD1306 OLED displays the calculated Luminous Flux and the raw Reference Voltage (Vref).

How to Tune RV1 and RV2 for the Proper Range

Tuning this circuit is about balancing Sensitivity (RV2) and Scale (RV1).

Step 1: Set the Dark Baseline (RV2)

  • Action: Cover the LDR so it is in total darkness.

  • Tuning: Adjust RV2 until the OLED shows a value very close to 0 lm and the Vref is at its lowest point (around 0.0V or 0.1V).

  • Purpose: This ensures your meter starts at zero when there is no light.

Step 2: Set the Maximum Brightness (RV1)

  • Action: Shine your brightest light source (or the "Torch") directly onto the LDR.

  • Tuning: Adjust RV1 (the Gain pot) until the OLED displays your desired maximum value (e.g., 900–1000 lm). You want the Vref to be near 4.5V to 4.8V at maximum light.

  • Note: If Vref hits 5.0V too early, turn RV1 down. If the value is too low even in bright light, turn RV1 up to increase the gain.

Step 3: Verify the Mid-Range

  • Move the light source to a medium distance. If the reading drops too fast or stays too high, slightly tweak RV1 again.

  • Final Calibration: Once you are happy with the physical tuning, update your Arduino code's map function to match your results:

             int lumenValue = map(rawValue, [Your_Low_Raw], [Your_High_Raw], 0, 1000);

Summary of Potentiometer Roles

ComponentFunctionEffect
RV2SensitivityShifts the whole range up or down. Controls the "Zero" point.
RV1Gain (Range)Expands or contracts the scale. Controls how high the "Max" goes.

Programming & Code

The following is the program code:
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

#define SCREEN_WIDTH 128 
#define SCREEN_HEIGHT 32 
#define OLED_RESET     -1 
#define SCREEN_ADDRESS 0x3C 

Adafruit_SSD1306 OLEDLCD(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, 400000, 400000, OLED_RESET);

const int sensorPin = A0;

void setup() {
  if(!OLEDLCD.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
    for(;;); 
  }
  
  OLEDLCD.clearDisplay();
  OLEDLCD.setTextColor(SSD1306_WHITE);
  OLEDLCD.setTextSize(2);
  OLEDLCD.setCursor(10, 0); 
  OLEDLCD.print(F("LUX METER")); 
  
  OLEDLCD.setTextSize(1);
  OLEDLCD.setCursor(28, 22); 
  OLEDLCD.print(F("ee-diary.net"));
  OLEDLCD.display();
  delay(3000); 
}

void loop() {
  int rawValue = analogRead(sensorPin);
  float voltage = rawValue * (5.0 / 1023.0);
  
  int luxValue = map(rawValue, 11, 719, 0, 1000);
  luxValue = constrain(luxValue, 0, 1000);

  OLEDLCD.clearDisplay();
  
  // --- HEADER ---
  OLEDLCD.setTextSize(1);
  OLEDLCD.setCursor(0, 0);
  OLEDLCD.print(F("Illuminance:"));

  // --- MAIN READING (BIG) ---
  OLEDLCD.setTextSize(2);
  OLEDLCD.setCursor(0, 14);
  OLEDLCD.print(luxValue);
  
  // --- DYNAMIC UNIT (SMALL) ---
  OLEDLCD.setTextSize(1);
  int unitX;
  // Large digits (size 2) are 12 pixels wide
  if (luxValue >= 1000) unitX = 48;      
  else if (luxValue >= 100) unitX = 36;  
  else if (luxValue >= 10) unitX = 24;   
  else unitX = 12;                      
  
  // Setting Y to 20 so the small "lx" aligns with the bottom of the big numbers
  OLEDLCD.setCursor(unitX + 2, 20); 
  OLEDLCD.print(F("lx")); 

  // --- FORMATTED Vref (SMALL) ---
  OLEDLCD.setCursor(75, 22); 
  OLEDLCD.print(F("Vref:"));
  OLEDLCD.print(voltage, 1); 
  OLEDLCD.print(F("V"));      

  OLEDLCD.display();
  delay(250); 
}
  
This code is designed to read a varying analog voltage from your LDR/Op-Amp circuit, convert it into a meaningful light intensity value, and display it on an I2C OLED screen.

Here is the breakdown of how the program works section by section:

1. Libraries and Definitions

#include <Wire.h>               // Handles I2C communication
#include <Adafruit_GFX.h>       // Core graphics library
#include <Adafruit_SSD1306.h>   // Specific library for the OLED driver
  • Wire: Essential because the OLED uses only two wires (SDA/SCL) to talk to the Arduino.

  • SSD1306: This manages the specific pixels on your 128x32 display.

2. The Setup Function (Initialization)

void setup() {
  OLEDLCD.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS);
  // Startup screen logic...
}
  • OLEDLCD.begin(): Wakes up the display. If the address (0x3C) is wrong, the screen stays black.

  • Startup Screen: We show the "LUX METER" title and your website URL for 3 seconds. This gives the LDR and Op-Amp time to stabilize their voltages before the first reading.

3. Data Acquisition (The loop)

int rawValue = analogRead(sensorPin);
float voltage = rawValue * (5.0 / 1023.0);
  • analogRead: Converts the voltage on Pin A0 (0V to 5V) into a number between 0 and 1023.

  • Voltage Calculation: This math converts that 0–1023 number back into a human-readable voltage (e.g., 0.9V) so you can see exactly what the Op-Amp is outputting.

4. Calibration & Mapping

int luxValue = map(rawValue, 11, 719, 0, 1000);
luxValue = constrain(luxValue, 0, 1000);
  • map: This is the heart of the "Meter." It takes your specific circuit range (where 11 is "dark" and 719 is "bright") and scales it to a clean 0–1000 Lux scale.

  • constrain: This prevents the display from showing weird negative numbers or values over 1000 if the light gets unexpectedly bright or dark.

5. Dynamic Display Logic

This is the most complex part of your specific code:

if (luxValue >= 1000) unitX = 48;      
else if (luxValue >= 100) unitX = 36;  
// ... and so on
  • The "Sticky" Unit: Since setTextSize(2) characters are 12 pixels wide, we calculate the width of the digits.

  • unitX: This moves the cursor for the "lx" unit so it always stays exactly next to the numbers, whether the reading is 5 or 555.

6. Rendering the Vref

OLEDLCD.setCursor(75, 22); 
OLEDLCD.print(F("Vref:"));
OLEDLCD.print(voltage, 1); 
  • Placement: By setting the cursor to (75, 22), we push the technical info to the bottom-right corner.

  • voltage, 1: The ,1 tells the Arduino to only show one digit after the decimal point (e.g., 0.9), which keeps the text short and prevents it from overlapping with your Lux reading.

7. The Final Push

OLEDLCD.display();
  • Crucial Step: All the print commands only draw to the Arduino's internal memory (the buffer). The display() command is what actually sends all that data over the I2C wires to make the pixels light up on the physical screen.

Video demonstration




Related tutorials



Post a Comment

Previous Post Next Post