Automatické řízení bazénu pomocí Arduino vzniklo postupem času, kdy jsem začínal pouze s automatickým dávkováním chloru (chlornan sodný) pomocí programovatelného relé z Číny.
Nedávno jsem narazil na „výukovou“ sadu Arduino a řekl si, že by tam šel automatizovat celý bazén. Tak jsem si na zkoušku objednal pár komponent a začal zkoušet.
Plán byl takový:
1/ Automatický provoz - měl by mít za úkol ranní a večerní filtrování bazénu, přičemž přitom přidá požadovanou dávku chloru. Mimo tyto časové úseky bude hlídat teplotu bazénu a solárního ohřevu a dle podmínek bude ohřívat vodu v bazénu.
2/ Ruční provoz – po zmáčknutí tlačítka, přeruší automatický provoz a zapne pouze čerpadlo filtru. Tato funkce je zejména pro čištění bazénu, nebo pro promíchání přidaných chemikálií.
3/ Ruční dávka chloru – tato funkce je pro přidání chloru mimo automatický režim a je využívána víceméně po čištění bazénu, kdy se dopouští nová voda a je nutné vyrovnat hladinu chloru
4/ vypnutí – toto tlačítko vypne veškeré běžící funkce do doby, než se některá opět ručně spustí.
Vedlejší funkce:
Na LCD displeji se bude zobrazovat aktuální čas, teplota vody bazénu, aktuální zapnutá funkce a teplota solárního panelu (pouze při automatu).
Použité vybavení:
1/ Arduino NANO
2/ DS3231
3/ LCD 16x2 s I2C
4/ relé modul se 4 relé (stačily by tři)
5/ motorový kulový ventil 1“
6/ teplotní dvě teplotní čidla DS18B20
7/ peristaltické čerpadlo
8/ nezbytné součástky jako jsou 4 tlačítka, 4 LED, 5 rezistorů
Celé zapojení je velmi jednoduché. Tlačítka jsou pomocí funkce pinMode nastaveny jako INPUT_PULLUP na pinech 9,10,11,12,. Výstupy jsou pak na pinech A0-A3 a 8 (power LED). Obě tepelné čidla sdílí přes rezistor pin číslo 2. A nakonec LCD a RCT sdílí piny A4 a A5 (SCL,SDA). Na výstupech A0-A3 je zapojen relé modul a zároveň stavové LED, které dle aktuální činnosti svítí nebo nesvítí. Ty pak spínají hlavní filtrační čerpadlo, čerpadlo chloru a otevírají nebo zavírají ventil solárního ohřevu.
Rezistory k LED jsou 330Ω, k teplotním čidlům je pak rezistor 4k7Ω. LCD 1602 a RTC (DS3231) jsou připojeny pomocí SLC a SDA pinů (LCD na schématu nemá I2C řadič).
Program:
#include "Wire.h"
#include <OneWire.h>
OneWire oneWire(2);
// ----------- Display ------------
#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C lcd(0x3F,16,2);
// ----------- Display end ------------
// -------- Nastavení čidel -------------
#include <DallasTemperature.h>
DallasTemperature sensors(&oneWire);
DeviceAddress BAZEN = { 0x28, 0xFF, 0x99, 0x70, 0x01, 0x17, 0x03, 0x6F };
DeviceAddress PANEL = { 0x28, 0xFF, 0xF1, 0x88, 0x01, 0x17, 0x03, 0x2D };
// -------- Nastavení čidel konec-------------
// ------- Nastavení vstupů a výstupů--------
int releFiltr = A0; // hlavní čerpadlo
int releChlor = A1; // čerpadlo chloru
int releVentil = A2; // ventil ohřevu
int ledOn = A3; // Zelená (dvoubarevná LED)
int ledOff = 8; // červená (dvoubarevná LED)
int buttonOn = 12; // zapnutí automatu
int buttonOff = 11; // vypnutí všeho
int buttonFiltr = 10; // zapnutí filtrace
int buttonChlor = 9; // Dávka chloru
// nastavení proměnných pro teplotu a funkce
float Tbazen;
float Tpanel;
boolean filtraceAuto, cisteni, chlor;
// nastavení časování
long previousMillis = 0;
long previousMillisTemp = 0;
long previousMillisTempP = 0;
long interval = 1000; // čas snímání teploty (ms)
long previousButton = 0;
long intervalButton = 50; // čas omezení prokliku tlačítka
// ------------ Nastavení hodin ------------
#define DS3231_I2C_ADDRESS 0x68
byte decToBcd(byte val){
return( (val/10*16) + (val%10) );
}
byte bcdToDec(byte val){
return( (val/16*10) + (val%16) );
}
// ------------ Nastavení hodin konec------------
void setup(){
Wire.begin();
Serial.begin(9600);
// -------- nastavení pinů ------------
pinMode(releFiltr, OUTPUT);
pinMode(releChlor, OUTPUT);
pinMode(releVentil, OUTPUT);
pinMode(ledOn, OUTPUT);
pinMode(ledOff, OUTPUT);
pinMode(buttonOn, INPUT_PULLUP);
pinMode(buttonOff, INPUT_PULLUP);
pinMode(buttonFiltr, INPUT_PULLUP);
pinMode(buttonChlor, INPUT_PULLUP);
lcd.init();
lcd.backlight();
lcd.setCursor(0,0);
lcd.print("Automat. bazen");
lcd.setCursor(0,1);
lcd.print("Dušan Vala");
delay(2000);
lcd.setCursor(0,1);
lcd.print(" ");
lcd.setCursor(0,1);
lcd.print("Vypnuto");
digitalWrite(ledOff, HIGH);
// ---------- POUZE PRO NASTAVENÍ ČASU! ---------------------
// sekundy, minuty, hodiny, den v týdnu (1- neděle), datum, měsíc, rok
//setDS3231time(0,8,19,7,16,12,17);
// ---------- POUZE PRO NASTAVENÍ ČASU! KONEC ---------------
//nastavení rozlišení snímání čidel (9 - 0,5°C 10 - 0,25°C..)
sensors.begin();
sensors.setResolution(9);
}
// ---------- POUZE PRO NASTAVENÍ ČASU! ---------------------
/*void setDS3231time(byte second, byte minute, byte hour, byte dayOfWeek, byte
dayOfMonth, byte month, byte year){
// sets time and date data to DS3231
Wire.beginTransmission(DS3231_I2C_ADDRESS);
Wire.write(0); // set next input to start at the seconds register
Wire.write(decToBcd(second)); // set seconds
Wire.write(decToBcd(minute)); // set minutes
Wire.write(decToBcd(hour)); // set hours
Wire.write(decToBcd(dayOfWeek)); // set day of week (1=Sunday, 7=Saturday)
Wire.write(decToBcd(dayOfMonth)); // set date (1 to 31)
Wire.write(decToBcd(month)); // set month
Wire.write(decToBcd(year)); // set year (0 to 99)
Wire.endTransmission();
}*/
void readDS3231time(byte *second, byte *minute, byte *hour){
Wire.beginTransmission(DS3231_I2C_ADDRESS);
Wire.write(0);
Wire.endTransmission();
Wire.requestFrom(DS3231_I2C_ADDRESS, 7);
*second = bcdToDec(Wire.read() & 0x7f);
*minute = bcdToDec(Wire.read());
*hour = bcdToDec(Wire.read() & 0x3f);
}
void displayTime(){
// ------- Hodiny ---------
byte second, minute, hour;
readDS3231time(&second, &minute, &hour);
lcd.setCursor(0,0);
lcd.print(hour, DEC);
lcd.print(":");
if (minute<10){
lcd.print("0");
}
lcd.print(minute, DEC);
lcd.print(":");
if (second<10){
lcd.print("0");
}
lcd.print(second, DEC);
//--------- Hodiny konec ----------
}
//-------------------- LOOP --------------
void loop(){
// ------------- ZAPNUTÍ AUTOMATIKY --------
if (digitalRead(buttonOn) == LOW) {
unsigned long currentMillis = millis();
if(currentMillis - previousButton > intervalButton) {
previousButton = currentMillis;
sensors.requestTemperatures();
off();
digitalWrite(ledOn, HIGH);
digitalWrite(ledOff, LOW);
lcd.setCursor(0,1);
lcd.print(" ");
lcd.setCursor(0,1);
lcd.print("Automat");
filtraceAuto = true;
}
}
// ----------- POUZE FILTRACE / ČÍŠTĚNÍ ------------
if (digitalRead(buttonFiltr) == LOW) {
unsigned long currentMillis = millis();
if(currentMillis - previousButton > intervalButton) {
previousButton = currentMillis;
sensors.requestTemperatures();
off();
digitalWrite(ledOn, HIGH);
digitalWrite(ledOff, LOW);
lcd.setCursor(0,1);
lcd.print(" ");
lcd.setCursor(0,1);
lcd.print("Cisteni/Filtrace");
cisteni = true;
}
}
// -------- DÁVKOVÁNÍ CHLORU --------
if (digitalRead(buttonChlor) == LOW) {
unsigned long currentMillis = millis();
if(currentMillis - previousButton > intervalButton) {
previousButton = currentMillis;
sensors.requestTemperatures();
off();
digitalWrite(ledOn, HIGH);
digitalWrite(ledOff, LOW);
digitalWrite(releChlor, HIGH);
lcd.setCursor(0,1);
lcd.print(" ");
lcd.setCursor(0,1);
lcd.print("Davka chloru");
chlor = true;
}
}
// ------------- VYPNUTO -----------
if (digitalRead(buttonOff) == LOW) {
unsigned long currentMillis = millis();
if(currentMillis - previousButton > intervalButton) {
previousButton = currentMillis;
sensors.requestTemperatures();
off();
}
}
filtraceChlor(); // načtení funkce denního programu
filtrace(); // funkce pro filtraci nebo čištění
displayTime(); // zobrazení hodin
bazenTemp(); // zobrazení teploty bazénu
if(filtraceAuto) { // pokud je aktivní automatický režim,
panelTemp(); // je zobrazena teplota soláního panelu
}
}
//-------------------- LOOP END ----------------
// ---------- VYPNUTÍ VŠECH FUNKCÍ --------------
void off(){
digitalWrite(releFiltr, LOW);
digitalWrite(releChlor, LOW);
digitalWrite(releVentil, LOW);
digitalWrite(ledOn, LOW);
digitalWrite(ledOff, HIGH);
cisteni = false;
filtraceAuto = false;
chlor = false;
lcd.setCursor(0,1);
lcd.print(" ");
lcd.setCursor(0,1);
lcd.print("Vypnuto");
}
// zapnutí pouze filtrace nebo dávky chloru
void filtrace(){
if(cisteni || (chlor)){
digitalWrite(releFiltr, HIGH); //zapni čerpadlo
digitalWrite(releVentil, LOW); //a vypni ventil pokud by byl otevřený
if(chlor) {
byte sec, minuty, hod;
readDS3231time(&sec, &minuty, &hod);
if((sec >= 5 & (sec < 45))) {
digitalWrite(releChlor, HIGH);
} else {
if(sec == 59) {
lcd.setCursor(0,1);
lcd.print(" ");
lcd.setCursor(0,1);
lcd.print("Automat");
digitalWrite(releFiltr, LOW);
filtraceAuto = true;
chlor = false;
}
digitalWrite(releChlor, LOW);
}
}
} else {
if(!filtraceAuto){
digitalWrite(releFiltr, LOW);
}
}
}
/* ------------ AUTOMATICKÁ FILTRACE ----------
* Ráno a večer zapne filtraci a nadávkuje chlor
* Mimo časovou filtraci zapíná denní program ohřevu bazénu
*/
void filtraceChlor(){
byte sec, minuty, hod;
readDS3231time(&sec, &minuty, &hod);
if(filtraceAuto & (!cisteni & (!chlor))) {
// --------------- VEČER / RÁNO------------
if((hod >= 21 & (hod < 22) || (hod >= 5 & (hod < 6)))) { // filtrace pojede mezi 21-22hod večer a mezi 5-6hod ráno
digitalWrite(releFiltr, HIGH); //zapni čerpadlo
if((minuty == 0) & (sec >=5) & (sec <=45)) { // u každé filtrace bude přidán chlor po dobu 40s
digitalWrite(releChlor, HIGH);
} else {
digitalWrite(releChlor, LOW);
}
} else {
teplota(); // mimo daný čas zapne denní režim ohřevu bazénu
}
}
}
// ------------- DENNÍ PROGRAM OHŘEVU -------------
void teplota(){
unsigned long currentMillis = millis();
if(currentMillis - previousMillis > interval) {
previousMillis = currentMillis;
sensors.requestTemperatures();
Tbazen = sensors.getTempC(BAZEN); //teplota bazenu
Tpanel = sensors.getTempC(PANEL); //teplota panelu
float hystereze = 2; //rozdíl teplot pro zapnutí a vypnutí
if(Tbazen == -127 || (Tpanel == -127)){ // pokud není čidlo připojeno hlásí teplotu -127
lcd.setCursor(9, 0);
lcd.print("Chyba cidla!");
} else {
if(Tbazen <= 24) {//pokud je teplota bazénu menší
if((Tpanel - 28) > hystereze) { //a teplota panelu větší
digitalWrite(releFiltr, HIGH); // zapni čerpadlo
digitalWrite(releVentil, HIGH); // a uzavři ventil
} else {
if((28 - Tpanel) > hystereze) { // pokud teplota panelu klesne
digitalWrite(releFiltr, LOW); // vypni čerpadlo
}
}
} else {
digitalWrite(releFiltr, LOW); // vypni čerpadlo
digitalWrite(releVentil, LOW); // otevři ventil
}
}
}
}
// ---------- TEPLOTA BAZÉNU -----------
void bazenTemp(){
unsigned long currentMillis = millis();
if(currentMillis - previousMillisTemp > interval) {
previousMillisTemp = currentMillis;
//načtení teploty z čidla
sensors.requestTemperatures();
Tbazen = sensors.getTempC(BAZEN);
if(Tbazen == -127){
lcd.setCursor(9, 0);
lcd.print("Error");
} else {
lcd.setCursor(9, 0);
lcd.print(Tbazen);
lcd.print((char)223);
lcd.print("C");
}
}
}
// ---------- TEPLOTA SOLÁRNÍHO PANELU -----------
void panelTemp(){
unsigned long currentMillis = millis();
if(currentMillis - previousMillisTempP > interval) {
previousMillisTempP = currentMillis;
//načtení teploty z čidla
sensors.requestTemperatures();
Tpanel = sensors.getTempC(PANEL);
if(Tpanel == -127){
lcd.setCursor(9, 1);
lcd.print("Error");
} else {
lcd.setCursor(9, 1);
lcd.print(Tpanel);
lcd.print((char)223);
lcd.print("C");
}
}
}
Systém krom dávkování chloru je myslím celkem jednoduchý a jasný. Ta dávka chloru mimo program chce ještě doladit. Takhle vím, že ji mám spustit mezi nultou a pátou sekundou aby prošla celá. Zatím nevím jak líp to udělat. Jsem naprostý začátečník a tohle je můj naprosto první program co jsem zkoušel. Určitě by tam šlo udělat něco líp nebo jinak, ale pro mé potřeby to zatím stačí a myslím, že tím nepohrdne ani někdo další kdo nad něčím podobným přemýšlí. :)
Další vývoj projektu je aktualizován na webu https://github.com/Rellik12/BazenBot