Sau một thời gian đi quảng cáo spam khắp nơi thì mình thấy hệ thống nông nghiệp thông minh được các bạn ủng hộ khá nhiều, nhưng với một số bạn không có TIVA mà lại sẵn Arduino thì quả là vất vả, giờ lại phải đi mua kit TIVA mà không có nhu cầu dùng tới thì hơi tốn kém. Do đó thì mình phải thay đổi code một chút để nó phù hợp hơn với Arduino, hi vọng nó là cái để mọi người có thể tham khảo và xây dựng riêng một hệ thống nông nghiệp thông minh đơn giản cho riêng mình.
Trong bài viết này mình chỉ tập trung vào chương trình và một số thay đổi, về phần giải thích thêm bớt các bạn có thể coi ở bài viết Hệ thống nông nghiệp thông minh đơn giản nhé.
Chuẩn bị
Phần cứng
- Arduino
- ESP8266 v1 hoặc v7/v12 (mình dùng v1 có sẵn)
- Cảm biến nhiệt độ, độ ẩm DHT22
- Cảm biến ánh sáng (mình tự chế)
- Modul Relay (2 relay)
- LCD 16×2
- Nút nhấn và LED (mình tận dụng có sẵn trên kit TIVA C)
- Dây nối
- Nguồn 3.3V
- Testboard (Breadboard)
Phần mềm
- Arduino IDE
- Thư viện DHT22
DHT22
Mình thử kiểm tra DHT22 hoạt động xem sao, kết nối dây như hình dưới
Chương trình đọc nhiệt độ, độ ẩm hiển thị lên máy tính với tốc độ baud là 115200
// Chương trình đọc nhiệt độ, độ ẩm từ cảm biến DHT
// Written by ladyada, public domain
// Chỉnh sửa cho Arduino bởi hocARM.org
#include "DHT.h"
#define DHTPIN 2 // Chân DATA nối với chân 2
// Uncomment loại cảm biến bạn sử dụng, nếu DHT11 thì uncomment DHT11 và comment DHT22
//#define DHTTYPE DHT11 // DHT 11
#define DHTTYPE DHT22 // DHT 22 (AM2302), AM2321
//#define DHTTYPE DHT21 // DHT 21 (AM2301)
// Kết nối
// DHT | Arduino
//----------------
// VCC(1) | 5V
// DATA(2) | 2
// NC(3) | x
// GND(4) | GND
// Kết nối chân 1 của DHT với 3.3V
// Chân 2 kết nối với bất kỳ chân nào của TIVA C
// Chân 4 nối với GND
// Nối trở 10k giữa chân 1 và chân 2
// Khởi tạo cảm biến
DHT dht(DHTPIN, DHTTYPE);
void setup() {
// Khởi tạo cổng serial baud 115200
Serial.begin(115200);
Serial.println("DHTxx test!");
// Bắt đầu đọc dữ liệu
dht.begin();
}
void loop() {
// Đợi chuyển đổi dữ liệu khoảng 2s
delay(2000);
float h = dht.readHumidity();
// Đọc giá trị nhiệt độ C (mặc định)
float t = dht.readTemperature();
// Đọc giá trị nhiệt độ F(isFahrenheit = true)
float f = dht.readTemperature(true);
// Kiểm tra quá trình đọc thành công hay không
if (isnan(h) || isnan(t) || isnan(f)) {
Serial.println("Failed to read from DHT sensor!");
return;
}
// Tính chỉ số nhiệt độ F (mặc định)
// float hif = dht.computeHeatIndex(f, h);
// Tính chỉ số nhiệt độ C (isFahreheit = false)
// float hic = dht.computeHeatIndex(t, h, false);
// IN thông tin ra màn hình
Serial.print("Do am: ");
Serial.print(h);
Serial.print(" %\t");
Serial.print("Nhiet do: ");
Serial.print(t);
Serial.println(" *C ");
}
Cảm biến ánh sáng
Mình vẫn giữ kết nối như cũ, thêm vào cảm biến ánh sáng với kết nối như hình
Chương trình đọc đồng thời cảm biến ánh sáng và DHT22, ở đây do Arduino ADC có 10 bit nên độ phân giải chỉ là 1024(0-1023)
// Chương trình đọc nhiệt độ, độ ẩm từ cảm biến DHT
// Thêm chức năng đọc cảm biến ánh sáng
// Written by ladyada, public domain
// Chỉnh sửa cho Arduino Uno bởi hocARM.org
#include "DHT.h"
#define DHTPIN 2 // Chân DATA nối với 2
#define LDR_PIN A0 // Chân A0 nối với chân OUT cảm biến as
// Uncomment loại cảm biến bạn sử dụng, nếu DHT11 thì uncomment DHT11 và comment DHT22
//#define DHTTYPE DHT11 // DHT 11
#define DHTTYPE DHT22 // DHT 22 (AM2302), AM2321
//#define DHTTYPE DHT21 // DHT 21 (AM2301)
// Kết nối
// DHT | Arduino
//----------------
// VCC(1) | 5V
// DATA(2) | 2
// NC(3) | x
// GND(4) | GND
// Nối trở 10k giữa chân 1 và chân 2
// Khởi tạo cảm biến
DHT dht(DHTPIN, DHTTYPE);
void setup() {
// Khởi tạo cổng serial baud 115200
Serial.begin(115200);
Serial.println("DHTxx test!");
// Bắt đầu đọc dữ liệu
dht.begin();
}
void loop() {
// Đợi chuyển đổi dữ liệu khoảng 2s
delay(2000);
float h = dht.readHumidity();
// Đọc giá trị nhiệt độ C (mặc định)
float t = dht.readTemperature();
// Đọc giá trị nhiệt độ F(isFahrenheit = true)
float f = dht.readTemperature(true);
// Kiểm tra quá trình đọc thành công hay không
if (isnan(h) || isnan(t) || isnan(f)) {
Serial.println("Failed to read from DHT sensor!");
return;
}
// Tính chỉ số nhiệt độ F (mặc định)
// float hif = dht.computeHeatIndex(f, h);
// Tính chỉ số nhiệt độ C (isFahreheit = false)
// float hic = dht.computeHeatIndex(t, h, false);
// Đọc dữ liệu cảm biến ánh sáng
int lumen = getLumen(LDR_PIN);
// IN thông tin ra màn hình
Serial.print("Do am: ");
Serial.print(h);
Serial.print(" %\t");
Serial.print("Nhiet do: ");
Serial.print(t);
Serial.print(" *C\t");
Serial.print("Anh sang: ");
Serial.print(lumen);
Serial.println(" %");
}
int getLumen(int anaPin)
{
int anaValue = 0;
for (int i = 0; i < 10; i++) // Đọc giá trị cảm biến 10 lần và lấy giá trị trung bình
{
anaValue += analogRead(anaPin);
delay(50);
}
anaValue = anaValue / 10;
anaValue = map(anaValue, 1023, 0, 0, 100); //Tối:0 ==> Sáng 100%
return anaValue;
}
Cảm biến độ ẩm đất
Cảm biến độ ẩm này mình chọn hình minh họa hơi khác một chút về thứ tự so với bản thực, nhưng cách kết nối thì khá dễ, chân A0 của cảm biến nối với chân A1 của Arduino,2 chân còn lại ta cấp nguồn. Bạn có thể xem thêm trong code.
Lưu ý các kết nối chân ở trên vẫn giữ như cũ nhé.
Chương trình đọc DHT22, cảm biến ánh sáng, độ ẩm đất
// Chương trình đọc nhiệt độ, độ ẩm từ cảm biến DHT
// Thêm chức năng đọc cảm biến ánh sáng
// Thêm chức năng đọc cảm biến độ ẩm đất
// Written by ladyada, public domain
// Chỉnh sửa cho Arduino Uno bởi hocARM.org
// Kết nối
// DHT | Arduino Uno
//---------------------------
// VCC(1) | 5V
// DATA(2) | 2
// NC(3) | x
// GND(4) | GND
// Nối trở 10k giữa chân 1 và chân 2
//----------------
//Cảm biến độ ẩm | Arduino
//--------------------------
// VCC(1) | 5V
// GND(2) | GND
// D0(3) | x
// A0(4) | A1
// Cảm biến ánh sáng nối chân A0
#include "DHT.h"
#define DHTPIN 2 // Chân DATA nối với 2
#define LDR_PIN A0 // Chân A0 nối với chân OUT cảm biến as
#define SOIL_MOIST_1_PIN A1 // Chân A1 nối với cảm biến độ ẩm
// Uncomment loại cảm biến bạn sử dụng, nếu DHT11 thì uncomment DHT11 và comment DHT22
//#define DHTTYPE DHT11 // DHT 11
#define DHTTYPE DHT22 // DHT 22 (AM2302), AM2321
//#define DHTTYPE DHT21 // DHT 21 (AM2301)
int humDHT;
int tempDHT;
int lumen;
int soilMoist;
// Khởi tạo cảm biến
DHT dht(DHTPIN, DHTTYPE);
void setup() {
// Khởi tạo cổng serial baud 115200
Serial.begin(115200);
Serial.println("DHTxx test!");
// Bắt đầu đọc dữ liệu
dht.begin();
}
void loop() {
readSensors();
// IN thông tin ra màn hình
Serial.print("Do am: ");
Serial.print(humDHT);
Serial.print(" %\t");
Serial.print("Nhiet do: ");
Serial.print(tempDHT);
Serial.print(" *C\t");
Serial.print("Anh sang: ");
Serial.print(lumen);
Serial.print(" %\t");
Serial.print("Do am dat: ");
Serial.print(soilMoist);
Serial.println(" %");
}
int getLumen(int anaPin)
{
int anaValue = 0;
for (int i = 0; i < 10; i++) // Đọc giá trị cảm biến 10 lần và lấy giá trị trung bình
{
anaValue += analogRead(anaPin);
delay(50);
}
anaValue = anaValue / 10;
anaValue = map(anaValue, 1023, 0, 0, 100); //Tối:0 ==> Sáng 100%
return anaValue;
}
int getSoilMoist()
{
int i = 0;
int anaValue = 0;
for (i = 0; i < 10; i++) //
{
anaValue += analogRead(SOIL_MOIST_1_PIN); //Đọc giá trị cảm biến độ ẩm đất
delay(50); // Đợi đọc giá trị ADC
}
anaValue = anaValue / (i);
anaValue = map(anaValue, 1023, 0, 0, 100); //Ít nước:0% ==> Nhiều nước 100%
return anaValue;
}
void readSensors(void)
{
tempDHT = dht.readTemperature(); //Đọc nhiệt độ DHT22
humDHT = dht.readHumidity(); //Đọc độ ẩm DHT22
lumen = getLumen(LDR_PIN); //Đọc ánh sáng
soilMoist = getSoilMoist(); //Đọc cảm biến độ ẩm đất
}
LCD 16×2
Đây là điểm khác biệt so với phiên bản TIVA, mình thay LCD này vì có 2 lý do
- Phổ biến: dễ mua và dễ tìm.
- Thư viện LCD Gphone sau khi build đã ăn mất 50% RAM, thêm linh tinh các chương trình khác nữa thì hết sạch RAM và chương trình không chạy được.
Tuy nhiên dùng LCD16x2 có nhược điểm là chỉ có 2 dòng hiển thị, trong khi đó thông tin mình cần hiển thị cần ít nhất 6 dòng (nhiệt độ, độ ẩm, ánh sáng, trạng thái 2 bơm), không sao cả mình khắc phục bằng cách hiển thị 2 dòng nhiệt độ, độ ẩm trước, sau đó là ánh sáng và độ ẩm đất
Đầu tiên là kết nối
Mình thử nạp chương trình và in dòng chữ hocarm.org xuống LCD
// Thêm thư viện có sẵn LCD
#include <LiquidCrystal.h>
//Khởi tạo với các chân
LiquidCrystal lcd(9, 8, 7, 6, 5, 4);
void setup() {
//Khai báo LCD 16x2
lcd.begin(16, 2);
//In ra màn hình HocARM.org
lcd.print("HocARM.org!");
}
void loop() {
// đặt con trỏ vào cột 0, dòng 1
lcd.setCursor(0, 1);
// In ra dong chu Hello
lcd.print(" Hello");
}
Lưu ý nhỏ là nếu LCD không hiển thị thì bạn xoay từ từ biến trở sẽ hiện màn hình
Chương trình hiển thị các thông tin của cảm biến lên LCD
// Chương trình đọc nhiệt độ, độ ẩm từ cảm biến DHT
// Thêm chức năng đọc cảm biến ánh sáng
// Thêm chức năng đọc cảm biến độ ẩm đất
// Chỉnh sửa cho Arduino Uno bởi hocARM.org
// Kết nối
// DHT | Arduino Uno
//---------------------------
// VCC(1) | 5V
// DATA(2) | 2
// NC(3) | x
// GND(4) | GND
// Nối trở 10k giữa chân 1 và chân 2
//----------------
//Cảm biến độ ẩm | Arduino
//--------------------------
// VCC(1) | 5V
// GND(2) | GND
// D0(3) | x
// A0(4) | A1
// Cảm biến ánh sáng nối chân A0
#include "DHT.h"
//#include "homephone.h"
#include <LiquidCrystal.h>
#define DHTPIN 2 // Chân DATA nối với PD0
#define LDR_PIN A0 // Chân PE3 nối với chân OUT cảm biến as
#define SOIL_MOIST_1_PIN A1 // Chân PE4 nối với cảm biến độ ẩm
// Uncomment loại cảm biến bạn sử dụng, nếu DHT11 thì uncomment DHT11 và comment DHT22
//#define DHTTYPE DHT11 // DHT 11
#define DHTTYPE DHT22 // DHT 22 (AM2302), AM2321
//#define DHTTYPE DHT21 // DHT 21 (AM2301)
int humDHT;
int tempDHT;
int lumen;
int soilMoist;
// Khởi tạo cảm biến
DHT dht(DHTPIN, DHTTYPE);
// Khởi tạo LCD
LiquidCrystal lcd(9, 8, 7, 6, 5, 4);
void setup() {
// Khởi tạo cổng serial baud 115200
Serial.begin(115200);
Serial.println("HocARM NDTR Bot!");
// Bắt đầu đọc dữ liệu
dht.begin();
//Thông báo đây là LCD 1602
lcd.begin(16, 2);
//In ra màn hình lcd dòng chữ
lcd.print("HocARM NDTR Bot!*");
lcd.setCursor(0, 1);
lcd.print("Xin chao ong chu");
}
void loop() {
delay(2000);
readSensors();
printData();
showDataLCD();
}
int getLumen(int anaPin)
{
int anaValue = 0;
for (int i = 0; i < 10; i++) // Đọc giá trị cảm biến 10 lần và lấy giá trị trung bình
{
anaValue += analogRead(anaPin);
delay(50);
}
anaValue = anaValue / 10;
anaValue = map(anaValue, 1023, 0, 0, 100); //Tối:0 ==> Sáng 100%
return anaValue;
}
int getSoilMoist()
{
int i = 0;
int anaValue = 0;
for (i = 0; i < 10; i++) //
{
anaValue += analogRead(SOIL_MOIST_1_PIN); //Đọc giá trị cảm biến độ ẩm đất
delay(50); // Đợi đọc giá trị ADC
}
anaValue = anaValue / (i);
anaValue = map(anaValue, 1023, 0, 0, 100); //Ít nước:0% ==> Nhiều nước 100%
return anaValue;
}
void readSensors(void)
{
tempDHT = dht.readTemperature(); //Đọc nhiệt độ DHT22
humDHT = dht.readHumidity(); //Đọc độ ẩm DHT22
lumen = getLumen(LDR_PIN); //Đọc ánh sáng
soilMoist = getSoilMoist(); //Đọc cảm biến độ ẩm đất
}
void showDataLCD(void)
{
lcd.clear(); //Xóa màn hình
lcd.setCursor(0, 1);
lcd.print(" DO.AM% = ");
lcd.print(humDHT);
lcd.println(" % " );
lcd.setCursor(1, 0);
lcd.print(" NH.DO = ");
lcd.print(tempDHT);
lcd.println(" *C ");
delay(5000);
lcd.clear();
lcd.setCursor(0, 1);
lcd.print(" AM.DAT% = ");
lcd.print(soilMoist);
lcd.println(" % " );
lcd.setCursor(1, 0);
lcd.print("A.SANG% = ");
lcd.print(lumen);
lcd.println(" % ");
}
void printData(void)
{
// IN thông tin ra màn hình
Serial.print("Do am: ");
Serial.print(humDHT);
Serial.print(" %\t");
Serial.print("Nhiet do: ");
Serial.print(tempDHT);
Serial.print(" *C\t");
Serial.print("Anh sang: ");
Serial.print(lumen);
Serial.print(" %\t");
Serial.print("Do am dat: ");
Serial.print(soilMoist);
Serial.println(" %");
}
Relay và nút nhấn
Do Arduino không có 2 nút nhấn có sẵn như TIVA nên mình phải thêm 2 nút nhấn gắn ngoài vào, thêm một lưu ý nữa là nếu relay dùng nguồn ngoài thì cần nối chung GND với Arduino, kết nối vẫn giữ nguyên kết nối bên trên và thêm vào relay và nút nhấn nhé.
Giờ thì thêm chương trình đọc nút nhấn và điều khiển relay cho chế độ thủ công
/* Chương trình đọc nhiệt độ, độ ẩm từ cảm biến DHT
Thêm chức năng đọc cảm biến ánh sáng
Thêm chức năng đọc cảm biến độ ẩm đất
Thêm hiển thị LCD
Thêm chức năng điều khiển tưới tiêu bằng tay 2 bơm
HocARM NDTR BOT for Arduino by hocARM.org
-------------------------------------------------
// Chương trình đọc nhiệt độ, độ ẩm từ cảm biến DHT
// Thêm chức năng đọc cảm biến ánh sáng
// Thêm chức năng đọc cảm biến độ ẩm đất
// Chỉnh sửa cho Arduino Uno bởi hocARM.org
// Kết nối
// DHT | Arduino Uno
//---------------------------
// VCC(1) | 5V
// DATA(2) | 2
// NC(3) | x
// GND(4) | GND
// Nối trở 10k giữa chân 1 và chân 2
//----------------
//Cảm biến độ ẩm | Arduino
//--------------------------
// VCC(1) | 5V
// GND(2) | GND
// D0(3) | x
// A0(4) | A1
// Cảm biến ánh sáng nối chân A0
//-------------------------------
// 2 nút nhấn sẽ được kết nối với chân A2 và A3
// 2 relay nối với chân 3 và chân 12
*/
#include "DHT.h"
//#include "homephone.h"
#include <LiquidCrystal.h>
#define DHTPIN 2 // Chân DATA nối với PD0
#define LDR_PIN A0 // Chân PE3 nối với chân OUT cảm biến as
#define SOIL_MOIST_1_PIN A1 // Chân PE4 nối với cảm biến độ ẩm
// Relay, nút nhấn
#define PUMPW_ON A2 //Nút có sẵn trên kit
#define PUMPW_PIN 3
#define PUMPS_ON A3 //Nút có sẵn trên kit
#define PUMPS_PIN 12
// Uncomment loại cảm biến bạn sử dụng, nếu DHT11 thì uncomment DHT11 và comment DHT22
//#define DHTTYPE DHT11 // DHT 11
#define DHTTYPE DHT22 // DHT 22 (AM2302), AM2321
//#define DHTTYPE DHT21 // DHT 21 (AM2301)
// Biến lưu các giá trị cảm biến
int humDHT;
int tempDHT;
int lumen;
int soilMoist;
// Biến lưu trạng thái bơm
boolean pumpWaterStatus = 0;
boolean pumpPesStatus = 0;
int timePumpOn = 10; // Thời gian bật bơm nước
// Biến cho timer
long sampleTimingSeconds = 50; // ==> Thời gian đọc cảm biến (s)
long startTiming = 0;
long elapsedTime = 0;
// Khởi tạo cảm biến
DHT dht(DHTPIN, DHTTYPE);
// Khởi tạo LCD
//homephone lcd (PD_1, PD_2, PD_3, PE_1, PE_2);
LiquidCrystal lcd(9, 8, 7, 6, 5, 4);
void setup() {
pinMode(PUMPW_PIN, OUTPUT);
pinMode(PUMPS_PIN, OUTPUT);
pinMode(PUMPW_ON, INPUT_PULLUP); // Button
pinMode(PUMPS_ON, INPUT_PULLUP); // Button
aplyCmd();
// Khởi tạo cổng serial baud 115200
Serial.begin(115200);
Serial.println("HocARM NDTR Bot!");
// Bắt đầu đọc dữ liệu
dht.begin();
lcd.begin(16, 2);
lcd.print("HocARM NDTR Bot!*");
lcd.setCursor(0, 1);
lcd.print("Xin chao ong chu");
readSensors(); // Khởi tạo đọc cảm biến
startTiming = millis(); // Bắt đầu đếm thời gian
lcd.clear();
lcd.setCursor(0, 1);
lcd.print("Vui long cho ...");
}
void loop() {
// Khởi tạo timer
elapsedTime = millis() - startTiming;
readLocalCmd();
if (elapsedTime > (sampleTimingSeconds * 1000))
{
readSensors();
printData();
showDataLCD();
startTiming = millis();
}
}
int getLumen(int anaPin)
{
int anaValue = 0;
for (int i = 0; i < 10; i++) // Đọc giá trị cảm biến 10 lần và lấy giá trị trung bình
{
anaValue += analogRead(anaPin);
delay(50);
}
anaValue = anaValue / 10;
anaValue = map(anaValue, 1023, 0, 0, 100); //Tối:0 ==> Sáng 100%
return anaValue;
}
int getSoilMoist()
{
int i = 0;
int anaValue = 0;
for (i = 0; i < 10; i++) //
{
anaValue += analogRead(SOIL_MOIST_1_PIN); //Đọc giá trị cảm biến độ ẩm đất
delay(50); // Đợi đọc giá trị ADC
}
anaValue = anaValue / (i);
anaValue = map(anaValue, 1023, 0, 0, 100); //Ít nước:0% ==> Nhiều nước 100%
return anaValue;
}
void readSensors(void)
{
tempDHT = dht.readTemperature(); //Đọc nhiệt độ DHT22
humDHT = dht.readHumidity(); //Đọc độ ẩm DHT22
lumen = getLumen(LDR_PIN); //Đọc ánh sáng
soilMoist = getSoilMoist(); //Đọc cảm biến độ ẩm đất
}
void showDataLCD(void)
{
lcd.setCursor(0, 1);
lcd.print(" DO.AM% = ");
lcd.print(humDHT);
lcd.println(" % " );
lcd.setCursor(1, 0);
lcd.print(" NH.DO = ");
lcd.print(tempDHT);
lcd.println(" *C ");
delay(1000);
lcd.clear();
lcd.setCursor(0, 1);
lcd.print(" AM.DAT% = ");
lcd.print(soilMoist);
lcd.println(" % " );
lcd.setCursor(1, 0);
lcd.print("A.SANG% = ");
lcd.print(lumen);
lcd.println(" % ");
delay(1000);
lcd.clear();
lcd.setCursor(0, 1);
lcd.print(" BOM.NC = ");
lcd.print(pumpWaterStatus);
lcd.println(" " );
lcd.setCursor(1, 0);
lcd.print("BOM.SAU = ");
lcd.print(pumpPesStatus);
lcd.println(" ");
delay(1000);
}
void printData(void)
{
// IN thông tin ra màn hình
Serial.print("Do am: ");
Serial.print(humDHT);
Serial.print(" %\t");
Serial.print("Nhiet do: ");
Serial.print(tempDHT);
Serial.print(" *C\t");
Serial.print("Anh sang: ");
Serial.print(lumen);
Serial.print(" %\t");
Serial.print("Do am dat: ");
Serial.print(soilMoist);
Serial.println(" %");
}
/****************************************************************
Hàm đọc trạng thái bơm và kiểm tra nút nhấn
(Nút nhấn mặc định là mức "CAO"):
****************************************************************/
void readLocalCmd()
{
int digiValue = debounce(PUMPW_ON);
// Serial.println(digiValue);
if (!digiValue)
{
// Serial.println(" %");
pumpWaterStatus = !pumpWaterStatus;
showDataLCD();
aplyCmd();
}
digiValue = debounce(PUMPS_ON);
if (!digiValue)
{
pumpPesStatus = !pumpPesStatus;
showDataLCD();
aplyCmd();
}
}
/***************************************************
Thực hiện điều khiển các bơm
****************************************************/
void aplyCmd()
{
if (pumpWaterStatus == 1) digitalWrite(PUMPW_PIN, LOW);
if (pumpWaterStatus == 0) digitalWrite(PUMPW_PIN, HIGH);
if (pumpPesStatus == 1) digitalWrite(PUMPS_PIN, LOW);
if (pumpPesStatus == 0) digitalWrite(PUMPS_PIN, HIGH);
}
/***************************************************
Hàm kiểm tra trạng thái phím bấm
****************************************************/
boolean debounce(int pin)
{
boolean state;
boolean previousState;
const int debounceDelay = 60;
previousState = digitalRead(pin);
for (int counter = 0; counter < debounceDelay; counter++)
{
delay(1);
state = digitalRead(pin);
if (state != previousState)
{
counter = 0;
previousState = state;
}
}
return state;
}
Chế độ tự động
Giờ thì coi như phần cứng và các kết nối đã tạm ổn, lập trình thêm một chút để có thể tự điều khiển bơm khi độ ẩm quá thấp
/* Chương trình đọc nhiệt độ, độ ẩm từ cảm biến DHT
Thêm chức năng đọc cảm biến ánh sáng
Thêm chức năng đọc cảm biến độ ẩm đất
Thêm hiển thị LCD
Thêm chức năng điều khiển tưới tiêu bằng tay 2 bơm
Thêm chức năng điều khiển tự động bơm khi thiếu nước
HocARM NDTR BOT for Arduino by hocARM.org
-------------------------------------------------
// Chương trình đọc nhiệt độ, độ ẩm từ cảm biến DHT
// Thêm chức năng đọc cảm biến ánh sáng
// Thêm chức năng đọc cảm biến độ ẩm đất
// Chỉnh sửa cho Arduino Uno bởi hocARM.org
// Kết nối
// DHT | Arduino Uno
//---------------------------
// VCC(1) | 5V
// DATA(2) | 2
// NC(3) | x
// GND(4) | GND
// Nối trở 10k giữa chân 1 và chân 2
//----------------
//Cảm biến độ ẩm | Arduino
//--------------------------
// VCC(1) | 5V
// GND(2) | GND
// D0(3) | x
// A0(4) | A1
// Cảm biến ánh sáng nối chân A0
//-------------------------------
// 2 nút nhấn sẽ được kết nối với chân A2 và A3
// 2 relay nối với chân 3 và chân 12
*/
#include "DHT.h"
//#include "homephone.h"
#include <LiquidCrystal.h>
#define DHTPIN 2 // Chân DATA nối với PD0
#define LDR_PIN A0 // Chân PE3 nối với chân OUT cảm biến as
#define SOIL_MOIST_1_PIN A1 // Chân PE4 nối với cảm biến độ ẩm
// Relay, nút nhấn
#define PUMPW_ON A2 //Nút có sẵn trên kit
#define PUMPW_PIN 3
#define PUMPS_ON A3 //Nút có sẵn trên kit
#define PUMPS_PIN 12
// Uncomment loại cảm biến bạn sử dụng, nếu DHT11 thì uncomment DHT11 và comment DHT22
//#define DHTTYPE DHT11 // DHT 11
#define DHTTYPE DHT22 // DHT 22 (AM2302), AM2321
//#define DHTTYPE DHT21 // DHT 21 (AM2301)
// Biến lưu các giá trị cảm biến
int humDHT;
int tempDHT;
int lumen;
int DARK_LIGHT = 40;
int soilMoist;
int DRY_SOIL = 40;
int WET_SOIL = 60;
// Biến lưu trạng thái bơm
boolean pumpWaterStatus = 0;
boolean pumpPesStatus = 0;
int timePumpOn = 10; // Thời gian bật bơm nước
// Biến cho timer
long sampleTimingSeconds = 50; // ==> Thời gian đọc cảm biến (s)
long startTiming = 0;
long elapsedTime = 0;
// Khởi tạo cảm biến
DHT dht(DHTPIN, DHTTYPE);
// Khởi tạo LCD
LiquidCrystal lcd(9, 8, 7, 6, 5, 4);
void setup() {
pinMode(PUMPW_PIN, OUTPUT);
pinMode(PUMPS_PIN, OUTPUT);
pinMode(PUMPW_ON, INPUT_PULLUP); // Button
pinMode(PUMPS_ON, INPUT_PULLUP); // Button
aplyCmd();
// Khởi tạo cổng serial baud 115200
Serial.begin(115200);
Serial.println("HocARM NDTR Bot!");
lcd.begin(16, 2);
lcd.print("HocARM NDTR Bot!*");
lcd.setCursor(0, 1);
lcd.print("Xin chao ong chu");
// Bắt đầu đọc dữ liệu
readSensors(); // Khởi tạo đọc cảm biến
startTiming = millis(); // Bắt đầu đếm thời gian
lcd.clear();
lcd.setCursor(0, 1);
lcd.print("Vui long cho ...");
}
void loop() {
// Khởi tạo timer
elapsedTime = millis() - startTiming;
readLocalCmd();
if (elapsedTime > (sampleTimingSeconds * 1000))
{
readSensors();
printData();
showDataLCD();
autoControlPlantation();
startTiming = millis();
}
}
int getLumen(int anaPin)
{
int anaValue = 0;
for (int i = 0; i < 10; i++) // Đọc giá trị cảm biến 10 lần và lấy giá trị trung bình
{
anaValue += analogRead(anaPin);
delay(50);
}
anaValue = anaValue / 10;
anaValue = map(anaValue, 1023, 0, 0, 100); //Tối:0 ==> Sáng 100%
return anaValue;
}
int getSoilMoist()
{
int i = 0;
int anaValue = 0;
for (i = 0; i < 10; i++) //
{
anaValue += analogRead(SOIL_MOIST_1_PIN); //Đọc giá trị cảm biến độ ẩm đất
delay(50); // Đợi đọc giá trị ADC
}
anaValue = anaValue / (i);
anaValue = map(anaValue, 1023, 0, 0, 100); //Ít nước:0% ==> Nhiều nước 100%
return anaValue;
}
void readSensors(void)
{
tempDHT = dht.readTemperature(); //Đọc nhiệt độ DHT22
humDHT = dht.readHumidity(); //Đọc độ ẩm DHT22
lumen = getLumen(LDR_PIN); //Đọc ánh sáng
soilMoist = getSoilMoist(); //Đọc cảm biến độ ẩm đất
}
void showDataLCD(void)
{
lcd.setCursor(0, 1);
lcd.print(" DO.AM% = ");
lcd.print(humDHT);
lcd.println(" % " );
lcd.setCursor(1, 0);
lcd.print(" NH.DO = ");
lcd.print(tempDHT);
lcd.println(" *C ");
delay(1000);
lcd.clear();
lcd.setCursor(0, 1);
lcd.print(" AM.DAT% = ");
lcd.print(soilMoist);
lcd.println(" % " );
lcd.setCursor(1, 0);
lcd.print("A.SANG% = ");
lcd.print(lumen);
lcd.println(" % ");
delay(1000);
lcd.clear();
lcd.setCursor(0, 1);
lcd.print(" BOM.NC = ");
lcd.print(pumpWaterStatus);
lcd.println(" " );
lcd.setCursor(1, 0);
lcd.print("BOM.SAU = ");
lcd.print(pumpPesStatus);
lcd.println(" ");
delay(1000);
}
void printData(void)
{
// IN thông tin ra màn hình
Serial.print("Do am: ");
Serial.print(humDHT);
Serial.print(" %\t");
Serial.print("Nhiet do: ");
Serial.print(tempDHT);
Serial.print(" *C\t");
Serial.print("Anh sang: ");
Serial.print(lumen);
Serial.print(" %\t");
Serial.print("Do am dat: ");
Serial.print(soilMoist);
Serial.println(" %");
}
/***************************************************
Hàm bật bơm nước
****************************************************/
void turnPumpOn()
{
digitalWrite(PUMPW_PIN, LOW);
pumpWaterStatus = 1;
showDataLCD();
delay (timePumpOn * 1000);
digitalWrite(PUMPW_PIN, HIGH);
pumpWaterStatus = 0;
showDataLCD();
}
/****************************************************************
Hàm đọc trạng thái bơm và kiểm tra nút nhấn
(Nút nhấn mặc định là mức "CAO"):
****************************************************************/
void readLocalCmd()
{
int digiValue = debounce(PUMPW_ON);
if (!digiValue)
{
pumpWaterStatus = !pumpWaterStatus;
showDataLCD();
aplyCmd();
}
digiValue = debounce(PUMPS_ON);
if (!digiValue)
{
pumpPesStatus = !pumpPesStatus;
showDataLCD();
aplyCmd();
}
}
/***************************************************
Thực hiện điều khiển các bơm
****************************************************/
void aplyCmd()
{
if (pumpWaterStatus == 1) digitalWrite(PUMPW_PIN, LOW);
if (pumpWaterStatus == 0) digitalWrite(PUMPW_PIN, HIGH);
if (pumpPesStatus == 1) digitalWrite(PUMPS_PIN, LOW);
if (pumpPesStatus == 0) digitalWrite(PUMPS_PIN, HIGH);
}
/***************************************************
Hàm kiểm tra trạng thái phím bấm
****************************************************/
boolean debounce(int pin)
{
boolean state;
boolean previousState;
const int debounceDelay = 60;
previousState = digitalRead(pin);
for (int counter = 0; counter < debounceDelay; counter++)
{
delay(1);
state = digitalRead(pin);
if (state != previousState)
{
counter = 0;
previousState = state;
}
}
return state;
}
/***************************************************
Chức năng tự động tưới tiêu
****************************************************/
void autoControlPlantation()
{
//--------------------------------- BƠM NƯỚC ------//
if (soilMoist < DRY_SOIL && lumen > DARK_LIGHT)
{
turnPumpOn();
}
}
Gửi dữ liệu lên thingspeak
Để gửi dữ liệu lên thingspeak thì cần có kết nối wifi và có modul ESP8266, mình sẽ minh họa kết nối với ESP8266 v1
Lưu ý quan trọng:
Nguồn cho ESP8266 phải là 3.3V, và nên lấy từ nguồn ngoài, lấy nguồn 3v3 từ Arduino nhiều lúc sẽ không hoạt động được do thiếu dòng.
Nếu bạn có mua mới ESP8266 thì nên mua bản V7 hoặc V12 để có nhiều IO và làm được nhiều việc khác hơn. Như học ESP8266 không kết hợp với Uno hay TIVA gì cả
Do Arduino không có Serial1 nên mình phải sử dụng thư viện có sẵn để cấu hình chân giao tiếp với ESP8266 là SoftwareSerial.h
trên 2 chân 10,11
SoftwareSerial Serial1(10, 11); // RX, TX
Chương trình tổng hợp các chức năng đọc dữ liệu, hiển thị LCD, điều khiển thủ công, tự động và gửi thông tin lên cloud
/* Chương trình đọc nhiệt độ, độ ẩm từ cảm biến DHT
Thêm chức năng đọc cảm biến ánh sáng
Thêm chức năng đọc cảm biến độ ẩm đất
Thêm hiển thị LCD
Thêm chức năng điều khiển tưới tiêu bằng tay 2 bơm
Thêm chức năng điều khiển tự động bơm khi thiếu nước
Thêm chức năng cập nhật dữ liệu qua cloud thingspeak
HocARM NDTR BOT by hocARM.org
-------------------------------------------------
// Kết nối
// DHT | Arduino Uno
//---------------------------
// VCC(1) | 5V
// DATA(2) | 2
// NC(3) | x
// GND(4) | GND
// Nối trở 10k giữa chân 1 và chân 2
//----------------
//Cảm biến độ ẩm | Arduino
//--------------------------
// VCC(1) | 5V
// GND(2) | GND
// D0(3) | x
// A0(4) | A1
// Cảm biến ánh sáng nối chân A0
//-------------------------------
// 2 nút nhấn sẽ được kết nối với chân A2 và A3
// 2 relay nối với chân 3 và chân 12
*/
#include "DHT.h"
#include <LiquidCrystal.h>
#include <SoftwareSerial.h>
SoftwareSerial Serial1(10, 11); // RX, TX
#define DHTPIN 2 // Chân DATA nối với PD0
#define LDR_PIN A0 // Chân PE3 nối với chân OUT cảm biến as
#define SOIL_MOIST_1_PIN A1 // Chân PE4 nối với cảm biến độ ẩm
// Relay, nút nhấn
#define PUMPW_ON A2 //Nút có sẵn trên kit
#define PUMPW_PIN 3
#define PUMPS_ON A3 //Nút có sẵn trên kit
#define PUMPS_PIN 12
#define IP "184.106.153.149"// thingspeak.com ip
//#define GREEN_LED 13
// Uncomment loại cảm biến bạn sử dụng, nếu DHT11 thì uncomment DHT11 và comment DHT22
//#define DHTTYPE DHT11 // DHT 11
#define DHTTYPE DHT22 // DHT 22 (AM2302), AM2321
//#define DHTTYPE DHT21 // DHT 21 (AM2301)
// Biến lưu các giá trị cảm biến
int humDHT;
int tempDHT;
int lumen;
int DARK_LIGHT = 40;
int soilMoist;
int DRY_SOIL = 40;
int WET_SOIL = 60;
// Biến lưu trạng thái bơm
boolean pumpWaterStatus = 0;
boolean pumpPesStatus = 0;
int timePumpOn = 30; // Thời gian bật bơm nước
// Biến cho timer
long sampleTimingSeconds = 60; // ==> Thời gian đọc cảm biến (s)
long startTiming = 0;
long elapsedTime = 0;
char msg[] = "GET /update?key=xxxxxxxxxxx"; // Thay xxxxxxxxxxx bằng API của bạn
char cmd[100];
char aux_str[100];
int legth;
// Khởi tạo cảm biến
DHT dht(DHTPIN, DHTTYPE);
// Khởi tạo LCD
LiquidCrystal lcd(9, 8, 7, 6, 5, 4);
void setup() {
pinMode(PUMPW_PIN, OUTPUT);
pinMode(PUMPS_PIN, OUTPUT);
pinMode(PUMPW_ON, INPUT_PULLUP); // Nut pullup
pinMode(PUMPS_ON, INPUT_PULLUP); // Nut pullup
aplyCmd();
// Khởi tạo cổng serial baud 115200
Serial.begin(115200);
Serial1.begin(9600);
Serial.println("HocARM NDTR Bot!");
// Bắt đầu đọc dữ liệu
dht.begin();
lcd.begin(16, 2);
lcd.print("HocARM NDTR Bot!*");
lcd.setCursor(0, 1);
lcd.print("Xin chao ong chu");
connectWiFi();
readSensors(); // Khởi tạo đọc cảm biến
startTiming = millis(); // Bắt đầu đếm thời gian
lcd.clear();
lcd.setCursor(1, 0);
lcd.print("Da ket noi wifi");
lcd.setCursor(0, 1);
lcd.print("Vui long cho ...");
}
void loop() {
// Khởi tạo timer
elapsedTime = millis() - startTiming;
readLocalCmd();
if (elapsedTime > (sampleTimingSeconds * 1000))
{
readSensors();
printData();
showDataLCD();
autoControlPlantation();
updateDataThingSpeak();
startTiming = millis();
}
}
int getLumen(int anaPin)
{
int anaValue = 0;
for (int i = 0; i < 10; i++) // Đọc giá trị cảm biến 10 lần và lấy giá trị trung bình
{
anaValue += analogRead(anaPin);
delay(50);
}
anaValue = anaValue / 10;
anaValue = map(anaValue, 1023, 0, 0, 100); //Tối:0 ==> Sáng 100%
return anaValue;
}
int getSoilMoist()
{
int i = 0;
int anaValue = 0;
for (i = 0; i < 10; i++) //
{
anaValue += analogRead(SOIL_MOIST_1_PIN); //Đọc giá trị cảm biến độ ẩm đất
delay(50); // Đợi đọc giá trị ADC
}
anaValue = anaValue / (i);
anaValue = map(anaValue, 1023, 0, 0, 100); //Ít nước:0% ==> Nhiều nước 100%
return anaValue;
}
void readSensors(void)
{
tempDHT = dht.readTemperature(); //Đọc nhiệt độ DHT22
humDHT = dht.readHumidity(); //Đọc độ ẩm DHT22
lumen = getLumen(LDR_PIN); //Đọc ánh sáng
soilMoist = getSoilMoist(); //Đọc cảm biến độ ẩm đất
}
void showDataLCD(void)
{
// lcd.clear(); //Xóa màn hình
lcd.setCursor(0, 1);
lcd.print(" DO.AM% = ");
lcd.print(humDHT);
lcd.println(" % " );
lcd.setCursor(1, 0);
lcd.print(" NH.DO = ");
lcd.print(tempDHT);
lcd.println(" *C ");
delay(1000);
lcd.clear();
lcd.setCursor(0, 1);
lcd.print(" AM.DAT% = ");
lcd.print(soilMoist);
lcd.println(" % " );
lcd.setCursor(1, 0);
lcd.print("A.SANG% = ");
lcd.print(lumen);
lcd.println(" % ");
delay(1000);
lcd.clear();
lcd.setCursor(0, 1);
lcd.print(" BOM.NUOC = ");
lcd.print(pumpWaterStatus);
lcd.println(" " );
lcd.setCursor(1, 0);
lcd.print("BOM.TRU.SAU = ");
lcd.print(pumpPesStatus);
lcd.println(" ");
delay(1000);
}
void printData(void)
{
// IN thông tin ra màn hình
Serial.print("Do am: ");
Serial.print(humDHT);
Serial.print(" %\t");
Serial.print("Nhiet do: ");
Serial.print(tempDHT);
Serial.print(" *C\t");
Serial.print("Anh sang: ");
Serial.print(lumen);
Serial.print(" %\t");
Serial.print("Do am dat: ");
Serial.print(soilMoist);
Serial.println(" %");
}
void showPumpLCD(void)
{
lcd.clear();
lcd.setCursor(0, 1);
//lcd.setCusor(0, 1);
lcd.print(" BOM.NUOC = ");
lcd.print(pumpWaterStatus);
lcd.println(" " );
lcd.setCursor(1, 0);
//lcd.setCusor (1, 0);
lcd.print("BOM.TRU.SAU = ");
lcd.print(pumpPesStatus);
lcd.println(" ");
// delay(1000);
}
/***************************************************
Hàm bật bơm nước
****************************************************/
void turnPumpOn()
{
digitalWrite(PUMPW_PIN, LOW);
pumpWaterStatus = 1;
showPumpLCD();
updateCmdThingSpeak();
delay (timePumpOn * 1000);
digitalWrite(PUMPW_PIN, HIGH);
pumpWaterStatus = 0;
showPumpLCD();
updateCmdThingSpeak();
}
/****************************************************************
Hàm đọc trạng thái bơm và kiểm tra nút nhấn
(Nút nhấn mặc định là mức "CAO"):
****************************************************************/
void readLocalCmd()
{
int digiValue = debounce(PUMPW_ON);
if (!digiValue)
{
pumpWaterStatus = !pumpWaterStatus;
showPumpLCD();
aplyCmd();
updateCmdThingSpeak();
}
digiValue = debounce(PUMPS_ON);
if (!digiValue)
{
pumpPesStatus = !pumpPesStatus;
showPumpLCD();
aplyCmd();
updateCmdThingSpeak();
}
}
/***************************************************
Thực hiện điều khiển các bơm
****************************************************/
void aplyCmd()
{
if (pumpWaterStatus == 1) digitalWrite(PUMPW_PIN, LOW);
if (pumpWaterStatus == 0) digitalWrite(PUMPW_PIN, HIGH);
if (pumpPesStatus == 1) digitalWrite(PUMPS_PIN, LOW);
if (pumpPesStatus == 0) digitalWrite(PUMPS_PIN, HIGH);
}
/***************************************************
Hàm kiểm tra trạng thái phím bấm
****************************************************/
boolean debounce(int pin)
{
boolean state;
boolean previousState;
const int debounceDelay = 60;
previousState = digitalRead(pin);
for (int counter = 0; counter < debounceDelay; counter++)
{
delay(1);
state = digitalRead(pin);
if (state != previousState)
{
counter = 0;
previousState = state;
}
}
return state;
}
/***************************************************
Chức năng tự động tưới tiêu
****************************************************/
void autoControlPlantation()
{
//--------------------------------- BƠM NƯỚC ------//
if (soilMoist < DRY_SOIL && lumen > DARK_LIGHT)
{
turnPumpOn();
}
}
// Hàm gửi lệnh AT
int8_t sendATcommand(char* ATcommand, char* expected_answer, unsigned int timeout) {
uint8_t x = 0, answer = 0;
char response[100];
unsigned long previous;
memset(response, '\0', 100); // xóa buffer
delay(100);
while ( Serial1.available() > 0) Serial1.read(); // đọc input
Serial1.println(ATcommand); // Gửi lệnh AT
x = 0;
previous = millis();
// Chờ phản hồi
do {
if (Serial1.available() != 0) {
// Nếu có dữ liệu trong buffer UART, đọc và kiểm tra nó với expected_answer
response[x] = Serial1.read();
x++;
// Nếu đúng thì trả kết quả answer = 1, thoát hàm
if (strstr(response, expected_answer) != NULL)
{
answer = 1;
}
}
} while ((answer == 0) && ((millis() - previous) < timeout)); // Nếu sai thì tiếp tục thử lại cho tới hết thời gian timeout
Serial.println(response); // In giá trị nhận được để debug
return answer;
}
// Hàm gửi lệnh AT 2 để gửi dữ liệu
int8_t sendATcommand2(char* ATcommand, char* expected_answer1,
char* expected_answer2, unsigned int timeout) {
uint8_t x = 0, answer = 0;
char response[100];
unsigned long previous;
memset(response, '\0', 100); // Khởi tạo lại chuỗi về 0
delay(100);
while ( Serial1.available() > 0) Serial1.read(); // Xóa buffer
Serial1.println(ATcommand); // Gửi lệnh AT
x = 0;
previous = millis();
// Chờ phản hồi
do {
// Nếu có dữ liệu từ UART thì đọc và kiểm tra
if (Serial1.available() != 0) {
response[x] = Serial1.read();
x++;
// Trả về giá trị 1 nếu nhận được expected_answer1
if (strstr(response, expected_answer1) != NULL)
{
answer = 1;
}
// Trả về giá trị 2 nếu nhận được expected_answer2
else if (strstr(response, expected_answer2) != NULL)
{
answer = 2;
}
}
}
// Đợi time out
while ((answer == 0) && ((millis() - previous) < timeout));
Serial.println(response); // In giá trị nhận được để debug
return answer;
}
/***************************************************
Kết nối wifi
****************************************************/
void connectWiFi(void)
{
sendATcommand("AT", "OK", 5000); //Kiểm tra kết nối
sendATcommand("AT+CWMODE=1", "OK", 5000); //Cấu hình chế độ station
sendATcommand("AT+CWJAP=\"ten_wifi\",\"mat_khau\"", "OK", 5000); //Thay ten_wifi và mat_khau của bạn
sendATcommand("AT+CIPMUX=1", "OK", 5000); // Bật chế độ đa kết nối
sendATcommand("AT+CIFSR", "OK", 5000); // Hiển thị ip
Serial.println("ESP8266 Connected");
}
/***************************************************
Kết nối với ThingsSpeak.com
****************************************************/
void startThingSpeakCmd(void)
{
memset(aux_str, '\0', 100);
snprintf(aux_str, sizeof(aux_str), "AT+CIPSTART=1,\"TCP\",\"%s\",80", IP);
if (sendATcommand2(aux_str, "OK", "ERROR", 20000) == 1)
{
Serial.println("OK Connected Thingspeak");
}
}
/***************************************************
Gửi data lên channel ThingsSpeak.com
****************************************************/
void sendThingSpeakCmd(void)
{
memset(aux_str, '\0', 100);
sprintf(aux_str, "AT+CIPSEND=1,%d", legth);
if (sendATcommand2(aux_str, ">", "ERROR", 20000) == 1)
{
Serial.println(cmd);
sendATcommand2(cmd, "SEND OK", "ERROR", 30000);
}
}
/***************************************************
Truyền tất cả dữ liệu lên thingspeak.com và đóng kết nối
****************************************************/
void updateDataThingSpeak(void)
{
startThingSpeakCmd();
// Gửi toàn bộ thông tin cảm biến cũng như trạng thái lên thingspeak
sprintf(cmd, "%s&field1=%d&field2=%d&field3=%d&field4=%d&field5=%d&field6=%d", msg, tempDHT, humDHT, lumen, soilMoist, pumpWaterStatus, pumpPesStatus);
legth = strlen(cmd) + 2;
sendThingSpeakCmd();
sendATcommand("AT+CIPCLOSE=1", "OK", 5000);
}
/***************************************************
Cập nhật trạng thái bơm lên thingspeak.com
****************************************************/
void updateCmdThingSpeak(void)
{
for (int i = 0; i < 1; i++) // Thực hiện 2 lần cho chắc ăn
{
startThingSpeakCmd ();
// Cập nhật trạng thái bơm
sprintf(cmd, "%s&field5=%d&field6=%d", msg, pumpWaterStatus, pumpPesStatus);
legth = strlen(cmd) + 2;
sendThingSpeakCmd();
sendATcommand("AT+CIPCLOSE=1", "OK", 5000);
}
}
Source code của 2 phiên bản bạn có thể tải tại đây
Nguồn: hocarm - org
About Mr. Facebook
0 nhận xét:
Đăng nhận xét