Cảm biến ánh sáng: quang trở, photodiode, BH1750 – cấu tạo, nguyên lý, chế tạo và đo lux bằng ESP32
Cảm biến ánh sáng hoạt động thế nào: độ rọi và lux, quang trở CdS, photodiode, phototransistor, cảm biến số BH1750, cách chế tạo và đo lux bằng BH1750, quang trở với ESP32.
Cảm biến ánh sáng giúp đèn đường tự bật lúc chập tối, màn hình điện thoại tự giảm độ sáng trong phòng tối, máy ảnh chọn thời gian phơi sáng, nhà kính biết khi nào cần kéo lưới che nắng. Bài viết giải thích độ rọi và đơn vị lux, cấu tạo và nguyên lý của quang trở, photodiode, phototransistor, bên trong một cảm biến ánh sáng số như BH1750, cách chế tạo các loại cảm biến này, rồi hướng dẫn đo độ rọi bằng BH1750 và quang trở trên cùng một ESP32, kèm cách hiệu chuẩn quang trở theo BH1750.
1. Độ rọi và đơn vị lux

- Độ rọi là lượng ánh sáng rơi trên một đơn vị diện tích, đơn vị lux (lx): 1 lux = 1 lumen/m². Lumen đã tính theo độ nhạy của mắt người, nhạy nhất với màu xanh lục khoảng 555 nm và gần như không thấy hồng ngoại, tử ngoại.
- Vì vậy muốn đo lux đúng, cảm biến phải "nhìn" màu giống mắt người. Cảm biến không có bộ lọc sẽ báo sai khi đổi nguồn sáng, ví dụ từ đèn LED trắng sang bóng sợi đốt (phát nhiều hồng ngoại).
- Độ rọi trải rộng tới hàng chục triệu lần: đêm không trăng khoảng 0,001 lux, phòng ở vài chục đến vài trăm lux, bàn học nên có khoảng 300 đến 500 lux, nắng trưa tới khoảng 100 000 lux. Thang đo của cảm biến cần chọn theo vùng cần đo.
- Đo năng lượng ánh sáng mặt trời cho pin mặt trời hay nông nghiệp thì dùng đơn vị W/m² (bức xạ) chứ không dùng lux; đó là việc của cảm biến bức xạ (pyranometer).
2. Quang trở, photodiode và phototransistor


Quang trở (LDR)
- Quang trở là một lớp bán dẫn cadmi sunfua (CdS) hoặc cadmi selenua phủ trên đế gốm. Trong tối, chất này có rất ít hạt mang điện nên điện trở lên tới hàng trăm kΩ hay vài MΩ.
- Photon có năng lượng đủ lớn bị hấp thụ sẽ tạo ra cặp electron và lỗ trống tự do; càng sáng, càng nhiều hạt mang điện, điện trở càng nhỏ. Hiện tượng này gọi là quang dẫn.
- Dải CdS được làm dài và hẹp, uốn hình răng cưa giữa hai điện cực răng lược, để điện trở khi sáng đủ nhỏ.
- Quang trở rẻ và dễ dùng nhưng chậm (tăng giảm điện trở mất hàng chục ms), phi tuyến, mỗi con lệch nhau nhiều, và còn "nhớ" mức sáng trước đó. Vì chứa cadmi, quang trở CdS bị hạn chế ở các thị trường áp dụng quy định RoHS.
Photodiode

- Photodiode là một tiếp giáp p-n (thường bằng silic) có lớp p rất mỏng ở mặt trên để ánh sáng xuyên tới vùng nghèo. Photon tạo cặp electron, lỗ trống trong vùng nghèo; điện trường có sẵn ở đây kéo chúng về hai phía, tạo dòng quang điện.
- Dòng quang điện tỉ lệ thuận với độ rọi trên nhiều bậc độ lớn và đáp ứng rất nhanh (cỡ µs hoặc nhanh hơn). Dòng rất nhỏ, cỡ nA đến µA, nên thường đi qua mạch khuếch đại chuyển dòng thành áp.
- Khi phân cực ngược (chế độ quang dẫn), photodiode nhanh và tuyến tính hơn; khi không phân cực (chế độ quang điện), nó hoạt động như một pin mặt trời rất nhỏ, ít nhiễu hơn. Pin mặt trời thực chất là một photodiode diện tích lớn.
- Silic nhạy nhất ở vùng hồng ngoại gần (khoảng 900 nm) chứ không phải ánh sáng xanh lục, nên photodiode trần không đo đúng lux nếu không có bộ lọc.
Phototransistor
Phototransistor là transistor có tiếp giáp base-collector làm việc như một photodiode; dòng quang điện được chính transistor khuếch đại thêm hàng trăm lần. Tín hiệu lớn hơn photodiode nên mạch đơn giản (chỉ cần một điện trở), đổi lại chậm hơn và kém tuyến tính hơn. Các module "cảm biến ánh sáng môi trường" có một chân ra tương tự thường dùng phototransistor.

Đường đặc tính của quang trở trên hai trục log gần như thẳng: R = R10 × (lux / 10)^(−γ), với R10 là điện trở ở 10 lux và γ khoảng 0,5 đến 0,8 tùy loại. Datasheet thường cho R10 trong một khoảng rộng (ví dụ 10 đến 20 kΩ), nên muốn đổi điện trở ra lux tương đối đúng thì phải hiệu chuẩn từng con.
3. Cảm biến ánh sáng số

- Cảm biến ánh sáng số gói photodiode, lớp lọc quang học, mạch khuếch đại, ADC và giao tiếp I2C vào một chip vài mm. Vi điều khiển chỉ việc đọc kết quả, không phải lo khuếch đại hay hiệu chuẩn.
- Với BH1750, lớp lọc làm đáp ứng phổ gần giống mắt người nên kết quả là lux trực tiếp. Datasheet ghi dải đo 1 đến 65 535 lux; với cấu hình mặc định dùng trong chương trình dưới đây (kết quả chia 1,2), số lớn nhất đọc được khoảng 54 600 lux. ADC tích phân trong khoảng 120 ms nên trung bình được dao động 50/60 Hz của đèn chạy điện lưới.
- Các loại khác cùng họ: cảm biến có thêm kênh hồng ngoại riêng để trừ ra (đo đúng hơn dưới đèn sợi đốt, nắng), cảm biến màu đo riêng đỏ, xanh lục, xanh lam, cảm biến tử ngoại, cảm biến chuyển ánh sáng thành tần số xung.
4. Chế tạo quang trở và photodiode

- Quang trở, lớp nhạy sáng. Bột CdS (hoặc CdSe) trộn chất kết dính được phủ lên đế gốm bằng in lưới hoặc phun, rồi nung cho kết tinh. Lượng tạp chất (đồng, clo) quyết định độ nhạy, tốc độ và màu ánh sáng nhạy nhất.
- Quang trở, điện cực và vỏ. In hoặc bốc bay hai điện cực răng lược xen kẽ, để lại dải CdS dài, hẹp; hàn chân rồi phủ nhựa epoxy trong suốt chống ẩm.
- Photodiode, tiếp giáp p-n. Trên tấm silic loại n, phủ oxit, mở cửa sổ bằng quang khắc rồi khuếch tán bo tạo lớp p⁺ mỏng. Lớp càng mỏng, ánh sáng bước sóng ngắn (xanh lam) càng tới được vùng nghèo.
- Chống phản xạ, điện cực. Một lớp mỏng silic nitrua hoặc oxit làm giảm phần ánh sáng bị dội lại; mạ kim loại làm điện cực mặt trên (vòng quanh vùng nhạy) và mặt dưới.
- Lọc quang học. Cảm biến lux phủ thêm lớp lọc chặn hồng ngoại để đáp ứng giống mắt; cảm biến hồng ngoại (như trong điều khiển từ xa) thì ngược lại, dùng vỏ nhựa sẫm màu chặn ánh sáng nhìn thấy.
- Tích hợp và đóng vỏ. Cảm biến số chế tạo photodiode cùng mạch khuếch đại, ADC, I2C trên một chip CMOS; cắt tấm, gắn chip, đóng vỏ nhựa trong, kiểm tra và hiệu chuẩn bằng nguồn sáng chuẩn.
5. Thực hành: đo độ rọi bằng BH1750 và quang trở với ESP32
Linh kiện
- ESP32 DevKit V1 30 chân.
- Một module cảm biến ánh sáng BH1750 (năm chân VCC, GND, SCL, SDA, ADDR), một quang trở loại 5 mm (ví dụ GL5528) và một điện trở 10 kΩ.
- Breadboard, dây cắm.
Sơ đồ nối

| Nối từ | Nối tới |
|---|---|
| BH1750 VCC | 3V3 |
| BH1750 GND | GND |
| BH1750 SCL | GPIO22 |
| BH1750 SDA | GPIO21 |
| BH1750 ADDR | Để hở hoặc nối GND (địa chỉ 0x23); nối 3V3 thì địa chỉ là 0x5C |
| Quang trở | Một chân vào 3V3, chân kia vào D34 |
| Điện trở 10 kΩ | Từ D34 xuống GND |
Chương trình
/*
PYWORLD.VN - Đo độ rọi bằng cảm biến ánh sáng số BH1750 (I2C) và quang trở LDR trên cùng một ESP32
BH1750: SDA GPIO21, SCL GPIO22, địa chỉ 0x23. LDR nối 3V3 -> LDR -> D34 -> 10 kΩ -> GND
Gõ "h" khi đèn sáng ổn định để tính điện trở LDR ở 10 lux theo số đo của BH1750 (hiệu chuẩn LDR)
*/
#include <Wire.h>
#include <math.h>
#define CHAN_SDA 21
#define CHAN_SCL 22
#define CHAN_LDR 34
const uint8_t DIA_CHI_BH1750 = 0x23; // Chân ADDR nối GND hoặc để hở; nối 3V3 thì địa chỉ là 0x5C
const uint8_t LENH_BAT_NGUON = 0x01;
const uint8_t LENH_DO_LIEN_TUC_H = 0x10; // Đo liên tục, độ phân giải 1 lux, mỗi lần khoảng 120 ms
const float R_CO_DINH = 10000.0; // Ôm, điện trở nối từ D34 xuống GND
const float VCC_MV = 3300.0;
const float GAMMA_LDR = 0.7; // Độ dốc đường R theo độ rọi trên thang log (tra datasheet LDR)
float r10 = 15000.0; // Ôm, điện trở LDR ở 10 lux; tìm lại bằng lệnh h
bool guiLenh(uint8_t lenh) {
Wire.beginTransmission(DIA_CHI_BH1750);
Wire.write(lenh);
return Wire.endTransmission() == 0;
}
// Độ rọi (lux) từ BH1750: hai byte kết quả chia 1,2 theo datasheet; trả về -1 nếu không đọc được
float docBH1750() {
if (Wire.requestFrom(DIA_CHI_BH1750, (uint8_t)2) != 2) {
return -1;
}
uint16_t cao = Wire.read();
uint16_t thap = Wire.read();
return ((cao << 8) | thap) / 1.2;
}
// Điện trở LDR (Ôm) từ cầu phân áp; trả về -1 khi điện áp gần 0 (quá tối hoặc đứt dây)
float docDienTroLdr() {
uint32_t tong = 0;
for (int i = 0; i < 16; i++) {
tong += analogReadMilliVolts(CHAN_LDR);
}
float u = tong / 16.0; // u = Vcc × Rcodinh / (Rcodinh + Rldr)
if (u < 5) {
return -1;
}
return R_CO_DINH * (VCC_MV - u) / u;
}
void setup() {
Serial.begin(115200);
Wire.begin(CHAN_SDA, CHAN_SCL, 100000);
analogSetPinAttenuation(CHAN_LDR, ADC_11db);
Serial.println("\nCam bien anh sang: BH1750 (I2C) va quang tro LDR");
if (!guiLenh(LENH_BAT_NGUON) || !guiLenh(LENH_DO_LIEN_TUC_H)) {
Serial.println("Khong thay BH1750 o dia chi 0x23: kiem tra day SDA, SCL va nguon");
}
delay(180); // Chờ lần đo đầu tiên (tối đa 180 ms)
}
void loop() {
float lux = docBH1750();
float rLdr = docDienTroLdr();
if (Serial.available()) {
String lenh = Serial.readStringUntil('\n');
lenh.trim();
if (lenh == "h" && lux > 1 && rLdr > 0) {
r10 = rLdr * pow(lux / 10.0, GAMMA_LDR); // R = R10 × (lux / 10)^(-gamma)
Serial.printf("R10 cua LDR = %.0f ohm (ghi vao bien r10)\n", r10);
}
}
if (lux < 0) {
Serial.print("BH1750 loi doc | ");
} else {
Serial.printf("BH1750 %.1f lux | ", lux);
}
if (rLdr < 0) {
Serial.println("LDR qua toi hoac dut day");
} else {
float luxLdr = 10.0 * pow(r10 / rLdr, 1.0 / GAMMA_LDR);
Serial.printf("LDR %.1f kOhm, uoc %.0f lux\n", rLdr / 1000, luxLdr);
}
delay(1000);
}
Giải thích
- BH1750 không cần thư viện: gửi lệnh 0x01 (bật nguồn) rồi 0x10 (đo liên tục, độ phân giải 1 lux). Sau đó mỗi lần đọc hai byte, ghép thành số 16 bit và chia 1,2 theo datasheet là ra lux. Cách đọc ghi I2C từng byte được giải thích kỹ trong bài về I2C ở mục Xem thêm.
- Quang trở cùng điện trở 10 kΩ tạo cầu phân áp: u = 3,3 V × 10 kΩ / (10 kΩ + R_LDR), suy ra R_LDR = 10 kΩ × (3300 − u) / u. Càng sáng, R_LDR càng nhỏ, điện áp ở D34 càng cao.
- Từ R_LDR, chương trình ước lượng lux bằng công thức lux = 10 × (R10 / R)^(1/γ), với γ = 0,7 và R10 ban đầu là 15 kΩ (giá trị tự chọn ở giữa khoảng datasheet).
- Hiệu chuẩn: đặt quang trở sát BH1750, dưới cùng một nguồn sáng ổn định, gõ
h. Chương trình tính R10 = R × (lux / 10)^γ theo số đo của BH1750 và in ra để ghi vào biếnr10. Muốn đúng hơn trên cả dải, có thể tìm thêm γ bằng cách đo ở hai mức sáng khác nhau. - Điện trở 10 kΩ hợp với ánh sáng trong phòng. Chủ yếu đo chỗ tối thì đổi sang 47 kΩ hoặc 100 kΩ, chủ yếu đo ngoài nắng thì đổi sang 1 kΩ, và sửa
R_CO_DINHcho khớp.
Kết quả mong đợi

Sau khi hiệu chuẩn, quang trở bám khá sát BH1750 ở gần mức sáng lúc hiệu chuẩn và lệch dần khi xa mức đó, như dòng đèn bàn ở trên. Quang trở đủ dùng để bật đèn khi trời tối; cần con số lux tin được (đo ánh sáng bàn học, điều khiển đèn theo lux) thì dùng cảm biến số.
6. Xử lý lỗi thường gặp
| Hiện tượng | Nguyên nhân thường gặp | Cách xử lý |
|---|---|---|
| Báo "Khong thay BH1750" hoặc "BH1750 loi doc" | Nhầm SDA với SCL, chân ADDR đang nối 3V3 (địa chỉ 0x5C), dây lỏng | Kiểm tra dây; quét địa chỉ I2C; sửa DIA_CHI_BH1750 nếu là 0x5C |
| BH1750 đứng ở khoảng 54612 lux | Ánh sáng quá mạnh (nắng chiếu thẳng) vượt dải đo | Che bớt bằng một tấm tản quang, hoặc đổi chế độ đo trong datasheet |
| Quang trở báo "qua toi hoac dut day" | Đứt dây, quang trở cắm lệch, hoặc trời tối thật | Kiểm tra dây; ở chỗ tối dùng điện trở cố định lớn hơn |
| Số lux của quang trở lệch xa BH1750 | Chưa hiệu chuẩn; hai cảm biến không cùng hướng, cùng chỗ | Đặt sát nhau, cùng hướng lên nguồn sáng rồi gõ h |
| Số đọc dao động dưới đèn huỳnh quang hoặc LED rẻ | Đèn nhấp nháy theo điện lưới, quang trở và ADC lấy mẫu ngắn | Lấy trung bình nhiều lần đọc trong ít nhất 20 ms; BH1750 tự trung bình |
7. Xem thêm
- I2C là gì: START, STOP, địa chỉ, ACK và cách đọc ghi thanh ghi trên ESP32.
- Quét địa chỉ I2C trên ESP32: kiểm tra BH1750 đang ở 0x23 hay 0x5C.
- PWM trên ESP32: dùng số lux để tự chỉnh độ sáng đèn LED.
- Cảm biến chuyển động PIR với ESP32: kết hợp với cảm biến ánh sáng để chỉ bật đèn khi trời tối.
- Các bài khác trong loạt: cảm biến quang (cảm biến phản xạ, chặn tia), cảm biến tiệm cận.
Xem thêm