Đã được đăng vào 05/06/2019 @ 11:57
Đo mức nước, mức nhiên liệu đang có trong bồn, bể chứa sử dụng cảm biến siêu âm SR04 và Arduino
Nếu bạn có 1 bồn chứa nước hoặc nhiên liệu đóng kín, bạn sẽ gặp phải vấn đề giống như tôi:
Làm sao để biết lượng nước (nhiện liệu) đang có trong bồn mà không cần mở bồn ra kiểm tra?
Xem thêm:
- Đo khoảng cách (cm-inch) với cảm biến siêu âm SR04T và Arduino
- Cảm biến độ ẩm | Hệ thống tưới tự động sử dụng Arduino
- Đo khoảng cách bằng cảm biến siêu âm HC-SRF04
Đương nhiên là lắp đồng hồ đo rồi!
Dạo quanh chợ 1 vòng thì than ôi, đủ các loại hàng từ cơ đến điện tử, giá cả đắt đỏ và hiệu quả chưa được kiểm chứng.
Vậy là Quyết Định tự làm 1 bộ đo cho riêng mình – Tiêu chí: Ngon – Bổ – Rẻ.
Bộ đo này sử dụng các module siêu âm có sẵn trên thị trường.
Các bạn có thể sử dụng Module siêu âm chống nước JSN-SR04T hoặc Module siêu âm KHÔNG chống nước HC-SR04.
Code hoạt động trên cả 2 loại và không cần chỉnh sửa gì cả, ráp đúng chân là chạy.
Trong bài viết này, mình sẽ lập trình bằng Arduino để đo mức (level) nước, nhiên liệu:
- Đơn vị đo “cm”
- Hiển thị đồng thời qua màn LCD 16×2 dưới dạng thanh báo “%” còn lại và Serial qua máy tính dưới dạng “cm” và có cảnh báo.
- Hiển thị LCD 16×2 dùng cách mắc trực tiếp – Hơi tốn chân GPIO của board Arduino nhưng đỡ tiền mua module I2C (Nếu không muốn dùng cách này có thể tham khảo bài sau để sử dụng cách module I2C cho tiết kiệm chân)
- Đo môi trường ngoài không khí. Trong điều kiện khắc nghiệt, độ ẩm cao.
NGUYÊN LÝ HOẠT ĐỘNG
Cảm biến khoảng cách siêu âm SR04 được sử dụng rất phổ biến để xác định khoảng cách vì RẺ và CHÍNH XÁC.
Cảm biến sử dụng sóng siêu âm và có thể đo khoảng cách trong khoảng từ 2 -> 300 cm, với độ chính xác gần như chỉ phụ thuộc vào cách lập trình.
- Cảm biến SR04 có 4 chân là: Vcc, Trig, Echo, GND.
- Để đo khoảng cách, ta sẽ phát 1 xung rất ngắn (5 microSeconds – us) từ chân Trig. Sau đó, cảm biến sẽ tạo ra 1 xung HIGH ở chân Echo cho đến khi nhận lại được sóng phản xạ ở pin này. Chiều rộng của xung sẽ bằng với thời gian sóng siêu âm được phát từ cảm biển và quay trở lại.
Tốc độ của âm thanh trong không khí là 340 m/s (hằng số vật lý), tương đương với 29,412 microSeconds/cm (106 / (340*100)).
Khi đã tính được thời gian, ta sẽ chia cho 29,412 để nhận được khoảng cách.
Các bước tính toán như sau:
- Đặt chân TRIG của module lên mức Cao (5V) trong ít nhất 10 μs (microseconds)
- Sau đó module siêu âm ghi lại thời gian và gửi ra sóng âm tần số 40Khz
- Sóng siêu âm truyền xuống bề mặt chất lỏng trong bể và phản xạ lại
- Sóng phản xạ sau đó truyền ngược về đầu dò
- Module siêu âm nhận được sóng phản xạ và đánh dấu thời gian nhận được sóng phản hồi
- Cuối cùng, module siêu âm đưa chân ECHO lên mức cao trong khoảng thời gian (microseconds ) phản hồi sóng âm (Gửi đi – nhận về) và tính toán ra khoảng cách.
Kết quả trên chân ECHO có thể được chuyển đổi khá đơn giản về: 58 μs/cm
Vì vậy, nếu chân ECHO lên mức cao trong thời gian 5800 μs (5.8 ms), thì chúng ta tính được khoảng cách giữa cảm biến và mức chất lỏng trong bể là:
5800μs / 58μs/cm = 100cm = 1m
VẬT TƯ CẦN THIẾT
Module siêu âm chống nước JSN-SR04T rất tiện dụng cho các ứng dụng ngoài trời như cảm biến đỗ xe; đo khoảng cách ngoài trời; đo mức nước bể cá, bể nước, đo mức nhiên liệu xăng, dầu,…
Bảng thông số của module JSN-SR04T mình trích ra từ tài liệu nhà sản xuất: Download
Giải thích:
Điện áp hoạt động | DC 3 – 5.5V |
Dòng tiêu thụ | Khoảng 8mA |
Tần số đầu dò | 40KHz |
Dải đo | 20cm – 600cm |
Sai số | +- 1cm |
Độ phân giải | 1mm |
Góc mở đầu dò | 75 độ |
Chú ý:
Vì cảm biến JSN-SR04T có góc mở rất lớn ( >= 75 độ) nên KHÔNG phù hợp đo khoảng cách trong không gian chật hẹp.
Chỉ nên dùng trong không gian mở như ngoài trời hoặc bể chứa kích thước lớn- không vật cản.
Nếu đo trong không gian chật hẹp thì sai số rất lớn do sóng phản xạ lại bị va đập vào vật cản hoặc thành bể – bình chứa.
Ảnh dưới mô tả góc quét và phản xạ của 1 loại cảm biến siêu âm trên thị trường, tùy loại sẽ có góc khác nhau:
SƠ ĐỒ NGUYÊN LÝ
Sơ đồ chân kết nối
MODULE PIN | ARDUINO PIN |
Trigger pin (Module SR04) | Pin 8 |
Echo Pin (Module SR04) | Pin 9 |
RS Pin (LCD) | Pin A4 |
EN Pin (LCD) | Pin A5 |
D4 Pin (LCD) | Pin 5 |
D5 Pin (LCD) | Pin 4 |
D6 Pin (LCD) | Pin 3 |
D7 Pin (LCD) | Pin 2 |
Những chân còn lại của LCD | Nối theo sơ đồ (hĩnh vẽ) |
LED xanh | Pin 6 |
LED đỏ | Pin 7 |
5V | 5V |
GND | GND |
THƯ VIỆN CHO CẢM BIẾN SIÊU ÂM
- Thư viện NewLiquidCrystal : Download here
- Thư viện LiquidCrystal : Download here
Hướng dẫn: Tải thư viện về và copy vào thư mục library của Arduino trong Documents
CODE
Code Arduino sử dụng thư viện LiquidCrystal, hiển thị đồng thời LCD dạng “%” và Serial đợn vị đo “cm”
Môi trường đo trong không khí, có điều chỉnh mức cảnh báo “dưới ngưỡng” hoặc “vượt ngưỡng” cho phép, tín hiệu cảnh báo “dưới ngưỡng” hoặc “vượt ngưỡng” đưa ra 2 đèn LED (dễ dàng thay bằng còi hú, relay.. tùy bạn chỉnh) và cảnh báo cả trên Serial:
Download here
/* Arduino Uno Ultrasonic Fuel Gauge / Liquid Level Sensor System by Scott Ogrin MIT License */ // include the library code: #include <LiquidCrystal.h> #include <math.h> #define G_LED 6 // Onboard Green LED #define R_LED 7 // Onboard Red LED // Init vars // ************** // CHANGE ME! // ************** const int tankEmptyDepth = 200; // This MUST be no greater than 450 cm (500cm for the HC-SR04)! const int tankFullDepth = 25; // This should be at least 25 cm, if possible (2cm for the HC-SR04) // Change the above tankEmptyDepth and tankFullDepth constants to be the distance (in centimeters): // - tankEmptyDepth = between the ultrasonic sensor and the empty level (i.e. bottom of tank) // - tankFullDepth = between the sensor and the liquid when the tank is full (MINIMUM 25cm) // Note that the ultrasonic sensor works only between 25cm and 450cm, so the min tankFullDepth = 25. // For my tank, the tankFullDepth = 15, which is okay... BUT it means that when the tank is full, // I will probably get incorrect readings until the level drops 10cm. This isn't a problem in my case // since I I don't care about accurate level readings when the fuel tank is full! But it means that // after getting the tank filled, the level will read near-empty or "Error: Timeout". // // You could also use the HC-SR04, which is larger but has a min depth of 2cm and max of 500cm. // Note however that it's not waterproof! I chose the JSN-SR04T-2.0 for that reason. // // Note also that you might want to set tankEmptyDepth to be less than the bottom of your tank, // esp if you have a vertical feed sucking liquid from, say, 5cm above the bottom of the tank. // For example, my tank is 163cm deep from the sensor, so I set tankEmptyDepth to 153. This ensures // that when my LevelMatic reads 0%, I should have 10cm of fuel left in the tank. // // If measuring in inches: 1 inch = 2.54 cm // These vars hold the current and last-measured level from the sensor int currentLevel = 0; int lastLevel = 0; int maximumRange = 190; // Maximum range needed int minimumRange = 30; // Minimum range needed // These vars are for showPartialBarChar() and showCurrentLevel() int done = 0; char levelTxt[] = "Current level:"; // Var for showError // This error means the ultrasound unit couldn't do a measurement, and timed out // Usually that means the sensor is at a weird angle, too close to the liquid, or // the ultrasound waves are bouncing off the walls of the tank. char timeoutErrorTxt[] = "ERROR: Timeout"; // Var for echo response from ultrasonic sensor board unsigned long timeHigh; // Custom chars for LCD byte barEmpty[8] = { B11111, B00000, B00000, B00000, B00000, B00000, B00000, B11111, }; byte barOne[8] = { B11111, B10000, B10000, B10000, B10000, B10000, B10000, B11111, }; byte barTwo[8] = { B11111, B11000, B11000, B11000, B11000, B11000, B11000, B11111, }; byte barThree[8] = { B11111, B11100, B11100, B11100, B11100, B11100, B11100, B11111, }; byte barFour[8] = { B11111, B11110, B11110, B11110, B11110, B11110, B11110, B11111, }; // constants for ultrasonic sensor board IO pins const int trigPin = 8, echoPin = 9; // constants for LCD IO pins const int rs = 12, en = 11, d4 = 5, d5 = 4, d6 = 3, d7 = 2; // initialize the library by associating any needed LCD interface pin // with the arduino pin number it is connected to LiquidCrystal lcd(rs, en, d4, d5, d6, d7); void setup() { // Set up IO pins for ultrasonic sensor board pinMode(trigPin, OUTPUT); pinMode(echoPin, INPUT); // Set trigger pin for sensor to 0 (aka "do nothing yet") digitalWrite(trigPin, LOW); pinMode(G_LED, OUTPUT); // Use LED indicator (if required) pinMode(R_LED, OUTPUT); // Use LED indicator (if required) Serial.begin(9600); // Serial monitoring // set up the LCD's number of columns and rows: lcd.begin(16, 2); // Set custom chars 0-7 lcd.createChar(0, barEmpty); lcd.createChar(1, barOne); lcd.createChar(2, barTwo); lcd.createChar(3, barThree); lcd.createChar(4, barFour); lcd.clear(); } void loop() { // Do level scan with ultrasonic board // Start a scan - trigger pin must be high for at least 10us digitalWrite(trigPin, HIGH); delayMicroseconds(10); digitalWrite(trigPin, LOW); // Get time echo pin is high using pulseIn, which returns value in microseconds // Default timeout of 1s is more than enough for 8 pulses, and we're not in a hurry timeHigh = pulseIn(echoPin, HIGH); if (timeHigh == 0) { // Oops! Timeout... showError(); } else { // Calculate level // Assume 343 m/s for the speed of the 40kHz sound waves in air at standard temperature and pressure // It's 58 us/cm, which we get from: // (343 m/s * (1s / 1000000 us) * (100cm / 1m)) / 2 = 0.01715 cm / us // Must divide by 2 because sound wave travels to liquid, and back // Invert that to get: // 1 / 0.01715 cm/us = 58.309038 us/cm // Note resolution of ultrasonic sensor is +/- 0.5cm currentLevel = round(timeHigh / 58); if (currentLevel > tankEmptyDepth) { // If level is lower than empty, show 0% // This is useful if you want to have "empty" be "still 10cm of liquid left in tank" currentLevel = tankEmptyDepth; } else if (currentLevel < tankFullDepth) { // If level is higher than full, show 100% // This is useful since "full" level may vary when tank is refilled currentLevel = tankFullDepth; } // Don't redraw screen if level is the same as last time if (currentLevel != lastLevel) { lastLevel = currentLevel; showCurrentLevel(); } } if (currentLevel <= minimumRange){ /* Send a negative number to computer and Turn LED ON to indicate "out of range" */ Serial.println("SAFE"); digitalWrite(R_LED, LOW); digitalWrite(G_LED, HIGH); } else if (currentLevel >= maximumRange){ /* Send the distance to the computer using Serial protocol, and turn LED OFF to indicate successful reading. */ Serial.println("DANGER"); digitalWrite(R_LED, HIGH); digitalWrite(G_LED, LOW); } else { /* Send the distance to the computer using Serial protocol, and turn LED OFF to indicate successful reading. */ Serial.println("NORMAL"); digitalWrite(R_LED, LOW); digitalWrite(G_LED, LOW); } // Delay 2s between scans delay(2000); } void showError() { lcd.clear(); lcd.setCursor(0, 0); lcd.print(timeoutErrorTxt); } void showPartialBarChar(int val) { switch (val) { case 0: lcd.write(byte(0)); // barEmpty ++done; break; case 1: lcd.write(byte(1)); // one bar ++done; break; case 2: lcd.write(byte(2)); // two bars ++done; break; case 3: lcd.write(byte(3)); // three bars ++done; break; case 4: lcd.write(byte(4)); // four bars ++done; break; } } void showCurrentLevel() { // Get integer between 0 and 50 for bar graph // We have 10 progress bar characters, and each character can have 0-5 vertical columns of pixels // Subtracting tankFullDepth gives us a precise ratio between full/empty, as if the ultrasonic sensor // would be 0 cm away from the liquid level when the tank is full. // Also, currentLevel contains height of "emptiness" above the liquid, so to get liquid level we do: // abs(1 - currentLevel/tankEmptyDepth). float ratio = 1 - ((float)currentLevel - (float)tankFullDepth) / ((float)tankEmptyDepth - (float)tankFullDepth); ratio = abs(ratio); int textLevelInt = round(ratio * 100.0); int levelInt = round(ratio * 50.0); int fulls = 0; // Reset done done = 0; // Display text above progress bar lcd.clear(); lcd.setCursor(0, 0); lcd.print(levelTxt); // Display progress bar based on levelInt lcd.setCursor(0, 1); // Draw progress bar for XX% fulls = levelInt / 5; if (fulls == 0) { // First char on bar is a partial char with 0-4 vertical columns of pixels showPartialBarChar(levelInt); } else { for (int i = 0; i < fulls; ++i) { lcd.write(255); // full ++done; } } if (done < 10) { if (fulls > 0) { // Here we may have a partial char with 0-4 vertical columns of pixels showPartialBarChar(levelInt - (fulls * 5)); } // Here we may have blank boxes left if (done < 10) { // We have empty boxes to draw for (int i = 0; i < 10 - done; ++i) { lcd.write(byte(0)); // barEmpty } } } // Lastly, print percentage: if (textLevelInt == 100) { lcd.setCursor(12, 1); lcd.print("100%"); } else if (textLevelInt < 10) { lcd.setCursor(14, 1); lcd.print(textLevelInt); lcd.print("%"); } else { lcd.setCursor(13, 1); lcd.print(textLevelInt); lcd.print("%"); } Serial.print("Distance "); Serial.print(currentLevel); Serial.println("cm"); delay(100); }
- Điều chỉnh “%” lượng nước hiển thị trên LCD thông qua 2 dòng này trong code (Các bạn tự điều chỉnh phù hợp với thực tế bể chứa nhà bạn – Nên tính toán sao cho vẫn còn dư 10cm so với ngưỡng cảnh báo để dự phòng khẩn cấp):
—- Khoảng cách từ cảm biến đến mặt nước 200cm- Bể hết-> Hiển thị 0% (Tối đa 600cm – Khuyến nghị dùng trong 250cm)
const int tankEmptyDepth = 200;
—- Khoảng cách từ cảm biến đến mặt nước còn 25cm- Bể đầy-> Hiển thị 100% (Tối thiểu 20cm)
const int tankFullDepth = 25;
- Điều chỉnh thông số cảnh báo “dưới ngưỡng” hoặc “vượt ngưỡng” ở 2 dòng dưới đây (Các bạn tự điều chỉnh phù hợp với thực tế bể chứa nhà bạn – Nên tính toán sao cho vẫn còn dư 10cm so với ngưỡng cảnh báo để dự phòng khẩn cấp ) :
—- Khoảng cách từ cảm biến đến mặt nước 190cm- Bể sắp hết-> cảnh báo (Tối đa 600cm – Khuyến nghị dùng tối đa trong khoảng 250-450cm)
int maximumRange = 190;
—- Khoảng cách từ cảm biến đến mặt nước còn 30cm- Bể sắp đầy-> cảnh báo (Tối thiểu 20cm)
int minimumRange = 30;
- Chân kết nối 2 LED cảnh báo: Pin 6 và Pin 7 của Arduino nối với Anot (+) của mỗi LED (Có thể nối Loa báo động hoặc Relay vào 2 chân này thay cho LED) :
define G_LED 6 // Onboard Green LED – Pin 6
define R_LED 7 // Onboard Red LED – Pin 7
TEST
Mạch hoạt động khá tốt, khoảng cách đo mức nước, mức nhiên liệu tối thiểu 20cm và tối đa 6m (khuyến cáo đo tối đa trong khoảng 2.5 – 4m).
- Nếu muốn đo mức nhiên liệu chính xác hơn và dùng trong không gian nhỏ, chật hẹp thì nên dùng module HC-SR04
- Nếu muốn đo trong không gian rộng rãi + yêu cầu chống nước, đo dưới nước, đo trong môi trường khắc nghiệt thì nên dùng module JSC-SR04T
Nguồn: scottiestech.info
Nguyễn Phan Anh viết
Tại sao tôi chép cái code này nên arduino thì nó bảo đã xảy ra lỗi khi tải lên bản phác thảo. Bạn giúp mình với
admin viết
Bạn có thể mô tả rõ hơn được không? Lỗi rất nhiều nguyên nhân. Có thể do sai thư viện hoặc trùng lặp thư viện. Cũng có thể trong quá trình copy chưa chuẩn…
Nguyễn Phan Anh viết
Hôm nay mình có qua 1 chỗ có 1 anh kỹ thuật giúp mình chép code y hệt như trên ý thì phải chờ 5 – 10 phút thì nó thông báo cho mình là lỗi gì đó mình không nhớ đc, anh kỹ thuật đó bảo code này nó lỗi rồi bạn à
admin viết
Chào bạn Phan Anh, mình không rõ anh kỹ thuật bên bạn có thao tác đúng trong quá trình build không? Tuy nhiên, mình có build lại thì code chạy bình thường bạn nhé! Chi tiết quá trình build mình đã gửi vào email của bạn rồi đó!