IoTLabs

Nghiên cứu, Sáng tạo và Thử nghiệm

Series 37 Module Cảm Biến – Nguyên Lý Hoạt Động LDR Quang Trở: CdS Photoconductor, Voltage Divider & Điều Khiển Theo Ánh Sáng

LDR (Light Dependent Resistor) — còn gọi là quang trở hoặc photoresistor — là linh kiện thay đổi điện trở theo cường độ ánh sáng. Đơn giản nhất trong các cảm biến ánh sáng nhưng đủ mạnh cho hầu hết ứng dụng điều khiển cơ bản. Bài này phân tích vật liệu CdS (cadmium sulfide), tại sao điện trở giảm khi có sáng, và cách đọc analog chính xác.

Nguyên Lý Hoạt Động

1. CdS — Vật Liệu Photoconductor

LDR dùng CdS (Cadmium Sulfide) — vật liệu bán dẫn với bandgap ~2.42eV, nhạy với ánh sáng nhìn thấy (đặc biệt 500–700nm, đỉnh ~520nm xanh lá).

Cơ chế dẫn điện:

Trong bóng tối:
  Electron ở vùng hóa trị (valence band) — bị "khóa"
  Vùng dẫn điện (conduction band) trống
  → Ít electron tự do → Điện trở RẤT CAO (1MΩ – 10MΩ)

Khi có ánh sáng:
  Photon ánh sáng có năng lượng E = hν
  Nếu E ≥ Eg (bandgap CdS = 2.42eV)
  → Photon đẩy electron từ valence lên conduction band
  → Tạo cặp electron-hole tự do
  → Nhiều carrier điện → Điện trở THẤP (100Ω – 10kΩ)

Khi sáng tắt:
  Electron và hole tái kết hợp → Carrier giảm → Điện trở tăng lại
  (Chậm: 10ms–100ms → LDR "chậm" hơn photodiode)

Công thức điện trở tương đương (log-log relationship):

R_LDR = C × E^(-γ)

Trong đó:
  E = cường độ ánh sáng (lux)
  C và γ = hệ số phụ thuộc loại LDR (thường γ ≈ 0.7 – 0.9)

2. Voltage Divider — Đọc Ánh Sáng Bằng ADC

LDR không tạo điện áp tự động. Cần mạch voltage divider để chuyển điện trở → điện áp đọc được bởi ADC:

    VCC
     │
   [R_cố_định] ← Thường 10kΩ
     │
     ├──── VOUT (đọc bởi ADC)
     │
   [LDR] ← Điện trở thay đổi theo ánh sáng
     │
    GND

Điện áp VOUT = VCC × LDR / (R_fixed + LDR):

Điều kiệnR_LDRVOUT (với R_fixed=10kΩ, VCC=5V)
Tối hoàn toàn~1MΩ≈ 5V × 1000/1010 ≈ 4.95V (gần VCC)
Ánh sáng phòng~10kΩ= 5V × 10/20 = 2.5V
Nắng gắt~100Ω≈ 5V × 0.1/10.1 ≈ 0.05V (gần GND)

Lưu ý: Sáng → VOUT thấp (logic ngược). Điều chỉnh R_fixed để tối ưu dải đo:

  • Môi trường tối (đêm): dùng R_fixed lớn hơn (47kΩ–100kΩ)
  • Môi trường sáng ban ngày: R_fixed 1kΩ–10kΩ
  • Để nhạy nhất ở cường độ quan tâm: đặt Rfixed = RLDR tại cường độ đó

3. Thời Gian Đáp Ứng — “Chậm” So Với Photodiode

LDR có thời gian đáp ứng chậm hơn nhiều so với photodiode:

  • LDR: ~10ms đến vài trăm ms
  • Photodiode: ~µs đến ns

Nguyên nhân: Cặp electron-hole tạo ra bởi photon cần thời gian để recombine khi tắt ánh sáng → điện trở giảm chậm sau khi bật sáng, tăng chậm sau khi tắt.

Điều này hoàn toàn chấp nhận được cho ứng dụng điều khiển ánh sáng thông thường (đèn đường, cửa chớp, điều chỉnh màn hình). Không phù hợp cho đo ánh sáng tần số cao (như đo nhấp nháy LED tần số kHz).

Thông Số Kỹ Thuật (LDR Thông Dụng GL5528)

Thông sốGiá trị
Điện trở tối1MΩ – 10MΩ
Điện trở sáng (10 lux)10kΩ – 20kΩ
Điện trở sáng (100 lux)2kΩ – 5kΩ
Đỉnh nhạy sáng~540nm (xanh lá–vàng)
Dải bước sóng nhạy~400nm – 700nm (visible)
Điện áp tối đa100V DC (linh kiện rời)
Công suất tối đa100mW
Thời gian tắt–sáng~20ms
Thời gian sáng–tối~30ms
Nhiệt độ hoạt động-30°C – 70°C

Sơ Đồ Chân (Pinout)

LDR Rời (GL5528 hoặc tương đương)

Hình dạng: ⌾ (hình tròn đường kính 5mm, 10mm, hoặc 12mm)
2 chân giống nhau — KHÔNG phân cực

Chân 1 ──┤  LDR  ├── Chân 2
          (tương đương điện trở — không phân cực)

Module KY-018 (trong kit 37 module)

┌──────────────────────┐
│   [LDR]              │
│   [10kΩ pull-down]   │
└──────────────────────┘
  GND   VCC   S(Signal)
ChânChức năng
GNDĐất
VCC3.3V – 5V
SAnalog output — điện áp tỷ lệ với ánh sáng

Logic KY-018: S cao = tối, S thấp = sáng (thường) — phụ thuộc cách nối voltage divider trong module.

So Sánh LDR Với Cảm Biến Ánh Sáng Khác

SensorNguyên lýĐầu raĐộ chính xácGhi chú
LDR (CdS)PhotoconductorAnalog điện trởTương đốiĐơn giản, rẻ
TEMT6000PhototransistorAnalog dòngTốt hơnNhỏ, tuyến tính hơn
BH1750Photodiode + ADCI2C digital (lux)Cao16-bit, hiệu chỉnh sẵn
APDS-9930PhotodiodeI2C, ambient + proximityCaoCó cả đo gần
TSL2561Dual photodiodeI2C (lux + IR)CaoBù IR tốt

Khi nào dùng LDR: Cần đơn giản, rẻ, không cần lux chính xác — bật/tắt đèn, phát hiện ngày/đêm. Khi nào dùng BH1750: Cần đọc lux thực tế, so sánh với tiêu chuẩn, IoT logging ánh sáng.

Kết Nối Phần Cứng

LDR Rời với Voltage Divider — ESP32

3V3
 │
[10kΩ điện trở cố định]
 │
 ├──── GPIO32 (ADC1_CH4)  ← Đọc điện áp
 │
[LDR]
 │
GND

Hoặc module KY-018:

ESP32 DevKit V1           KY-018 Module
─────────────────────     ─────────────────
3V3  ─────────────────→  VCC
GND  ─────────────────→  GND
GPIO32 (ADC1) ←──────────  S (Signal)

LDR Rời với Voltage Divider — Arduino Uno

5V
 │
[10kΩ]
 │
 ├──── A0  ← Đọc điện áp analog
 │
[LDR]
 │
GND

Code Arduino IDE

Code Đọc Ánh Sáng Và Bật Đèn Tự Động — Arduino Uno

/*
 * LDR Quang Trở — Đọc ánh sáng, bật đèn khi tối
 * Board: Arduino Uno
 * Kết nối:
 *   Voltage divider: 5V → [10kΩ] → A0 → [LDR] → GND
 *   LED/Relay → Pin 9 (OUTPUT)
 *
 * Hiệu chỉnh: Chạy code lần đầu, đọc giá trị ADC trong
 *   điều kiện TỐI và SÁNG để set DARK_THRESHOLD
 */

const int LDR_PIN   = A0;
const int LED_PIN   = 9;  // LED hoặc relay — bật khi tối

// Ngưỡng — ĐỌC GIÁ TRỊ THỰC TẾ TRƯỚC!
// Khi tối: ADC cao (gần 1023 với R pull-up=10kΩ)
// Khi sáng: ADC thấp
// Ngưỡng 600 = bật đèn khi ADC > 600 (tương đối tối)
const int DARK_THRESHOLD = 600;

// Hysteresis: tránh đèn bật tắt liên tục ở ngưỡng
const int HYSTERESIS = 50; // ADC khác biệt 50 đơn vị để thay đổi trạng thái

bool lightOn = false; // Trạng thái đèn hiện tại

void setup() {
  Serial.begin(9600);
  pinMode(LED_PIN, OUTPUT);
  digitalWrite(LED_PIN, LOW);
  Serial.println("=== LDR Đèn Tự Động - Arduino Uno ===");
  Serial.println("Chạy lần đầu: đọc ADC trong TỐI và SÁNG để hiệu chỉnh DARK_THRESHOLD");
}

void loop() {
  // Đọc 5 lần lấy trung bình để ổn định
  int total = 0;
  for (int i = 0; i < 5; i++) {
    total += analogRead(LDR_PIN);
    delay(10);
  }
  int ldrValue = total / 5;

  // Logic với hysteresis
  if (!lightOn && ldrValue > DARK_THRESHOLD + HYSTERESIS) {
    // Tối → bật đèn
    lightOn = true;
    digitalWrite(LED_PIN, HIGH);
    Serial.print("TỐI → Bật đèn | ADC: "); Serial.println(ldrValue);
  } else if (lightOn && ldrValue < DARK_THRESHOLD - HYSTERESIS) {
    // Sáng → tắt đèn
    lightOn = false;
    digitalWrite(LED_PIN, LOW);
    Serial.print("SÁNG → Tắt đèn | ADC: "); Serial.println(ldrValue);
  }

  // In trạng thái mỗi 2 giây
  static unsigned long lastPrintTime = 0;
  if (millis() - lastPrintTime >= 2000) {
    lastPrintTime = millis();
    Serial.print("ADC: "); Serial.print(ldrValue);
    Serial.print(" | Đèn: "); Serial.println(lightOn ? "BẬT" : "TẮT");
  }

  delay(200);
}

Code Đọc Ánh Sáng Và Ánh Xạ Ra Lux Ước Tính — Arduino Uno

/*
 * LDR — Đọc và ước tính lux từ điện trở
 * Board: Arduino Uno
 * Kết nối: 5V → [10kΩ] → A0 → [LDR] → GND
 *
 * Ước tính lux: không chính xác như BH1750 nhưng đủ cho phân loại
 * Công thức: lux ≈ 500 / R_LDR(kΩ) — xấp xỉ cho GL5528 ở điều kiện thường
 */

const int LDR_PIN   = A0;
const float R_FIXED = 10.0; // kΩ — điện trở cố định trong voltage divider
const float VCC     = 5.0;  // Volt

void setup() {
  Serial.begin(9600);
  Serial.println("=== LDR Quang Trở - Arduino Uno ===");
}

float getLDRResistance(int rawADC) {
  // Điện áp tại A0
  float voltage = (float)rawADC / 1023.0 * VCC;
  if (voltage >= VCC) return 9999.0; // Tối hoàn toàn
  // Tính điện trở LDR: R_LDR = R_fixed × voltage / (VCC - voltage)
  // Sắp xếp lại: khi voltage tại A0 = VCC × LDR/(R_fixed+LDR)
  // → LDR = R_fixed × voltage / (VCC - voltage)
  float r_ldr = R_FIXED * voltage / (VCC - voltage);
  return r_ldr;
}

// Ước tính lux từ điện trở (xấp xỉ GL5528)
float estimateLux(float r_ldr_kohm) {
  // GL5528: R(lux) ≈ C × lux^(-gamma), thường C≈500, gamma≈0.8
  // Đảo ngược: lux ≈ (C/R)^(1/gamma) — đây là ước tính thô
  if (r_ldr_kohm <= 0) return 100000; // Rất sáng
  return pow(500.0 / r_ldr_kohm, 1.0 / 0.8);
}

String getLightCategory(float lux) {
  if (lux < 1)     return "Đêm tối";
  if (lux < 10)    return "Ánh trăng / Đèn đường yếu";
  if (lux < 100)   return "Trong nhà tối";
  if (lux < 500)   return "Trong nhà sáng";
  if (lux < 5000)  return "Nhiều mây / Bóng râm";
  if (lux < 25000) return "Ánh nắng nhạt";
  return "Nắng gắt";
}

void loop() {
  int rawADC = analogRead(LDR_PIN);
  float r_ldr = getLDRResistance(rawADC);
  float lux   = estimateLux(r_ldr);

  Serial.print("ADC: "); Serial.print(rawADC);
  Serial.print(" | R_LDR: "); Serial.print(r_ldr, 2); Serial.print("kΩ");
  Serial.print(" | ~Lux: "); Serial.print(lux, 0);
  Serial.print(" | "); Serial.println(getLightCategory(lux));

  delay(1000);
}

Code ESP32 — Điều Khiển Độ Sáng LED Theo Ánh Sáng Phòng

/*
 * LDR + ESP32 — Điều chỉnh độ sáng LED tỷ lệ nghịch với ánh sáng phòng
 * Board: ESP32 DevKit V1
 * Kết nối:
 *   LDR voltage divider: 3V3 → [10kΩ] → GPIO32 → [LDR] → GND
 *   LED + điện trở 220Ω: GPIO5 (PWM) → [220Ω] → LED → GND
 *
 * Logic: Phòng sáng → LED mờ | Phòng tối → LED sáng hơn
 * Ứng dụng: màn hình tự điều chỉnh độ sáng theo môi trường
 */

const int LDR_PIN = 32; // ADC1_CH4
const int LED_PIN = 5;  // PWM GPIO

// Giới hạn độ sáng LED: LEDC_MIN = sáng tối thiểu, LEDC_MAX = sáng tối đa
const int LED_MIN = 10;   // 0-255 (pwm min)
const int LED_MAX = 255;  // pwm max

// ADC boundaries — đo thực tế khi tối và khi sáng
// Thay bằng giá trị thực sau khi test
const int ADC_BRIGHT = 500;  // ADC khi ánh sáng phòng rất sáng
const int ADC_DARK   = 3500; // ADC khi tối

void setup() {
  Serial.begin(115200);
  analogSetAttenuation(ADC_11db); // Đọc 0-3.3V
  analogReadResolution(12);       // 12-bit: 0-4095

  // Cấu hình PWM cho ESP32
  ledcAttachPin(LED_PIN, 0);   // Pin, Channel
  ledcSetup(0, 5000, 8);       // Channel, Tần số 5kHz, 8-bit resolution

  Serial.println("=== LDR Tự Điều Chỉnh Độ Sáng - ESP32 ===");
}

void loop() {
  // Đọc 10 mẫu lấy trung bình
  int total = 0;
  for (int i = 0; i < 10; i++) {
    total += analogRead(LDR_PIN);
    delay(5);
  }
  int ldrADC = total / 10;

  // Map ADC → độ sáng LED (tỷ lệ nghịch — tối thì LED sáng hơn)
  // ADC cao (tối) → LED sáng (LED_MAX)
  // ADC thấp (sáng) → LED mờ (LED_MIN)
  int brightness = map(ldrADC, ADC_BRIGHT, ADC_DARK, LED_MIN, LED_MAX);
  brightness = constrain(brightness, LED_MIN, LED_MAX);

  ledcWrite(0, brightness); // Đặt PWM

  Serial.printf("ADC LDR: %4d | Độ sáng LED: %3d/255\n", ldrADC, brightness);
  delay(100);
}

Kết Quả Mong Đợi

=== LDR Tự Điều Chỉnh Độ Sáng - ESP32 ===
ADC LDR: 3521 | Độ sáng LED: 255/255  ← Tối (che LDR)
ADC LDR: 2100 | Độ sáng LED: 148/255  ← Ánh sáng phòng vừa
ADC LDR:  620 | Độ sáng LED:  10/255  ← Sáng (đèn flash chiếu)

Ứng Dụng Thực Tế

Ứng dụngChi tiết
Đèn đường tự bật khi tốiLDR + relay, ngưỡng twilight ~50-100 lux
Điều chỉnh độ sáng màn hìnhTỷ lệ nghịch với ánh sáng phòng
Cảnh báo cửa mở khi có ánh sángTủ, két khi đóng = tối, khi mở = sáng
Logger ánh sáng theo thời gianKết hợp DS3231 RTC và SD card
Tưới cây theo chu kỳ ngày đêmNgắt tưới ban đêm (tối)
Đèn đọc sách thông minhTự điều chỉnh độ sáng theo ánh sáng phòng

Lưu Ý Khi Sử Dụng

1. Chọn R_fixed phù hợp với dải ánh sáng quan tâm

Công thức tối ưu: Rfixed = √(Rdark × Rbright). Ví dụ: tối tối=1MΩ, sáng phòng=10kΩ → Rfixed ≈ 3.16kΩ. Trong thực tế, 10kΩ là lựa chọn phổ biến vì cân bằng giữa các điều kiện ánh sáng phòng.

2. Hiệu chỉnh ADC thực tế trước khi dùng

Mỗi LDR có đặc tuyến khác nhau. Đặt LDR ở điều kiện tối nhất và sáng nhất của ứng dụng thực tế → đo ADC → dùng map() với 2 giá trị đó. Không dùng ngưỡng cố định từ internet.

3. Hysteresis cho điều khiển ON/OFF

Không dùng 1 ngưỡng: đèn sẽ bật/tắt liên tục khi ánh sáng dao động ở ngưỡng (ví dụ: mây lướt qua mặt trời). Dùng 2 ngưỡng: BẬT khi THRESHOLD+50, TẮT khi >DARKTHRESHOLD-50.

4. Tốc độ đáp ứng chậm

LDR có lag 10-100ms. Không dùng đo ánh sáng tần số cao hoặc phát hiện nhấp nháy LED. Với ứng dụng điều khiển đèn/mái che: hoàn toàn ổn.

5. CdS chứa Cadmium — Không phải RoHS

CdS chứa cadmium (Cd), kim loại nặng hạn chế theo RoHS. Không thải bỏ vào rác thông thường. Với ứng dụng cần RoHS compliance: dùng photodiode silicon (BH1750, TEMT6000) thay thế.

Bài tiếp theo: