Bài 1 (Level 1) sẽ dùng LDR (quang trở) để đo mức sáng dạng tương đối (0–100%). LDR rất dễ làm nhưng bạn cần mắc mạch chia áp đúng để ESP32 đọc được analog ổn định.
1) Chuẩn bị
Phần cứng
- ESP32-C3 SuperMini (Tenstar Robot)
- 1x LDR (GL5528 hoặc tương tự)
- 1x điện trở 10kΩ (khuyến nghị)
- Breadboard + dây
Chân dùng trong bài
- ADC_PIN = GPIO4 (analog đọc giá trị)
- 3V3 và GND
Lưu ý: ESP32 chỉ chịu 3.3V ở chân GPIO/ADC. Không đưa 5V vào chân ADC.
2) Nối dây (mạch chia áp)
Bạn mắc như sau (đọc ở điểm giữa):
Cách A (phổ biến, sáng lên → giá trị tăng/giảm tuỳ bạn chọn):
3V3 → LDR → (điểm đo) → R 10kΩ → GND
- Điểm đo nối vào GPIO4
3V3 --- LDR ---+--- GPIO4 (ADC)
|
10k
|
GND
- Khi sáng hơn: điện trở LDR giảm → điện áp tại GPIO4 thường tăng (tuỳ vị trí LDR/R).
Nếu bạn muốn chiều ngược lại, chỉ cần đổi chỗ LDR và điện trở 10k.
3) Đo “cường độ ánh sáng” thế nào cho đúng?
LDR không đo lux chuẩn như BH1750. Ở bài này ta đo:
- Raw ADC: 0 → 4095
- Voltage: 0.0 → 3.3V
- Light %: chuẩn hoá 0 → 100% (tương đối)
Để ổn định hơn, ta dùng trung bình trượt (moving average).
Ví dụ 1 — Code local đơn giản (đọc LDR + in Serial)
Mục tiêu: cắm dây xong là thấy số, có % độ sáng.
// ===== LDR Local Read (ESP32-C3) =====
const int ADC_PIN = 4; // GPIO4
const int ADC_MAX = 4095; // 12-bit
const float VREF = 3.3; // ESP32 ADC reference ~3.3V
// Moving average
const int N = 10;
int buf[N];
int idx = 0;
long sum = 0;
int readAdcAvg() {
int x = analogRead(ADC_PIN);
sum -= buf[idx];
buf[idx] = x;
sum += buf[idx];
idx = (idx + 1) % N;
return (int)(sum / N);
}
void setup() {
Serial.begin(115200);
delay(200);
// ESP32 Arduino: analogRead default 12-bit on many cores,
// but keep it explicit if supported:
// analogReadResolution(12);
// init avg buffer
for (int i = 0; i < N; i++) buf[i] = 0;
pinMode(ADC_PIN, INPUT);
Serial.println("LDR local read started...");
}
void loop() {
int raw = readAdcAvg();
float v = (raw * VREF) / ADC_MAX;
// Light percent (tương đối)
// Nếu bạn thấy chiều % bị ngược (sáng lên mà % giảm),
// hãy đổi: pct = 100 - pct;
int pct = (raw * 100) / ADC_MAX;
Serial.print("raw=");
Serial.print(raw);
Serial.print(" v=");
Serial.print(v, 3);
Serial.print("V light=");
Serial.print(pct);
Serial.println("%");
delay(500);
}
✅ Kết quả bạn sẽ thấy Serial kiểu:raw=1234 v=0.994V light=30%
Ví dụ 2 — Gửi IoTLabs Cloud MQTT theo dõi realtime
4) Payload & Topic gợi ý
Topic (chuẩn IoTLabs bạn đang dùng):iotlabs/<orgId>/devices/<deviceId>/telemetry
Payload JSON:
{
"ts": 1760000000,
"metrics": { "light_raw": 1234, "light_pct": 30, "light_v": 0.994 },
"status": { "ok": true }
}
5) Code MQTT realtime (WiFi + MQTT + gửi JSON)
Ghi chú: Mình để
setInsecure()để demo nhanh TLS. Khi production, bạn nên cấu hình CA cert chuẩn.
#include <WiFi.h>
#include <WiFiClientSecure.h>
#include <PubSubClient.h>
#include <HTTPClient.h>
// ====== CONFIG ======
const char* WIFI_SSID = "YOUR_WIFI";
const char* WIFI_PASS = "YOUR_PASS";
// IoTLabs MQTT
const char* MQTT_HOST = "mqtt.iotlabs.vn";
const int MQTT_PORT = 8883; // TLS
const char* MQTT_USER = "YOUR_MQTT_USER";
const char* MQTT_PASS = "YOUR_MQTT_PASS";
const char* MQTT_TOPIC = "iotlabs/<orgId>/devices/<deviceId>/telemetry";
// Time service (optional but recommended for real epoch ts)
const char* TIME_URL = "https://time.iotlabs.vn/"; // returns ms timestamp as text
// ====== LDR ======
const int ADC_PIN = 4;
const int ADC_MAX = 4095;
const float VREF = 3.3;
const int N = 10;
int buf[N];
int idx = 0;
long sum = 0;
WiFiClientSecure net;
PubSubClient mqtt(net);
unsigned long lastSendMs = 0;
const unsigned long SEND_EVERY_MS = 1000;
long long epochSec = 0; // cached epoch seconds
unsigned long lastTimeSyncMs = 0;
const unsigned long TIME_SYNC_EVERY_MS = 5UL * 60UL * 1000UL; // 5 minutes
int readAdcAvg() {
int x = analogRead(ADC_PIN);
sum -= buf[idx];
buf[idx] = x;
sum += buf[idx];
idx = (idx + 1) % N;
return (int)(sum / N);
}
bool syncTimeEpoch() {
if (WiFi.status() != WL_CONNECTED) return false;
HTTPClient http;
http.begin(TIME_URL);
int code = http.GET();
if (code != 200) {
http.end();
return false;
}
String s = http.getString();
http.end();
// service returns ms as text
long long ms = s.toInt();
if (ms <= 0) return false;
epochSec = ms / 1000;
lastTimeSyncMs = millis();
return true;
}
void connectWiFi() {
WiFi.mode(WIFI_STA);
WiFi.begin(WIFI_SSID, WIFI_PASS);
Serial.print("WiFi connecting");
while (WiFi.status() != WL_CONNECTED) {
delay(400);
Serial.print(".");
}
Serial.println("\nWiFi connected!");
}
void connectMQTT() {
// Quick demo: skip cert validation (OK for test)
net.setInsecure();
mqtt.setServer(MQTT_HOST, MQTT_PORT);
Serial.print("MQTT connecting");
while (!mqtt.connected()) {
if (mqtt.connect("esp32c3-ldr", MQTT_USER, MQTT_PASS)) {
Serial.println("\nMQTT connected!");
break;
}
Serial.print(".");
delay(800);
}
}
long long nowEpochSec() {
// If we have epochSec synced, estimate using millis drift:
if (epochSec > 0) {
unsigned long dt = (millis() - lastTimeSyncMs) / 1000;
return epochSec + dt;
}
// Fallback (not real epoch): still works for realtime viewing
return (long long)(millis() / 1000);
}
void setup() {
Serial.begin(115200);
delay(200);
for (int i = 0; i < N; i++) buf[i] = 0;
pinMode(ADC_PIN, INPUT);
connectWiFi();
// time sync once at start (optional)
if (syncTimeEpoch()) Serial.println("Time synced OK.");
else Serial.println("Time sync failed (will fallback to uptime seconds).");
connectMQTT();
}
void loop() {
if (WiFi.status() != WL_CONNECTED) connectWiFi();
if (!mqtt.connected()) connectMQTT();
mqtt.loop();
// Re-sync time occasionally
if (millis() - lastTimeSyncMs > TIME_SYNC_EVERY_MS) {
syncTimeEpoch();
}
if (millis() - lastSendMs >= SEND_EVERY_MS) {
lastSendMs = millis();
int raw = readAdcAvg();
float v = (raw * VREF) / ADC_MAX;
int pct = (raw * 100) / ADC_MAX;
long long ts = nowEpochSec();
// Build JSON payload
// (String is ok for small payload; optimize later if needed)
String payload = "{";
payload += "\"ts\":" + String(ts) + ",";
payload += "\"metrics\":{";
payload += "\"light_raw\":" + String(raw) + ",";
payload += "\"light_pct\":" + String(pct) + ",";
payload += "\"light_v\":" + String(v, 3);
payload += "},";
payload += "\"status\":{\"ok\":true}";
payload += "}";
bool ok = mqtt.publish(MQTT_TOPIC, payload.c_str());
Serial.print("Publish: ");
Serial.print(ok ? "OK " : "FAIL ");
Serial.println(payload);
}
}
6) Lỗi thường gặp & cách xử lý nhanh
- Giá trị nhảy loạn: dây dài, tiếp xúc kém → dùng moving average (đã có) + rút ngắn dây + kiểm tra GND chung.
- Sáng/tối bị ngược: đổi vị trí LDR và điện trở 10k, hoặc dùng
pct = 100 - pct; - Không lên MQTT: kiểm tra host/port/user/pass/topic; test trước bằng mosquitto_pub trên máy.
7) Bài tiếp theo (Level 1)
- PIR HC-SR501 (phát hiện chuyển động)
- Reed switch (cửa)
- SW-420 (rung)


