Explore Analog-to-Digital Conversion (ADC) and PWM control by building an Automated Light Compensation System on the ESP32. An LDR serves as the analog input, feeding raw data to the ESP32. This data is inversely mapped to the duty cycle of four distinct hardware PWM channels, enabling smooth, synchronous dimming of four output LEDs. The system’s operational state is concisely communicated through an I2C 16×2 LCD, informing the user whether the environment is classified as “LIGHT” or “DARK” based on a pre-set threshold.
Key Concepts:
- LDR as Analog Input (ADC): Reading the variable resistance of the LDR to measure light level.
- Inverse Mapping: Converting the LDR value (low resistance = high light) to PWM brightness (high duty cycle = high brightness) in reverse.
- PWM Channels: Using multiple LEDC channels on the ESP32 to independently control the brightness of all four LEDs.
- I2C Communication: Displaying real-time status on the LCD.
Circuit Connections

| Components | ESP32 Dev Module |
| LCD 16×2 I2C (SDA, SCL) | SDA, SCL |
| LDR Sensor | GPIO34 |
| BLUE LED | GPIO16 |
| RED LED | GPIO17 |
| YELLOW LED | GPIO18 |
| GREEN LED | GPIO19 |
Logic Flow
- LDR resistance changes with ambient light (Dark = High Resistance; Light = Low Resistance).
- The ESP32 reads the resulting voltage on GPIO 34 (0-4095 range).
- The raw LDR value is inversely mapped to the PWM duty cycle.
- High LDR Value (Bright Light) rightarrow Low PWM Value (LEDs OFF)
- Low LDR Value (Dark) rightarrow High PWM Value (LEDs ON)
- The brightness value is applied to four separate LEDC channels (Ch. 0-3) to control the four LEDs simultaneously.
- A simple threshold determines the textual status on the LCD: “DARK” or “LIGHT”.
Code Lab
Step 1: Install Libraries
You need to download library for the LCD:
LiquidCrystal_I2C.hby Frank de Brabander (Install via Arduino Library Manager).

Step 2: Copy and Paste Code
Copy and paste this code into the Arduino IDE:
#include <LiquidCrystal_I2C.h>
const int ldrPin = 34; // Analog Input for LDR
const int ledPins[] = {16, 17, 18, 19}; // Array of 4 LED pins
const int numLeds = 4;
const int darkThreshold = 2000; // Threshold to define 'DARK' (adjust as needed, typically 1000-3000)
// The I2C address is usually 0x27 or 0x3F. Try 0x3F if 0x27 doesn't work.
LiquidCrystal_I2C lcd(0x27, 16, 2);
const int freq = 5000; // 5 KHz frequency
const int resolution = 8; // 8-bit resolution (Duty Cycle: 0-255)
void setup() {
Serial.begin(115200);
lcd.init();
lcd.backlight();
lcd.setCursor(0, 0);
lcd.print("Light Status:");
// Setup all four LED PWM channels
for (int i = 0; i < numLeds; i++) {
// 1. Configure the PWM properties for channel 'i'
ledcSetup(i, freq, resolution);
// 2. Attach the LED pin to channel 'i'
ledcAttachPin(ledPins[i], i);
}
Serial.println("Smart Light System Initialized!");
}
void loop() {
int ldrValue = analogRead(ldrPin);
int brightness = map(ldrValue, 0, 4095, 255, 0);
brightness = constrain(brightness, 0, 255);
for (int i = 0; i < numLeds; i++) {
ledcWrite(i, brightness);
}
String status;
if (ldrValue < darkThreshold) {
status = "DARK "; // 'DARK' requires the lights to turn on (high brightness)
} else {
status = "LIGHT "; // 'LIGHT' requires the lights to turn off (low brightness)
}
lcd.setCursor(0, 1);
lcd.print("Status: ");
lcd.print(status);
Serial.print("LDR Value: ");
Serial.print(ldrValue);
Serial.print(" -> Brightness: ");
Serial.println(brightness);
delay(50); // Small delay for stable readings and I2C communication
}
Testing and Troubleshooting Guide
How to Test
- Upload: Upload the code to your ESP32.
- Monitor: Open the Serial Monitor (set to 115200 baud) to see the LDR and brightness values.
- Control: Test the system by changing the ambient light:
- Cover the LDR: The
LDR Valuewill drop (e.g., 100 to 1000). TheBrightnesswill increase (e.g., 150 to 255), and the LCD should display “Status: DARK”. - Shine a Light on the LDR: The
LDR Valuewill rise (e.g., 3000 to 4000). TheBrightnesswill drop (e.g., 50 to 0), and the LCD should display “Status: LIGHT”.
- Cover the LDR: The
Common Issues and Solutions
| Problem | Observation | Solution |
| LEDs are ON in the light | The brightness control is reversed. | Fix Mapping: Change map(ldrValue, 0, 4095, 255, 0) to map(ldrValue, 0, 4095, 0, 255) in the code (though the current code is already correct for inverse control). |
| LCD displays nothing | Screen is blank or shows squares. | Check Address: The address is likely 0x3F instead of 0x27. Change LiquidCrystal_I2C lcd(0x27, 16, 2); to LiquidCrystal_I2C lcd(0x3F, 16, 2);. |
| LDR reading is stuck | LDR Value is always 0 or 4095 in the Serial Monitor. | Check Wiring Connection: Ensure that the signal pin is connect to GPIO34. |
| Only one LED works | Some LEDs light up, but others don’t dim. | Check Channels: Verify all four LED pins (16, 17, 18, 19) are correctly wired and that the for loop in setup() correctly sets up all four channels (0, 1, 2, 3).Export to Sheets |
Buy from:
Myduino AIoT Education Kit (click here) from myduino.com






