Mạch ESP32 tích hợp OLED 0.96 và pin 18650: sơ đồ chân, sạc pin, đo % pin và chương trình đầu tiên
Mạch ESP32 tích hợp OLED 0.96 inch và đế pin 18650: chân OLED GPIO5, GPIO4, LED GPIO16, sạc pin, công tắc, gắn cầu phân áp đo phần trăm pin và chương trình hiện lên màn hình.
Mạch ESP32 tích hợp màn hình OLED 0.96 inch và đế pin 18650 gom vào một bo những thứ mà thiết bị cầm tay cần: vi điều khiển có Wi-Fi, Bluetooth, màn hình hiển thị, pin sạc và công tắc nguồn. Không cần cắm dây cho màn hình, không cần mạch sạc rời. Bài này giới thiệu các phần trên mạch, sơ đồ chân và các chân đã dùng sẵn, cách sạc pin, cách gắn thêm cầu phân áp để đo phần trăm pin, và một chương trình đầu tiên hiện thông tin lên màn hình.
1. Trên mạch có gì

- Module ESP32-WROOM-32: Wi-Fi, Bluetooth, lập trình bằng Arduino IDE như mạch ESP32 DevKit.
- Màn hình OLED 0.96 inch 128 × 64 điểm, chip SSD1306, giao tiếp I2C địa chỉ 0x3C, đã nối sẵn vào GPIO5 (SDA) và GPIO4 (SCL).
- Đế pin 18650 ở mặt sau (pin không kèm theo mạch) và mạch sạc, xả trên bo: cắm nguồn 5 V qua cổng Micro USB là vừa sạc pin vừa chạy.
- Đèn báo trạng thái sạc và đầy pin; công tắc bật, tắt nguồn.
- LED trên mạch nối vào GPIO16.
Theo mô tả của shop, một pin 18650 cho mạch chạy khoảng 20 giờ với chương trình đơn giản; thời gian thực tế phụ thuộc pin và việc dùng Wi-Fi nhiều hay ít.
2. Sơ đồ chân và các chân đã dùng sẵn

| Chân | Đang dùng cho | Lưu ý |
|---|---|---|
| GPIO5, GPIO4 | Màn hình OLED (SDA, SCL) | Không dùng việc khác. Cảm biến I2C thêm vào thì nối chung hai chân này. |
| GPIO16 | LED trên mạch | Dùng làm đèn báo trong chương trình. |
| GPIO34, 35, 36, 39 | Trống | Chỉ đọc (ADC1, đọc nút nhấn, cảm biến analog); không có điện trở kéo lên bên trong. |
| GPIO0, 2, 12, 15 | Trống | Chân ảnh hưởng tới lúc khởi động: tránh nối thiết bị kéo mức cố định. |
| GPIO6 đến 11 | Bộ nhớ flash | Không dùng. |
| GPIO13, 14, 17, 18, 19, 21, 22, 23, 25, 26, 27, 32, 33 | Trống | Dùng thoải mái cho cảm biến, relay, nút nhấn. |
Thứ tự chân trong hình vẽ theo tài liệu của nhà cung cấp. Trước khi hàn hay cắm dây, đối chiếu với chữ in cạnh từng chân trên mạch. Cách chọn chân ESP32 chi tiết hơn xem bài sơ đồ chân ESP32 DevKit V1.
3. Lắp pin, sạc pin và công tắc

- Lắp pin 18650 vào đế ở mặt sau, đúng chiều theo ký hiệu + và − trên đế. Lắp ngược pin có thể làm hỏng mạch ngay.
- Sạc: cắm củ sạc 5 V (từ 1 A) vào cổng Micro USB. Đèn báo cho biết đang sạc hay đã đầy.
- Chạy bằng pin: rút USB, gạt công tắc sang bật.
- Cất máy: gạt công tắc về tắt để pin không bị xả dần.
Dùng pin 18650 thật, dung lượng 2000–3500 mAh; pin ghi dung lượng quá cao thường là hàng kém. Cách chọn, ghép pin 18650 được trình bày trong bài về ghép pin 18650 cùng loạt bài này.
4. Cài đặt để lập trình
- Trong Arduino IDE 2, cài gói ESP32 và chọn bo mạch ESP32 Dev Module như bài cài ESP32 vào Arduino IDE 2.
- Vào Tools → Manage Libraries, tìm và cài Adafruit SSD1306; Arduino IDE hỏi cài kèm Adafruit GFX Library thì chọn cài cả hai.
- Cắm cáp Micro USB có dây dữ liệu, chọn đúng cổng COM. Máy tính không nhận cổng thì xem bài ESP32 không nhận cổng COM.
5. Gắn cầu phân áp để đo phần trăm pin
Mạch không có sẵn chân đo điện áp pin. Muốn hiện phần trăm pin, gắn thêm một cầu phân áp hai điện trở 100 kΩ:

- R1 100 kΩ: một đầu hàn vào cực dương của đế pin ở mặt sau, đầu kia nối vào chân GPIO35.
- R2 100 kΩ: từ GPIO35 xuống chân GND.
- Pin đầy 4,2 V cho 2,1 V ở GPIO35, trong khoảng đo của ESP32. Dòng chạy qua cầu khoảng 21 µA, tức khoảng 0,5 mAh mỗi ngày, không đáng kể so với dung lượng pin.
- GPIO35 thuộc ADC1 nên đo được cả khi bật Wi-Fi.
Phần trăm pin được ước lượng từ điện áp theo một bảng có sẵn, nên chỉ là số gần đúng: khi ESP32 đang phát Wi-Fi, điện áp pin tụt xuống một chút và phần trăm hiện thấp hơn. Cách đo pin bằng ADC và hiệu chỉnh số đo được trình bày kỹ ở bài đo điện áp pin 18650 bằng ESP32.
6. Chương trình đầu tiên
Chương trình hiện chữ PYWORLD, thời gian chạy và điện áp, phần trăm pin lên màn hình, nháy LED GPIO16 mỗi giây và in cùng thông tin ra Serial Monitor (115200 baud). Khi chưa gắn cầu phân áp, để nguyên DA_GAN_CAU_DO_PIN = false; màn hình hiện "Pin: chua do". Gắn cầu xong thì đổi thành true và nạp lại.
Bài dùng gói esp32 phiên bản 3.3.10, thư viện Adafruit SSD1306 2.5.17 và Adafruit GFX Library 1.12.6.
// PYWORLD.VN - Chương trình đầu tiên cho mạch ESP32 tích hợp màn hình OLED 0.96 inch và đế pin 18650
// PYWORLD.VN - Màn hình SSD1306 nối sẵn trên mạch: SDA = GPIO5, SCL = GPIO4; LED trên mạch ở GPIO16
// PYWORLD.VN - Đo điện áp pin qua cầu phân áp 100 kΩ / 100 kΩ hàn thêm vào GPIO35, hiện phần trăm pin ước lượng
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
const int SDA_PIN = 5;
const int SCL_PIN = 4;
const int LED_PIN = 16;
const uint8_t DIA_CHI_OLED = 0x3C;
// Đổi thành true sau khi đã hàn cầu phân áp từ cực dương đế pin xuống GPIO35 (xem bài viết).
// Khi chưa hàn, chân GPIO35 bỏ trống nên số đọc được không có nghĩa.
const bool DA_GAN_CAU_DO_PIN = false;
const int CHAN_DO_PIN = 35; // chân chỉ đọc, thuộc ADC1: đo được cả khi bật Wi-Fi
const float R1_OHM = 100000.0; // nối từ cực dương pin tới GPIO35
const float R2_OHM = 100000.0; // nối từ GPIO35 xuống GND
const float HE_SO_CHINH = 1.000; // = điện áp đo bằng đồng hồ / điện áp chương trình in ra
// Bảng điện áp pin Li-ion lúc nghỉ và phần trăm tương ứng (ước lượng thô, giống bài đo pin 18650 bằng ADC)
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]);
Adafruit_SSD1306 manHinh(128, 64, &Wire, -1);
bool coManHinh = false;
float docDienApPin() {
uint32_t tong = 0;
for (int i = 0; i < 16; i++) {
tong += analogReadMilliVolts(CHAN_DO_PIN);
delay(2);
}
float vonTaiChan = tong / 16.0 / 1000.0;
return vonTaiChan * (R1_OHM + R2_OHM) / R2_OHM * HE_SO_CHINH;
}
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;
}
// Biểu tượng pin 24 x 12 điểm ảnh ở góc phải, phần tô đậm theo phần trăm
void veBieuTuongPin(int phanTram) {
manHinh.drawRect(100, 46, 24, 12, SSD1306_WHITE);
manHinh.fillRect(124, 49, 3, 6, SSD1306_WHITE);
manHinh.fillRect(102, 48, 20 * phanTram / 100, 8, SSD1306_WHITE);
}
void setup() {
Serial.begin(115200);
pinMode(LED_PIN, OUTPUT);
analogSetAttenuation(ADC_11db); // thang đo rộng nhất cho mọi chân ADC
Wire.begin(SDA_PIN, SCL_PIN);
// Hỏi thử địa chỉ I2C trước: hàm begin() của thư viện SSD1306 không tự phát hiện màn hình chưa nối
Wire.beginTransmission(DIA_CHI_OLED);
if (Wire.endTransmission() == 0 && manHinh.begin(SSD1306_SWITCHCAPVCC, DIA_CHI_OLED)) {
coManHinh = true;
} else {
Serial.println("Khong thay OLED o dia chi 0x3C (SDA GPIO5, SCL GPIO4)");
}
Serial.println("PYWORLD.VN - ESP32 OLED 18650");
}
void loop() {
static uint32_t lanTruoc = 0;
static bool denSang = false;
if (millis() - lanTruoc < 1000) {
return;
}
lanTruoc = millis();
denSang = !denSang;
digitalWrite(LED_PIN, denSang ? HIGH : LOW); // nháy LED trên mạch mỗi giây
unsigned long giay = millis() / 1000;
float von = 0;
int phanTram = -1;
if (DA_GAN_CAU_DO_PIN) {
von = docDienApPin();
phanTram = phanTramPin(von);
Serial.printf("Chay %lu s | Pin %.2f V, khoang %d %%\n", giay, von, phanTram);
} else {
Serial.printf("Chay %lu s | chua gan cau do pin\n", giay);
}
if (coManHinh) {
manHinh.clearDisplay();
manHinh.setTextColor(SSD1306_WHITE);
manHinh.setTextSize(2);
manHinh.setCursor(0, 0);
manHinh.print("PYWORLD");
manHinh.setTextSize(1);
manHinh.setCursor(0, 20);
manHinh.print("ESP32 OLED 18650");
manHinh.setCursor(0, 34);
manHinh.printf("Chay: %lus", giay);
manHinh.setCursor(0, 48);
if (phanTram >= 0) {
manHinh.printf("Pin %.2fV %d%%", von, phanTram);
veBieuTuongPin(phanTram);
} else {
manHinh.print("Pin: chua do");
}
manHinh.display();
}
}

Các phần chính của chương trình:
Wire.begin(5, 4)mở I2C ở đúng hai chân màn hình đã nối sẵn. Chương trình hỏi thử địa chỉ 0x3C trước khi khởi tạo, vì hàmbegin()của thư viện không tự phát hiện màn hình không phản hồi.phanTramPin()nội suy tuyến tính giữa hai mức trong bảng điện áp, giống bài đo pin bằng ADC.HE_SO_CHINH: đo điện áp pin bằng đồng hồ vạn năng, chia cho số chương trình in ra, ghi kết quả vào đây để số đo khớp hơn.- Vòng lặp dùng
millis()để cập nhật mỗi giây mà không chặn chương trình, dễ thêm phần đọc cảm biến hay Wi-Fi về sau.
7. Lỗi thường gặp
| Hiện tượng | Nguyên nhân hay gặp | Cách xử lý |
|---|---|---|
| Màn hình không hiện, Serial báo không thấy OLED | Dùng chân I2C mặc định 21, 22 thay vì 5, 4 | Gọi Wire.begin(5, 4) trước khi khởi tạo màn hình |
| Màn hình hiện nhiễu, lệch cột | Dùng nhầm thư viện của chip khác | Màn hình trên mạch dùng thư viện Adafruit SSD1306 |
| Rút USB là mạch tắt | Công tắc đang tắt, chưa lắp pin hoặc pin hết | Gạt công tắc, kiểm tra chiều pin, sạc pin |
| Phần trăm pin luôn 0 % hoặc nhảy lung tung | Chưa gắn cầu phân áp mà đã để true, hoặc mối hàn hở | Kiểm tra cầu phân áp, đo điện áp ở GPIO35 bằng đồng hồ |
| Thiết bị cảm biến I2C thêm vào làm màn hình tắt | Trùng địa chỉ 0x3C, hoặc nối sang chân I2C khác | Nối chung GPIO5, GPIO4; quét địa chỉ để kiểm tra trùng |
| Nạp code xong mạch khởi động lại liên tục | Nối thiết bị vào GPIO0, 2, 12, 15 làm sai mức khởi động | Chuyển thiết bị sang các chân trống khác |
8. Xem thêm
- Sản phẩm: mạch ESP32 tích hợp OLED 0.96 inch và pin 18650, mạch ESP32 tích hợp OLED, mạch ESP32 tích hợp pin 18650, các loại mạch ESP32.
- Chọn mạch ESP32 nào: DevKit 30 hay 38 chân, bản có OLED, có pin 18650.
- OLED 0.96 hay 1.3 inch, SSD1306 hay SH1106.
- Đồng hồ Internet với ESP32, NTP và OLED 0.96 inch, quét địa chỉ I2C, nối nhiều thiết bị.
- Deep Sleep trên ESP32: tiết kiệm pin.
Cần tư vấn chọn mạch ESP32 cho thiết bị cầm tay, liên hệ PYWORLD qua số 0915703319.
Xem thêm