Cảm biến lưu lượng nước YF-S201 với ESP32: nối dây 3,3 V, đếm xung, lít/phút, tổng lít, hiệu chuẩn

Dùng cảm biến lưu lượng YF-S201 với ESP32: nguyên lý tua bin Hall, F = 7,5 × Q, nối dây an toàn 3,3 V bằng diode, đếm xung bằng ngắt, tính lít/phút, tổng lít lưu flash, hiệu chuẩn bằng xô đo.

YF-S201 là cảm biến lưu lượng nước kiểu tua bin, ren 1/2 inch, rất hay dùng trong máy lọc nước, hệ tưới, đồng hồ đo nước tự làm. Cảm biến không cho ra số lít mà cho ra một chuỗi xung: vi điều khiển phải đếm xung rồi tự tính lưu lượng và tổng thể tích. Bài này giải thích nguyên lý, cách nối YF-S201 với ESP32 an toàn ở mức 3,3 V, chương trình đếm xung bằng ngắt, tính lít/phút mỗi giây, cộng dồn tổng lít có lưu vào flash, và cách hiệu chuẩn bằng một xô đo để số đọc sát thực tế.

1. Nguyên lý và thông số

Nguyên lý cảm biến lưu lượng YF-S201: nước quay tua bin có nam châm, cảm biến Hall tạo xung ở dây vàng, tần số F bằng 7,5 nhân lưu lượng Q tính bằng lít mỗi phút
Tần số xung tỉ lệ với lưu lượng; tổng số xung tỉ lệ với thể tích.

Nước chảy qua làm quay một tua bin nhựa có gắn nam châm. Bên ngoài ống, một cảm biến Hall đổi mức mỗi khi nam châm đi qua, tạo ra xung vuông ở dây vàng. Thông số thường ghi cho YF-S201:

  • Nguồn 5–18 V (dùng 5 V khi đi với ESP32); dây đỏ +, đen GND, vàng xung.
  • Dải đo khoảng 1–30 L/phút; dưới 1 L/phút tua bin quay không đều hoặc đứng yên.
  • Hệ số: F (Hz) = 7,5 × Q (L/phút), tức khoảng 450 xung cho mỗi lít.
  • Sai số khi chưa hiệu chuẩn khoảng ±10 %; hệ số thật thay đổi theo từng con, theo áp lực và theo cách lắp.
  • Áp lực tối đa khoảng 1,75 MPa; nước sạch, nhiệt độ dưới 80 °C. Bản YF-S201C có thân trong suốt, cùng cách dùng.

Lắp cảm biến đúng chiều mũi tên trên thân, đoạn ống trước cảm biến nên thẳng; ống có bọt khí hoặc cặn sẽ làm tua bin quay sai.

2. Nối dây với ESP32

Sơ đồ nối YF-S201 với ESP32 DevKit V1: dây đỏ vào VIN, dây đen vào GND, dây vàng qua diode Schottky tới D27, điện trở 10 kΩ kéo D27 lên 3V3
Cần một diode Schottky và một điện trở 10 kΩ.

YF-S201 cần nguồn 5 V, lấy từ chân VIN của ESP32 DevKit khi cắm USB (khoảng 4,7–5 V). Vấn đề nằm ở dây vàng: tùy lô hàng, đầu ra có thể là cực góp hở (chỉ kéo xuống GND) hoặc có sẵn điện trở kéo lên 5 V bên trong, khi đó mức cao lên tới 5 V, quá mức chịu của chân GPIO ESP32.

Mạch giao tiếp YF-S201 với 3,3 V: transistor đầu ra trong cảm biến, diode Schottky D1 với vạch quay về dây vàng, điện trở R1 10 kΩ từ GPIO27 lên 3V3
Diode chỉ cho dòng đi từ GPIO về cảm biến.
  • Khi cảm biến kéo xuống: dòng từ 3,3 V qua R1 và diode về cảm biến, chân GPIO còn khoảng 0,3–0,6 V, dưới ngưỡng mức thấp 0,8 V của ESP32.
  • Khi cảm biến nhả: diode bị phân cực ngược, R1 kéo chân GPIO lên 3,3 V. Dù bên trong cảm biến có kéo lên 5 V, điện áp đó không đi qua được diode.
  • Dùng diode Schottky (1N5817, BAT43, BAT85) vì sụt áp nhỏ. Diode thường như 1N4148 sụt áp lớn hơn, mức thấp có thể sát ngưỡng.
  • Cầu chia điện trở (10 kΩ / 20 kΩ) chỉ hợp với đầu ra đẩy kéo; với đầu ra có kéo lên bên trong, điện trở kéo lên đó cộng vào cầu chia làm mức cao tụt xuống dưới ngưỡng. Cách diode không phụ thuộc loại đầu ra.

Cách nối an toàn và các trường hợp cần chuyển mức khác có ở bài mức logic 3,3 V và 5 V.

3. Từ xung ra lít/phút

Bảng tần số xung theo lưu lượng YF-S201: 1 L/phút 7,5 Hz, 10 L/phút 75 Hz, 30 L/phút 225 Hz, chu kỳ xung và sai số do đếm thiếu một xung ở cửa sổ 1 giây và 5 giây
Lưu lượng nhỏ: số L/phút nhảy theo từng xung.

Với hệ số K xung/lít, nếu trong khoảng thời gian t giây đếm được n xung thì:

  • lưu lượng Q = n × 60 / (K × t) L/phút;
  • thể tích chảy qua trong khoảng đó là n / K lít, cộng dồn lại thành tổng.

Ở 30 L/phút tần số chỉ 225 Hz (chu kỳ 4,4 ms), nên ngắt GPIO đếm thoải mái, không cần bộ đếm phần cứng. Cửa sổ đếm 1 giây cho số đọc nhanh; ở lưu lượng nhỏ (1–2 L/phút) mỗi giây chỉ có 7–15 xung, sai một xung đã lệch vài phần trăm, khi đó tăng CHU_KY_MS lên 3–5 giây. Tổng lít không bị ảnh hưởng vì không xung nào bị bỏ. Cách dùng ngắt và chống nhiễu xung có ở bài ngắt GPIO trên ESP32.

4. Chương trình ESP32

Chuẩn bị: ESP32 DevKit V1 30 chân, cảm biến YF-S201, một diode Schottky, một điện trở 10 kΩ. Chương trình chỉ dùng thư viện có sẵn trong gói esp32 cho Arduino IDE.

// PYWORLD.VN - Đo lưu lượng nước bằng cảm biến YF-S201 với ESP32: đếm xung bằng ngắt, tính lít/phút mỗi giây và tổng số lít
// PYWORLD.VN - Hệ số mặc định 450 xung/lít (F = 7,5 × Q); hiệu chuẩn bằng xô đo: gõ "c" để bắt đầu, xả đúng thể tích, gõ "c5" (5 lít)
// PYWORLD.VN - Dây vàng -> diode Schottky (vạch về phía cảm biến) -> GPIO27, kéo lên 10 kΩ lên 3V3; dây đỏ VIN (5 V), dây đen GND

#include <Preferences.h>

const int CHAN_XUNG = 27;
const float XUNG_MOI_LIT_MAC_DINH = 450.0f;       // 7,5 Hz cho mỗi L/phút × 60 giây
const uint32_t CHU_KY_MS = 1000;                  // cửa sổ đếm; lưu lượng nhỏ thì cửa sổ dài cho số ổn định hơn
const uint32_t XUNG_NGAN_NHAT_US = 1000;          // 30 L/phút ≈ 225 Hz (chu kỳ 4,4 ms): xung cách nhau dưới 1 ms là nhiễu
const double LUU_MOI_LIT = 1.0;                   // ghi tổng vào flash khi tăng thêm 1 lít…
const uint32_t LUU_TOI_THIEU_MS = 60000;          // …và cách lần ghi trước ít nhất 1 phút (flash có số lần ghi giới hạn)
const uint32_t CANH_BAO_CHAY_MS = 30UL * 60 * 1000;  // nước chảy liên tục 30 phút: có thể rò rỉ hoặc quên khóa vòi

Preferences nvs;
volatile uint32_t demXung = 0;
volatile uint32_t lucXungTruoc = 0;
portMUX_TYPE khoaDem = portMUX_INITIALIZER_UNLOCKED;

float xungMoiLit = XUNG_MOI_LIT_MAC_DINH;
double tongLit = 0, tongDaLuu = 0;
uint32_t xungTruoc = 0, lucTinh = 0, lucLuu = 0, lucBatDauChay = 0;
bool dangChay = false, daCanhBao = false;
bool dangHieuChuan = false;
uint32_t xungDauHieuChuan = 0;

void IRAM_ATTR ngatXung() {
  uint32_t bayGio = micros();
  portENTER_CRITICAL_ISR(&khoaDem);
  if (bayGio - lucXungTruoc >= XUNG_NGAN_NHAT_US) {
    demXung = demXung + 1;              // không viết demXung++ với biến volatile (C++20 báo cảnh báo)
    lucXungTruoc = bayGio;
  }
  portEXIT_CRITICAL_ISR(&khoaDem);
}

uint32_t docDem() {
  portENTER_CRITICAL(&khoaDem);
  uint32_t n = demXung;
  portEXIT_CRITICAL(&khoaDem);
  return n;
}

void luuTong() {
  nvs.putDouble("tong", tongLit);
  tongDaLuu = tongLit;
  lucLuu = millis();
}

void xuLyLenh() {
  if (!Serial.available()) return;
  String lenh = Serial.readStringUntil('\n');
  lenh.trim();
  if (lenh == "r") {
    tongLit = 0;
    luuTong();
    Serial.println("Da dat tong ve 0");
  } else if (lenh == "c") {
    dangHieuChuan = true;
    xungDauHieuChuan = docDem();
    Serial.println("HIEU CHUAN: xa nuoc vao xo do, du the tich thi khoa voi va go c<so lit>, vi du c5");
  } else if (lenh.startsWith("c") && dangHieuChuan) {
    float lit = lenh.substring(1).toFloat();
    uint32_t n = docDem() - xungDauHieuChuan;
    if (lit <= 0 || n < 200) {
      Serial.printf("Khong hieu chuan: %u xung, %.2f lit (can it nhat 200 xung)\n", (unsigned)n, lit);
    } else {
      xungMoiLit = n / lit;
      nvs.putFloat("k", xungMoiLit);
      Serial.printf("Dem duoc %u xung cho %.2f lit -> he so moi %.1f xung/L (da luu)\n", (unsigned)n, lit, xungMoiLit);
    }
    dangHieuChuan = false;
  } else if (lenh.startsWith("k")) {
    float k = lenh.substring(1).toFloat();
    if (k > 50 && k < 5000) {
      xungMoiLit = k;
      nvs.putFloat("k", xungMoiLit);
      Serial.printf("He so %.1f xung/L (da luu)\n", xungMoiLit);
    }
  } else {
    Serial.println("Lenh: r = dat tong ve 0, c = bat dau hieu chuan, c5 = ket thuc voi 5 lit, k450 = dat he so");
  }
}

void setup() {
  Serial.begin(115200);
  nvs.begin("luuluong", false);
  xungMoiLit = nvs.getFloat("k", XUNG_MOI_LIT_MAC_DINH);
  tongLit = tongDaLuu = nvs.getDouble("tong", 0);
  pinMode(CHAN_XUNG, INPUT_PULLUP);               // có thêm điện trở 10 kΩ ngoài cho dây dài
  attachInterrupt(digitalPinToInterrupt(CHAN_XUNG), ngatXung, FALLING);
  lucTinh = lucLuu = millis();
  Serial.printf("YF-S201: he so %.1f xung/L, tong da luu %.3f L\n", xungMoiLit, tongLit);
}

void loop() {
  xuLyLenh();
  uint32_t bayGio = millis();
  if (bayGio - lucTinh < CHU_KY_MS) return;

  uint32_t xung = docDem();
  uint32_t moi = xung - xungTruoc;                // phép trừ không dấu vẫn đúng khi bộ đếm tràn
  float giay = (bayGio - lucTinh) / 1000.0f;
  xungTruoc = xung;
  lucTinh = bayGio;

  float tanSo = moi / giay;
  float litPhut = tanSo * 60.0f / xungMoiLit;
  tongLit += moi / (double)xungMoiLit;

  bool chay = moi > 0;
  if (chay && !dangChay) {
    lucBatDauChay = bayGio;
    daCanhBao = false;
  }
  if (chay && !daCanhBao && bayGio - lucBatDauChay >= CANH_BAO_CHAY_MS) {
    Serial.println("CANH BAO: nuoc chay lien tuc 30 phut");
    daCanhBao = true;
  }
  // Ghi flash khi đủ 1 lít và đủ 1 phút, hoặc ngay lúc nước vừa ngừng chảy
  bool vuaDung = dangChay && !chay;
  dangChay = chay;
  if ((tongLit - tongDaLuu >= LUU_MOI_LIT && bayGio - lucLuu >= LUU_TOI_THIEU_MS) || (vuaDung && tongLit > tongDaLuu)) {
    luuTong();
  }

  Serial.printf("Q %.2f L/phut | %.1f Hz | tong %.3f L%s\n", litPhut, tanSo, tongLit, dangHieuChuan ? " | dang hieu chuan" : "");
}
  • Hàm ngắt ngatXung() chỉ tăng bộ đếm, có IRAM_ATTR và khóa portMUX để vòng lặp chính đọc bộ đếm không bị chen ngang. Xung cách nhau dưới 1 ms bị bỏ vì ở dải đo của YF-S201 chúng chỉ có thể là nhiễu.
  • Mỗi giây chương trình lấy hiệu số xung, tính Q theo thời gian thật đã trôi qua (không giả định đúng 1000 ms), cộng thể tích vào tổng.
  • Lưu tổng vào flash bằng Preferences khi tăng thêm 1 lít và đã qua 1 phút, hoặc ngay lúc nước vừa ngừng chảy, nên mất điện chỉ mất phần lẻ dưới 1 lít mà flash không bị ghi liên tục. Cách dùng Preferences có ở bài lưu dữ liệu khi mất điện.
  • Cảnh báo khi nước chảy liên tục 30 phút: dấu hiệu rò rỉ hoặc quên khóa vòi; sửa CANH_BAO_CHAY_MS theo cách dùng thực tế.
  • Lệnh Serial: r đặt tổng về 0, c và c5 để hiệu chuẩn, k450 đặt hệ số bằng tay.

5. Hiệu chuẩn bằng xô đo

Serial Monitor khi hiệu chuẩn YF-S201: gõ c, xả 5 lít vào xô, chương trình đọc 5,133 lít với hệ số 450, gõ c5 được hệ số mới 462 xung mỗi lít; sau đó mở vòi 8 lít mỗi phút đọc 7,92 đến 8,05
Số liệu mô phỏng, cảm biến giả định 462 xung/lít.
  1. Lắp cảm biến vào đúng đường ống sẽ dùng (áp lực và đoạn ống ảnh hưởng tới hệ số).
  2. Chuẩn bị xô hoặc can có vạch chia, càng lớn càng chính xác (5–20 lít).
  3. Gõ c trong Serial Monitor, mở vòi với lưu lượng thường dùng, khóa vòi khi nước đúng vạch.
  4. Gõ c kèm số lít, ví dụ c5. Chương trình tính hệ số mới = số xung / số lít và lưu lại.
  5. Làm lại 2–3 lần, lấy trung bình rồi đặt bằng lệnh k nếu các lần lệch nhau.

Ở ví dụ trên, cảm biến thật cho 462 xung/lít; với hệ số mặc định 450, chương trình đọc 5,133 lít cho một xô 5 lít (cao hơn 2,7 %). Sau khi hiệu chuẩn, số đọc khớp. Hệ số có thể khác nhau giữa lưu lượng nhỏ và lớn; nếu dùng ở nhiều mức lưu lượng, hiệu chuẩn ở mức hay dùng nhất.

6. Lỗi hay gặp

Hiện tượngNguyên nhânCách xử lý
Luôn 0 L/phútThiếu nguồn 5 V, diode cắm ngược, lắp ngược chiều nướcĐo 5 V ở dây đỏ; vạch diode về phía dây vàng; xem mũi tên trên thân
Có số khi không có nước chảyNhiễu vào dây tín hiệu dài, chân thả nổiĐiện trở kéo lên 10 kΩ, dây xoắn hoặc cáp có lưới, đi xa dây 220 V
Lưu lượng nhỏ đọc 0 hoặc nhảyDưới 1 L/phút tua bin không quay đều; cửa sổ đếm ngắnTăng CHU_KY_MS; dùng cảm biến cỡ nhỏ hơn cho lưu lượng nhỏ
Tổng lít lệch so với đồng hồ nướcChưa hiệu chuẩn, có bọt khí, áp lực khác lúc hiệu chuẩnHiệu chuẩn tại chỗ; xả khí trong ống
ESP32 khởi động lại khi bơm chạyNhiễu từ bơm, relay qua nguồn chungTụ lọc nguồn, nguồn riêng cho bơm, mass đi riêng
Mất tổng sau khi mất điệnChưa kịp ghi flash (dưới 1 lít)Bình thường theo thiết kế; giảm LUU_MOI_LIT nếu cần

7. Xem thêm

Bài cùng cụm chủ đề: đo mực nước bồn và tự bơm bằng ESP32.


Tin tức liên quan

LED ma trận MAX7219 và LED 7 đoạn TM1637 với ESP32: chữ chạy 32 × 8 và đồng hồ, không cần thư viện
LED ma trận MAX7219 và LED 7 đoạn TM1637 với ESP32: chữ chạy 32 × 8 và đồng hồ, không cần thư viện

So sánh LED ma trận MAX7219 và LED 7 đoạn TM1637, nối với ESP32 an toàn mức điện áp, giải thích khung 16 bit và ba lệnh TM1637, hai chương trình chữ chạy và đồng hồ không cần thư viện.

Module thời gian thực DS3231 với ESP32: đọc giờ, đặt giờ, báo thức hằng ngày, giữ giờ khi mất điện
Module thời gian thực DS3231 với ESP32: đọc giờ, đặt giờ, báo thức hằng ngày, giữ giờ khi mất điện

DS3231 khác DS1307 thế nào, nối với ESP32 qua I2C, thanh ghi và mã BCD, chương trình đọc giờ, nhiệt độ, đặt giờ qua Serial và báo thức hằng ngày qua chân SQW, kèm bảng lỗi.

AccelStepper: tăng tốc, giảm tốc mượt cho động cơ bước NEMA 17, A4988, ESP32, về gốc, hàm hay dùng
AccelStepper: tăng tốc, giảm tốc mượt cho động cơ bước NEMA 17, A4988, ESP32, về gốc, hàm hay dùng

Dùng thư viện AccelStepper cho động cơ bước NEMA 17 và A4988 trên ESP32: vì sao cần tăng tốc, cách thư viện tính khoảng cách bước, nối dây, về gốc bằng công tắc, hàm hay dùng, lỗi thường gặp.

Cảm biến siêu âm HC-SR04: nguyên lý, sai số theo nhiệt độ, nối ESP32 3,3 V và code đo khoảng cách
Cảm biến siêu âm HC-SR04: nguyên lý, sai số theo nhiệt độ, nối ESP32 3,3 V và code đo khoảng cách

Cảm biến siêu âm HC-SR04 đo khoảng cách thế nào, nối với ESP32 3,3 V qua cầu chia ở chân Echo, sai số theo nhiệt độ và cách bù, chương trình đo có lọc trung vị, khi nào cảm biến đo sai.

Cách nạp code cho ESP-01 và ESP-01S bằng USB-TTL, đế nạp: sơ đồ chân và lỗi thường gặp
Cách nạp code cho ESP-01 và ESP-01S bằng USB-TTL, đế nạp: sơ đồ chân và lỗi thường gặp

Cách nạp code cho ESP-01, ESP-01S: sơ đồ 8 chân, khác nhau giữa hai bản, nối USB-TTL đúng cách, cấp nguồn 3,3 V đủ dòng, chế độ nạp GPIO0, cài Arduino IDE, chương trình đầu tiên và lỗi hay gặp.

Dải LED WS2812B với ESP32: điều khiển từng bóng bằng RMT không cần thư viện, tính dòng và chọn nguồn
Dải LED WS2812B với ESP32: điều khiển từng bóng bằng RMT không cần thư viện, tính dòng và chọn nguồn

Bên trong bóng WS2812B, tín hiệu một dây 24 bit GRB, cách tính dòng để chọn nguồn, nối dải LED với ESP32 và tạo tín hiệu bằng bộ RMT không cần thư viện, kèm ba hiệu ứng.

Điều khiển vị trí động cơ DC bằng PID trên ESP32: encoder, quỹ đạo hình thang, bù vùng chết
Điều khiển vị trí động cơ DC bằng PID trên ESP32: encoder, quỹ đạo hình thang, bù vùng chết

Điều khiển vị trí động cơ DC có encoder bằng PID trên ESP32 và L298N: quay đúng góc, quỹ đạo hình thang, bù vùng chết, vùng nghỉ chống rung, lệnh Serial, mô phỏng ba cách và cách chỉnh thông số.

Màn hình TFT SPI ILI9341 với ESP32: nối dây, lệnh khởi động, vẽ chữ và đồ thị không cần thư viện
Màn hình TFT SPI ILI9341 với ESP32: nối dây, lệnh khởi động, vẽ chữ và đồ thị không cần thư viện

Nối màn hình TFT ILI9341 2.4–2.8 inch với ESP32, các lệnh khởi động và vẽ vùng, màu RGB565, bộ chữ 5x7, chương trình vẽ số lớn và đồ thị điện áp không nhấp nháy, bảng sửa lỗi.


Bình luận
Đã thêm vào giỏ hàng