Hướng dẫn đọc ADC trên ESP32 và đo điện áp pin 18650 bằng cầu phân áp
- 1. ADC của ESP32: những điều cần biết
- 2. Linh kiện cần chuẩn bị
- 3. Chương trình 1: đọc điện áp từ biến trở
- 4. Vì sao phải dùng cầu phân áp để đo pin?
- 5. Chương trình 2: đo điện áp pin và ước lượng phần trăm
- 6. Giải thích các lệnh chính
- 7. Hiệu chỉnh bằng đồng hồ đo
- 8. Lưu ý khi dùng thực tế
- 9. Xử lý lỗi thường gặp
Hướng dẫn đọc điện áp analog bằng ADC của ESP32 trên Arduino IDE, dùng analogReadMilliVolts, làm cầu phân áp đo pin Li-ion 18650 và ước lượng phần trăm pin còn lại.
Thiết bị chạy pin nào cũng cần biết pin còn bao nhiêu để kịp báo sạc hoặc tự tắt an toàn. ESP32 có sẵn bộ chuyển đổi tương tự – số (ADC) để đo điện áp, nhưng ADC này có vài giới hạn cần hiểu trước khi dùng. Bài viết hướng dẫn đọc điện áp analog trên ESP32 bằng Arduino IDE, sau đó dùng cầu phân áp để đo điện áp pin Li-ion 18650 và ước lượng phần trăm pin còn lại.
1. ADC của ESP32: những điều cần biết
- Độ phân giải 12 bit:
analogRead()trả về số từ 0 đến 4095. - Chỉ đo được đến khoảng 3,1 V ở mức suy giảm lớn nhất (11 dB). Không bao giờ đưa điện áp cao hơn 3,3 V vào chân GPIO.
- Hai nhóm chân: ADC1 gồm GPIO32–39, dùng được mọi lúc. ADC2 gồm GPIO0, 2, 4, 12–15 và 25–27, không đọc được analog khi đang bật Wi-Fi. Nên chọn chân thuộc ADC1.
- Không tuyến tính ở hai đầu dải đo (hình dưới): điện áp rất thấp đọc ra 0, điện áp gần 3,3 V đọc ra 4095 từ sớm.
Từ gói esp32 phiên bản 3.x, cách đọc nên dùng là analogReadMilliVolts(). Hàm này trả về thẳng số milivôn, đã hiệu chỉnh theo dữ liệu mà nhà sản xuất ghi sẵn trong từng chip, nên chính xác hơn việc tự nhân giá trị thô với 3,3 / 4095.
2. Linh kiện cần chuẩn bị
- ESP32 DevKit V1 30 chân (hoặc bản dùng chip CH340) và cáp USB có truyền dữ liệu.
- Một biến trở 10 kΩ cho chương trình đầu tiên.
- Hai điện trở 100 kΩ và một tụ gốm 100 nF để làm cầu phân áp đo pin.
- Pin Li-ion 18650 đặt trong khay pin 18650, hoặc mạch dùng pin sẵn như mô-đun ESP32 tích hợp pin 18650.
- Breadboard MB-102 và dây cắm test board.
- Đồng hồ đo điện để hiệu chỉnh số đo.
- Máy tính đã cài Arduino IDE 2.x và gói esp32 by Espressif Systems phiên bản 3.x (bài dùng 3.3.10). Không cần cài thêm thư viện.
3. Chương trình 1: đọc điện áp từ biến trở
Nối hai chân ngoài của biến trở vào 3V3 và GND, chân giữa vào D34 (GPIO34). Chương trình in cả giá trị thô lẫn số milivôn để so sánh:
/*
PYWORLD.VN - ESP32 đọc điện áp analog: giá trị thô và milivôn
Biến trở 10 kΩ: hai chân ngoài nối 3V3 và GND, chân giữa nối GPIO34
Không cần cài thêm thư viện (gói esp32 phiên bản 3.x)
*/
#define ADC_PIN 34 // Chân thuộc nhóm ADC1, dùng được cả khi bật Wi-Fi
void setup() {
Serial.begin(115200);
delay(500);
analogReadResolution(12); // 12 bit: giá trị thô từ 0 đến 4095
analogSetPinAttenuation(ADC_PIN, ADC_11db); // Thang đo rộng nhất của ADC
}
void loop() {
int raw = analogRead(ADC_PIN); // Giá trị thô
uint32_t miliVon = analogReadMilliVolts(ADC_PIN); // Đã hiệu chỉnh theo dữ liệu riêng của từng chip
Serial.printf("raw = %4d U = %4lu mV\n", raw, (unsigned long)miliVon);
delay(500);
}
Chọn board DOIT ESP32 DEVKIT V1, nạp chương trình rồi mở Serial Monitor ở 115200 baud. Ví dụ kết quả khi vặn biến trở từ đầu GND lên giữa hành trình (số minh họa):
raw = 0 U = 142 mV
raw = 871 U = 850 mV
raw = 1924 U = 1650 mV
analogReadResolution(12): chọn độ phân giải 12 bit (đây cũng là giá trị mặc định).analogSetPinAttenuation(ADC_PIN, ADC_11db): chọn mức suy giảm 11 dB để đo được dải điện áp rộng nhất. Các mức nhỏ hơn (ADC_0db,ADC_2_5db,ADC_6db) chỉ đo được điện áp thấp hơn nhưng mịn hơn.analogRead()cho giá trị thô;analogReadMilliVolts()cho số milivôn đã hiệu chỉnh.
Để ý dòng đầu của ví dụ: khi chân nối GND, giá trị thô bằng 0 nhưng số milivôn vẫn khác 0. Đó là vùng chết ở đầu dưới của ADC, vì vậy không nên dùng ADC của ESP32 để đo các điện áp dưới khoảng 0,15 V.
4. Vì sao phải dùng cầu phân áp để đo pin?
Pin Li-ion 18650 có điện áp từ khoảng 3,0 V lúc cạn đến 4,2 V lúc đầy, cao hơn mức 3,3 V mà chân GPIO chịu được. Cầu phân áp gồm hai điện trở mắc nối tiếp sẽ chia điện áp pin xuống trước khi đưa vào chân ADC:
| Điểm nối | Nối tới | Ghi chú |
|---|---|---|
| Cực dương của pin | Một đầu của R1 (100 kΩ) | Không nối thẳng cực dương pin vào chân GPIO. |
| Điểm giữa R1 và R2 | D35 / GPIO35 | Chân ADC1, chỉ là ngõ vào. |
| Đầu còn lại của R2 (100 kΩ) | GND | GND của ESP32 phải nối chung với cực âm của pin. |
| Tụ 100 nF | Giữa GPIO35 và GND | Đặt sát chân ESP32. |
- Điện trở lớn để đỡ hao pin: với hai điện trở 100 kΩ, cầu phân áp chỉ tiêu thụ khoảng 0,02 mA.
- Tụ 100 nF: điện trở lớn làm ADC lấy mẫu không ổn định; tụ giữ điện áp tại chân đứng yên trong lúc ADC đọc.
- Đo đúng chỗ: lấy điện áp ở cực dương của pin (cọc B+ hoặc chân pin của mạch). Điện áp ở chân 5V hay 3V3 đã qua mạch ổn áp nên không thay đổi theo pin.
- An toàn: chập hai cực pin Li-ion gây dòng rất lớn, có thể cháy. Kiểm tra dây nối trước khi lắp pin vào khay.
5. Chương trình 2: đo điện áp pin và ước lượng phần trăm
/*
PYWORLD.VN - ESP32 đo điện áp pin Li-ion 18650 qua cầu phân áp và ước lượng phần trăm pin
Cầu phân áp: cực dương pin -> R1 100 kΩ -> GPIO35 -> R2 100 kΩ -> GND; tụ 100 nF song song với R2
Không cần cài thêm thư viện (gói esp32 phiên bản 3.x)
*/
#define ADC_PIN 35
#define R1_OHM 100000.0 // Điện trở nối lên cực dương của pin
#define R2_OHM 100000.0 // Điện trở nối xuống GND
#define HE_SO_CHINH 1.000 // = điện áp đo bằng đồng hồ / điện áp chương trình in ra
#define SO_MAU 16 // Số lần đọc để lấy trung bình
// Bảng ước lượng phần trăm pin theo điện áp lúc pin nghỉ (pin Li-ion 3,7 V)
const float BANG_VON[] = {4.20, 4.06, 3.98, 3.92, 3.87, 3.82, 3.79, 3.77, 3.74, 3.68, 3.45, 3.00};
const int BANG_PHAN_TRAM[] = {100, 90, 80, 70, 60, 50, 40, 30, 20, 10, 5, 0};
const int SO_MUC = sizeof(BANG_PHAN_TRAM) / sizeof(BANG_PHAN_TRAM[0]);
float docDienApPin() {
uint32_t tong = 0;
for (int i = 0; i < SO_MAU; i++) {
tong += analogReadMilliVolts(ADC_PIN);
delay(2);
}
float vonTaiChan = tong / (float)SO_MAU / 1000.0; // Điện áp tại chân ADC
return vonTaiChan * (R1_OHM + R2_OHM) / R2_OHM * HE_SO_CHINH; // Điện áp của pin
}
int phanTramPin(float von) {
if (von >= BANG_VON[0]) {
return 100;
}
for (int i = 1; i < SO_MUC; i++) {
if (von >= BANG_VON[i]) {
// Nội suy tuyến tính giữa hai mức liền nhau trong bảng
float tiLe = (von - BANG_VON[i]) / (BANG_VON[i - 1] - BANG_VON[i]);
return BANG_PHAN_TRAM[i] + (int)(tiLe * (BANG_PHAN_TRAM[i - 1] - BANG_PHAN_TRAM[i]) + 0.5);
}
}
return 0;
}
void setup() {
Serial.begin(115200);
delay(500);
analogSetPinAttenuation(ADC_PIN, ADC_11db);
}
void loop() {
float von = docDienApPin();
Serial.printf("Pin: %.2f V, khoang %d %%\n", von, phanTramPin(von));
delay(2000);
}
6. Giải thích các lệnh chính
docDienApPin()đọc 16 lần rồi lấy trung bình để giảm nhiễu, đổi sang vôn, sau đó nhân với (R1 + R2) / R2 để ra điện áp thật của pin. Với hai điện trở bằng nhau, hệ số này bằng 2.HE_SO_CHINHbù sai số của điện trở và của ADC (xem mục 7).phanTramPin()tra điện áp trong bảngBANG_VON, rồi nội suy tuyến tính giữa hai mức liền nhau. Ví dụ 3,90 V nằm giữa 3,92 V (70 %) và 3,87 V (60 %), kết quả là khoảng 66 %.
Điện áp pin không giảm đều theo dung lượng, nên công thức chia đều từ 3,0 V đến 4,2 V cho kết quả sai nhiều. Bảng trong chương trình là giá trị ước lượng cho pin Li-ion 3,7 V lúc nghỉ; phần trăm tính ra chỉ nên dùng để tham khảo.
7. Hiệu chỉnh bằng đồng hồ đo
- Đo điện áp pin bằng đồng hồ, ví dụ được 3,95 V.
- Xem số chương trình in ra cùng lúc đó, ví dụ 3,90 V.
- Tính
HE_SO_CHINH= 3,95 / 3,90 ≈ 1,013, sửa vào chương trình rồi nạp lại.
Điện trở thường có sai số 1–5 %, nên bước này giúp số đo sát thực tế hơn hẳn. Chỉ cần làm một lần cho mỗi mạch.
8. Lưu ý khi dùng thực tế
- Đo lúc tải nhẹ: khi ESP32 đang phát Wi-Fi, điện áp pin sụt tạm thời nên phần trăm tính ra thấp hơn thật. Nên đo trước khi bật Wi-Fi hoặc lấy trung bình nhiều lần đo.
- Ngưỡng cảnh báo: báo pin yếu ở khoảng 3,4–3,5 V và ngừng dùng ở khoảng 3,3 V. Xả pin Li-ion xuống quá thấp làm pin chai nhanh; mạch bảo vệ pin là lớp an toàn cuối cùng, không nên dựa hẳn vào nó.
- Pin nhiều cell nối tiếp (2S, 3S): điện áp cao hơn nhiều, phải tính lại cầu phân áp sao cho điện áp tại chân ADC không vượt 3,1 V khi pin đầy.
- Cần chính xác hơn: dùng ADC ngoài như trong bài đo điện áp 16 bit bằng ADS1115 với ESP32.
9. Xử lý lỗi thường gặp
- Số đo luôn bằng 0 hoặc luôn rất cao: nối sai điểm giữa của cầu phân áp, hoặc GND của ESP32 chưa nối chung với cực âm của pin.
- Số đo nhảy lung tung: thiếu tụ 100 nF, dây nối dài hoặc lỏng. Thêm tụ và tăng
SO_MAU. - Số đo lệch đều so với đồng hồ: hiệu chỉnh
HE_SO_CHINHtheo mục 7; kiểm tra giá trị thật của hai điện trở. - Đọc analog được lúc chưa bật Wi-Fi, bật Wi-Fi thì sai: đang dùng chân thuộc ADC2. Chuyển sang một chân trong nhóm GPIO32–39.
- Lỗi biên dịch "analogReadMilliVolts was not declared": gói esp32 quá cũ. Cập nhật gói esp32 by Espressif Systems trong Boards Manager.
- Không nạp được chương trình: dùng cáp USB có truyền dữ liệu, chọn đúng cổng COM và giữ nút BOOT khi IDE dừng ở dòng Connecting...
Xem thêm: đo điện áp 16 bit bằng ADS1115 với ESP32, mạch hạ áp LM2596 và MP1584: cách chọn và cấp nguồn cho ESP32 và mô đun pin Lithium-ion 18650 để cấp nguồn di động cho mạch.
Xem thêm