Thước đo khoảng cách siêu âm hiện số trên màn OLED
Đo khoảng cách tới vật cản từ 3cm đến khoảng 3m bằng cảm biến siêu âm HC-SR04, hiện số to trên màn OLED kèm thanh mức và mốc gần nhất – xa nhất. Một nút nhấn để giữ số đo hoặc đổi đơn vị cm/inch. Có cầu chia áp bảo vệ chân ESP32 khỏi mức 5V.
✅ Đã kiểm thử
Xem chi tiết 9 phép đo
Đang có xung dội về (ECHO ở mức cao)
- Tổng dòng đỉnh từ USB 285.0mA (đủ với cục sạc 5V 1A).
- Áp vào chân ESP32 sau cầu chia áp (phải ≤ 3,6V): 3.11V ✓
- Áp ngay tại chân ECHO của cảm biến (mức 5V): 4.66V ✓
- Công suất trên điện trở 1kΩ: 2mW ✓
- Mức xung phát TRIG (HC-SR04 nhận từ 2V): 3.30V ✓
- Tổng dòng từ cổng USB: 285.0mA ✓
Chưa có vật cản, đang bấm nút giữ số đo
- Tổng dòng đỉnh từ USB 285.0mA (đủ với cục sạc 5V 1A).
- Chân ECHO về mức thấp: 0.00V ✓
- Áp chân nút khi nhấn: 0.00V ✓
Nguyên lý hoạt động
ESP32 phát một xung 10 micro giây ra chân TRIG; HC-SR04 bắn 8 chu kỳ siêu âm 40kHz rồi kéo chân ECHO lên cao đúng bằng thời gian sóng đi và về. Khoảng cách = thời gian × 343m/s ÷ 2, tức là chia cho 58 để ra centimet. Chân ECHO xuất mức 5V trong khi ESP32 chỉ chịu 3,3V, nên bắt buộc phải có cầu chia áp: 1kΩ nối tiếp rồi 2kΩ xuống mát kéo 5V xuống khoảng 3,1V — vẫn thừa ngưỡng nhận mức cao của ESP32. Màn OLED chạy I2C ở 3,3V nên nối thẳng vào GPIO21 (SDA) và GPIO22 (SCL). Code đo 5 lần rồi lấy giá trị giữa (trung vị) để bỏ các lần dội sai, nhờ vậy số không nhảy loạn. Nút ở GPIO19 dùng trở kéo lên trong chip: bấm nhanh để giữ/thả số đo, giữ 1,5 giây để đổi cm sang inch.
Sơ đồ đấu dây
Mỗi màu là một dây. Chấm tròn là điểm hàn. Chạm vào tên dây trong bảng để làm nổi bật.
Đây là mô phỏng một chiều (DC) ở trạng thái tĩnh: dùng để kiểm tra mức áp, dòng và công suất. Hiệu ứng nhấp nháy, PWM hay giao tiếp số do code quyết định nên không chạy ở đây.
| # | Dây | Nối các điểm | Áp mô phỏng |
|---|---|---|---|
| 1 | đỏ 5V | U1 VIN ⟷ U2 VCC | — |
| 2 | cam 3V3 | U1 3V3 ⟷ U3 VCC | — |
| 3 | đen GND | U1 GND ⟷ U2 GND ⟷ U3 GND ⟷ R2 2 ⟷ SW1 2 | — |
| 4 | vàng TRIG | U1 GPIO5 (D5) ⟷ U2 TRIG | — |
| 5 | xanh lá ECHO_5V | U2 ECHO ⟷ R1 1 | — |
| 6 | xanh dương ECHO_3V3 | R1 2 ⟷ R2 1 ⟷ U1 GPIO18 (D18) | — |
| 7 | tím SDA | U3 SDA ⟷ U1 GPIO21 (D21) | — |
| 8 | xanh ngọc SCL | U3 SCL ⟷ U1 GPIO22 (D22) | — |
| 9 | nâu BTN | SW1 1 ⟷ U1 GPIO19 (D19) | — |
Danh sách linh kiện
| Ký hiệu | Linh kiện | Giá tham khảo | |
|---|---|---|---|
| U1 | ESP32 DevKit V1 30 chân (ESP32-WROOM-32) | 110.000đ | Tìm mua |
| U2 | Cảm biến siêu âm HC-SR04 | 22.000đ | Tìm mua |
| U3 | Màn hình OLED 0,96 inch I2C SSD1306 128×64 | 48.000đ | Tìm mua |
| R1 | Điện trở 1kΩ 1/4W (nâu-đen-đỏ) | 200đ | Tìm mua |
| R2 | Điện trở 2kΩ 1/4W (đỏ-đen-đỏ) | 200đ | Tìm mua |
| SW1 | Nút nhấn 4 chân 6×6mm | 1.500đ | Tìm mua |
| Tổng (chưa gồm board hàn, dây, thiếc) | 181.900đ |
Các bước làm
Code nạp cho mạch
Cài thư viện: Adafruit SSD1306, Adafruit GFX Library (Arduino IDE → Library Manager). Chọn board DOIT ESP32 DEVKIT V1.
/*
Mạch Gọn — Thước đo khoảng cách siêu âm (ESP32 + HC-SR04 + OLED SSD1306)
Thư viện: Adafruit SSD1306, Adafruit GFX Library
Chân : GPIO5 -> TRIG ; ECHO -> 1kΩ -> GPIO18, có 2kΩ xuống GND (BẮT BUỘC)
GPIO21 SDA, GPIO22 SCL -> OLED 3V3 ; GPIO19 nút nhấn về GND
*/
#include <Arduino.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
// ---------- Cấu hình ----------
const uint8_t PIN_TRIG = 5, PIN_ECHO = 18, PIN_NUT = 19;
const float BU_TRU_CM = 0.0; // cộng/trừ bù sai số cố định (cm)
const float XA_NHAT_CM = 300.0; // quá số này coi như không thấy vật
const float GAN_NHAT_CM = 2.0; // HC-SR04 không đo nổi gần hơn
const uint32_t CHU_KY_MS = 200; // nhịp đo
const uint32_t HET_GIO_US = 25000; // chờ xung dội tối đa (~4m)
const uint32_t GIU_DOI_DV_MS = 1500; // giữ nút bao lâu thì đổi cm/inch
Adafruit_SSD1306 man(128, 64, &Wire, -1);
bool manOk = false;
float khoangCach = NAN, ganNhat = 9999, xaNhat = 0;
bool dungDonViInch = false, dangGiuSo = false, daDoiDonVi = false, nutDangNhan = false;
float soDaGiu = NAN;
uint32_t mocDo = 0, mocNhanNut = 0;
// Một lần đo: trả về cm, NAN nếu không có xung dội
float doMotLan() {
digitalWrite(PIN_TRIG, LOW);
delayMicroseconds(3);
digitalWrite(PIN_TRIG, HIGH);
delayMicroseconds(10);
digitalWrite(PIN_TRIG, LOW);
uint32_t us = pulseIn(PIN_ECHO, HIGH, HET_GIO_US);
if (us == 0) return NAN;
float cm = us / 58.0 + BU_TRU_CM;
if (cm < GAN_NHAT_CM || cm > XA_NHAT_CM) return NAN;
return cm;
}
// Đo 5 lần lấy giá trị giữa để bỏ lần dội sai
float doTrungVi() {
float m[5];
uint8_t n = 0;
for (uint8_t i = 0; i < 5; i++) {
float v = doMotLan();
if (!isnan(v)) m[n++] = v;
delay(12);
}
if (n == 0) return NAN;
for (uint8_t i = 1; i < n; i++) { float k = m[i]; int8_t j = i - 1; while (j >= 0 && m[j] > k) { m[j + 1] = m[j]; j--; } m[j + 1] = k; }
return m[n / 2];
}
void veMan() {
if (!manOk) return;
man.clearDisplay();
man.setTextColor(SSD1306_WHITE);
man.setTextSize(1);
man.setCursor(0, 0);
man.print(dangGiuSo ? F("DANG GIU SO") : F("Khoang cach"));
float hien = dangGiuSo ? soDaGiu : khoangCach;
man.setTextSize(3);
man.setCursor(0, 16);
if (isnan(hien)) man.print(F("--.-"));
else man.print(dungDonViInch ? hien / 2.54 : hien, 1);
man.setTextSize(1);
man.setCursor(104, 30);
man.print(dungDonViInch ? F("in") : F("cm"));
// thanh mức 0..100cm
man.drawRect(0, 44, 128, 8, SSD1306_WHITE);
if (!isnan(hien)) {
int w = (int)(hien > 100 ? 126 : hien * 1.26);
if (w > 0) man.fillRect(1, 45, w, 6, SSD1306_WHITE);
}
man.setCursor(0, 56);
if (ganNhat < 9999) { man.print(F("gan ")); man.print(ganNhat, 0); man.print(F(" xa ")); man.print(xaNhat, 0); man.print(F(" cm")); }
else man.print(F("Chua co so do"));
man.display();
}
void setup() {
pinMode(PIN_TRIG, OUTPUT);
digitalWrite(PIN_TRIG, LOW);
pinMode(PIN_ECHO, INPUT);
pinMode(PIN_NUT, INPUT_PULLUP);
Serial.begin(115200);
Serial.println(F("Mach Gon - thuoc do khoang cach sieu am"));
Wire.begin(21, 22);
for (uint8_t a = 0x3C; a <= 0x3D && !manOk; a++) {
if (man.begin(SSD1306_SWITCHCAPVCC, a)) {
manOk = true;
Serial.print(F("Dia chi OLED: 0x")); Serial.println(a, HEX);
}
}
if (!manOk) Serial.println(F("Khong thay man OLED - kiem tra day SDA/SCL"));
else {
man.clearDisplay();
man.setTextColor(SSD1306_WHITE);
man.setTextSize(1);
man.setCursor(0, 24);
man.println(F("Mach Gon"));
man.println(F("Thuoc sieu am"));
man.display();
delay(900);
}
}
void loop() {
uint32_t bayGio = millis();
// 1. Đo theo nhịp
if (bayGio - mocDo >= CHU_KY_MS) {
mocDo = bayGio;
khoangCach = doTrungVi();
if (!isnan(khoangCach) && !dangGiuSo) {
if (khoangCach < ganNhat) ganNhat = khoangCach;
if (khoangCach > xaNhat) xaNhat = khoangCach;
}
Serial.print(F("d="));
if (isnan(khoangCach)) Serial.print(F("--")); else Serial.print(khoangCach, 1);
Serial.print(F(" cm giu=")); Serial.println(dangGiuSo ? 1 : 0);
veMan();
}
// 2. Nút: bấm nhanh giữ/thả số đo, giữ 1,5 giây đổi đơn vị
bool dangNhan = (digitalRead(PIN_NUT) == LOW);
if (dangNhan && !nutDangNhan) { nutDangNhan = true; daDoiDonVi = false; mocNhanNut = bayGio; }
if (dangNhan && nutDangNhan && !daDoiDonVi && bayGio - mocNhanNut >= GIU_DOI_DV_MS) {
daDoiDonVi = true;
dungDonViInch = !dungDonViInch;
Serial.print(F("Doi don vi: ")); Serial.println(dungDonViInch ? F("inch") : F("cm"));
veMan();
}
if (!dangNhan && nutDangNhan) {
nutDangNhan = false;
if (!daDoiDonVi && bayGio - mocNhanNut > 30) {
dangGiuSo = !dangGiuSo;
soDaGiu = khoangCach;
veMan();
}
}
}
🔌 Cắm thử trên breadboard
Cắm nháp trên board cắm trước cho chắc, chạy đúng rồi hãy làm mạch in. Sơ đồ này do máy tự xếp từ chính netlist ở trên và đã đối chiếu lại: hai chân cùng một cột (cùng nửa board) là thông nhau, không có net nào bị chập.
- Mỗi cột 5 lỗ trong cùng một nửa board thông nhau; hai nửa cách nhau bởi rãnh giữa.
- Hai đường kẻ dọc mép board là đường nguồn: hàng + (vạch đỏ) và hàng − (vạch xanh). Nhớ bắc dây nối đường nguồn trên với đường nguồn dưới nếu dùng cả hai nửa.
- Màu dây trên hình trùng với màu trong bảng nối dây ở mục Sơ đồ, cắm theo màu cho dễ dò.
🧲 Mạch in để ủi (PCB 1 mặt)
Bản mạch in một mặt đồng, tự xếp linh kiện và tự đi dây, đã kiểm quy tắc khoảng cách và đối chiếu đúng 100% với sơ đồ nguyên lý ở trên. In tỉ lệ 1:1 rồi ủi lên tấm đồng.
- net 3V3: U1.3V3 → U3.VCC
Hướng dẫn ủi – ăn mòn – khoan – hàn (đầy đủ)
Hướng dẫn làm mạch in bằng phương pháp ủi
Mạch: Thước đo khoảng cách siêu âm hiện số trên màn OLED
Kích thước board: 55,9×55,9 mm · 46 lỗ khoan (mũi 0,8, 0,9 và 1 mm) · 15 đoạn mạch, tổng 327,6 mm · 1 cầu nhảy.
Quy tắc đã kiểm: đường mạch rộng 0,8mm (nguồn và GND 1mm), khe cách tối thiểu 0,5mm. Mạch in đã được máy đối chiếu đúng 100% với sơ đồ nguyên lý.
1. In đúng tỉ lệ 1:1
- In trang "MẶT ĐỒNG" bằng máy in LASER, đặt tỉ lệ 100%, KHÔNG chọn "Fit to page" hay "Scale to fit".
- Dùng thước trên bản in đo lại đoạn 100mm: phải đúng 100mm mới dùng được.
- In trên giấy in ảnh bóng, giấy decal bóc, hoặc giấy tạp chí láng. Mực càng dày càng tốt.
- Trang "MẶT ĐỒNG" đã được LẬT GƯƠNG sẵn: úp mặt có mực vào tấm đồng là đúng chiều.
2. Chuẩn bị tấm đồng
- Cắt phíp đồng (board đồng một mặt) lớn hơn kích thước mạch mỗi bề khoảng 5mm.
- Chà mặt đồng bằng giấy nhám mịn (P800–P1200) rồi rửa sạch, lau khô. Mặt đồng phải sáng, không vết dầu mỡ, không chạm tay vào.
3. Ủi mực sang đồng
- Úp mặt có mực vào tấm đồng, dùng băng keo giấy dán cố định một cạnh.
- Bàn ủi để mức nóng nhất (cotton/linen), KHÔNG dùng chế độ phun hơi nước.
- Ủi đè mạnh và đều 3–5 phút, tập trung từng vùng nhỏ, chú ý các góc và mép.
- Để nguội hẳn rồi ngâm cả tấm vào nước ấm 5–10 phút, bóc giấy thật nhẹ. Giấy decal thì bóc khi còn ấm.
- Soi kỹ: chỗ nào mực bị thiếu hoặc đứt đường thì tô lại bằng bút lông dầu (bút CD) rồi để khô.
4. Ăn mòn (etching)
- Pha dung dịch sắt(III) clorua FeCl3 theo hướng dẫn trên bao bì, hoặc dung dịch oxy già + muối ăn + a-xít; dùng hộp nhựa, KHÔNG dùng đồ kim loại.
- Nhiệt độ dung dịch khoảng 40–50°C thì ăn nhanh hơn. Lắc nhẹ hộp cho đều, thường 10–25 phút là xong.
- An toàn: đeo găng tay cao su và kính bảo hộ, làm ở nơi thoáng. Dung dịch ăn mòn làm hỏng quần áo, bồn rửa và gây bỏng rát da.
- Xong thì rửa board bằng nước sạch, lau khô, dùng xăng thơm hoặc axeton lau sạch lớp mực.
- Dung dịch đã dùng KHÔNG đổ xuống bồn rửa hay cống; đựng vào can kín và bỏ đúng nơi thu gom chất thải.
5. Khoan lỗ
- Đối chiếu trang "BẢN VẼ LỖ KHOAN" để biết lỗ nào dùng mũi nào.
- Dùng mũi khoan 0,8mm, 0,9mm và 1mm, khoan từ MẶT ĐỒNG vào để mũi tự định tâm giữa pad.
- Khoan tốc độ cao, đẩy nhẹ; mũi nhỏ rất dễ gãy. Khoan xong chà nhẹ bavia hai mặt.
6. Hàn linh kiện
- Linh kiện cắm ở MẶT KHÔNG CÓ ĐỒNG, hàn ở mặt đồng. Theo trang "MẶT LINH KIỆN" để đặt đúng vị trí và đúng chiều.
- Hàn linh kiện thấp trước (điện trở, đi-ốt), cao sau (tụ, hàng chân, board vi điều khiển).
- Chú ý chiều: LED chân dài là (+); đi-ốt có vạch trắng là K; tụ hoá có vạch là (−); transistor theo mặt chữ như nét vẽ đỏ.
- Mạch này còn 1 CẦU NHẢY: hàn đoạn dây đơn ở mặt linh kiện theo nét đỏ gạch trên bản vẽ:
- cầu nhảy net 3V3: từ U1.3V3 sang U3.VCC
- Trước khi cấp điện: đo thông mạch giữa chân nguồn và GND — KHÔNG được kêu (không được chập).
Lưu ý với board vi điều khiển
- Board nhái có thể khác thứ tự chân. Đối chiếu tên chân in cạnh từng lỗ trên trang "MẶT LINH KIỆN" với chữ in trên board thật TRƯỚC KHI ăn mòn. Nếu khác, đừng hàn mà báo lại để sửa bản vẽ.
Bản vẽ lỗ khoan
Đo kiểm bằng đồng hồ vạn năng
| Giữa VCC và GND của HC-SR04 | 4,6 – 5,1V |
| Giữa VCC và GND của OLED | 3,2 – 3,4V |
| Điện trở từ GPIO18 xuống GND (rút điện) | khoảng 2kΩ |
| Serial Monitor 115200 | Mỗi 200ms một dòng d=… cm |
| Đặt bìa cứng cách cảm biến 30cm | Màn hình hiện 29 – 31 cm |
Sửa lỗi thường gặp
Màn OLED không sáng
Xem Serial Monitor: code in địa chỉ I2C dò được (0x3C hoặc 0x3D). Không thấy gì là sai dây SDA/SCL hoặc OLED chưa được cấp 3V3.
Số đo luôn là 0 hoặc nhảy loạn
Kiểm tra cầu chia áp: ECHO phải qua 1kΩ rồi mới vào GPIO18, và có 2kΩ xuống GND. Nối thẳng ECHO vào ESP32 sẽ vừa sai vừa hỏng chân.
Đo xa quá 2m là mất tín hiệu
Bình thường: HC-SR04 chỉ ổn định tới khoảng 2–3m với bề mặt phẳng cứng. Tăng diện tích vật phản xạ hoặc chấp nhận giới hạn này.
Số đo nhỏ hơn thực tế vài cm
Đo từ mặt trước hai mắt siêu âm chứ không phải từ mép board. Trừ bù bằng hằng số BU_TRU trong code nếu cần.
ESP32 khởi động lại liên tục
Cổng USB máy tính không đủ dòng. Dùng cục sạc điện thoại 5V ≥1A.
⚠️ An toàn
- Mạch dùng 5V từ cổng USB, an toàn khi chạm.
- Không dùng để đo trong môi trường ẩm ướt hoặc ngoài mưa: HC-SR04 thường không chống nước.
- Không dùng số đo cho việc cảnh báo an toàn con người (cửa tự động, máy móc chuyển động), vì cảm biến có thể bỏ sót vật mềm.