Witam!
Pragnę dziś przedstawić swój pierwszy projekt na Arduino, a mianowicie sterownik pieca C.O. Tak na prawdę jest to sterownik pompek C.O. i pieca niejako przy okazji, ale kto by się takimi szczegółami przejmował :)
Po co to komu?
Tobie? Nie mam pojęcia, ale liczę, że uda mi się zainspirować Cię do popełnienia jeszcze ciekawszego projektu (i oczywiście podzielenia się dalej). Mi projekt jest potrzebny do sterowania ogrzewaniem w domu. Dlaczego?
Otóż mieszkam w piętrowym domu z piwnicą. Parter i piętro to osobne mieszkania, połączone klatką schodową, która nie jest częścią żadnego z nich. Obie kondygnacje obsługuje wspólny piec gazowy, który (sam w sobie) posiada dwa oddzielne obiegi: CO (centralnego ogrzewania) oraz CWU (Ciepłej Wody Użytkowej – znaczy, bojler). Dla każdego obiegu jest osobne wyjście sterujące – pompa obiegu CO oraz pompa obiegu CWU. Piec posiada także wejścia: termostat do regulacji temperatury CO oraz wejście analogowe temperatury wody w bojlerze (termistor). Żeby nie było za prosto, piec ma tylko jedno wejście do sterowania obiegiem CO. Jeśli chodzi o CWU, to oprócz bojlera grzanego gazem, posiadam drugi bojler podłączony do instalacji solarnej (wot, dotacje unijne :>). Logicznym było podłączenie tych bojlerów kaskadowo: zimna woda z rurociągu wpływa najpierw do zbiornika solarnego, gdzie jest podgrzewana albo i nie (np w nocy lub zimą), a następnie MUSI przepłynąć do zbiornika podłączonego do pieca gazowego, który jest podgrzewany (lub nie) w zależności od pory dnia i oczywiście temperatury. Stamtąd woda bez przeszkód dociera do kranów, lub wraca do zbiornika solarnego w celu mieszania (obieg wtórny z oddzielną pompką).
Taki układ został spowodowany wieloma czynnikami: oszczędnością, rozłożeniem w czasie, dostępnymi funduszami… Kto nie jest milionerem i sam grzebie w swoim domu, ten wie jak to jest :)
Generalnie działanie było OK, jednak posiadało dwa istotne mankamenty:
- Piec ma tylko jedno wejście sterujące obiegiem CO, co oznacza, że wysterować mogę jedynie grzanie na piętrze lub parterze – tam, gdzie jest czujnik temperatury. Można oczywiście włączać pompkę obiegu na piętrze na którym nie ma czujnika, ale jeśli piec w tym czasie nie będzie grzał, to dana kondygnacja będzie się tylko chłodzić.
- Czasami (latem) słońce nagrzewało 150-cio litrowy zbiornik solarny do temperatury ~95°C, gdy w tym samym czasie 150L w zbiorniku gazowym było zimne. Pomagało włączenie pompki obiegowej między zbiornikami np. przed wyjściem do pracy, i wyłączenie po przyjściu, ale jest to niewygodne (trzeba pamiętać żeby zejść specjalnie do piwnicy), energii (pompka wymiesza wodę szybciej niż wrócę z pracy (8h+), a do tego nie jest idealne – jak się zachmurzy albo będzie po prostu zimno to mieszanie nie jest potrzebne.
Co z tych rzeczy udało się zrealizować? W wersji testowej na moim biurku – wszystko :) Niestety działanie “produkcyjne” nie jest takie łatwe (o tym później), ale już od tygodnia działa oddzielne grzanie w zależności od temperatury/godziny/dnia tygodnia na obu poziomach. Pozostało mieszanie CWU, do którego brakuje mi uruchomienie jednego czujnika. W jaki sposób tego dopiąłem? Czytaj dalej…
Logika (czyli jak to działa)
Cała logika sterownika jest prosta:
- Jeżeli temperatura wody w zbiorniku solarnym jest wyższa o 5 stopni od temperatury w zbiorniku gazowym, uruchom mieszanie na 30min.
- Jeżeli temperatura na dowolnej kondygnacji jest poniżej zadanej dla aktualnej pory dnia minus histereza, uruchom pompkę CO dla danej kondygnacji i daj sygnał do włączenia pieca aż do momentu osiągnięcia docelowej temperatury plus histereza.
- Wyświetl temperaturę czujników, stan przekaźników i aktualny czas na ekranie.
Banalne, nie? :D No to budujemy!
Sprzęt (to, co tygryski lubią najbardziej)
Co wykorzystałem w swoim projekcie:
- Arduino ProMini 16MHz 5V
- Moduł RTC – DS1307, interfejs I2C
- Moduł przekaźników na I2C
- Wyświetlacz LCD 4×16
- Rezystory (220 Ω, 4.7 kΩ)
- Puszki plastikowe, kawałek płytki miedzianej do wytrawienia, kabelki, inne pierdoły widoczne na zdjęciach.
Schemat oraz kod całego bałaganu do ściągnięcia TUTAJ: git, podgląd “na szybko” załączam poniżej. Brakuje na nim trzech rzeczy: czujników temperatury (na PCB jest opisane wyjście OneWire, myślę że nie ma potrzeby dorysowywania samego czujnika), RelayShielda (nie ma go we fritzingu, a jest podłączony dokładnie tak samo jak moduł zegara) oraz wyjść do programowania (mam zmontowaną własnoręcznie przejściówkę z RS232 na FTDI)
Najpierw podłączenia na breadboardzie, kompletnie poglądowo:
Następnie logika (chyba wystarczająco czytelnie):
I na końcu schemat PCB – był tak naprawdę tylko punktem odniesienia do wytrawienia płytki
Tutaj w sumie szału nie ma: będąc wygodnym wykorzystuję jak się da magistrale (OneWire i I2C), co pozwala mi zaoszczędzić piny niezbędne do obsługi wyświetlacza. obie magistrale są na tej płytce jedynie wyprowadzeniami, moduły są podłączane kabelkami – łatwiej potem zmieścić to wszystko w puszkę. Dodatkowo do RealyShielda dopięte zostaną kable zasilające pompki, ale to już jest poza schematem samego Arduino, i myślę że każdy wie jak podłączyć gotowy przekaźnik (konkretne opisy wejść i wyjść znajdą się pewnie przy programowaniu)
Programowanie (komplikowanie życia)
Okej, podłączenie już za nami – nie było tak strasznie, nie? To przechodzimy do zagwozdek logicznych. Kod udostępniam dla potomnych TUTAJ, a poniżej listing całego programu:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 |
#include <Wire.h> #include <OneWire.h> #include <LiquidCrystal.h> #include <Time.h> #include <DS1307RTC.h> #include <avr/wdt.h> //Definicje, definicje: LiquidCrystal lcd(4,5,6,7,8,9,10,11,12,13); // PINy wyświetlacza LCD. const int wiersze = 4, kolumny = 16; // I jego geometria //Tablice stanu przekaźników + dwie funkcje do zarządzania przekaźnikami + adres shielda: volatile bool relaySet[4]={true,true,true,true}; volatile bool relayChan[4]={false,false,false,false}; #define relay B0100000 void Relay_on(byte _data ) { byte _data2; Wire.requestFrom(relay, 1); if(Wire.available()) { _data2 = Wire.read(); // } Wire.beginTransmission(relay); Wire.write(_data2 | 1<<_data); Wire.endTransmission(); relaySet[_data]=true; lcd.setCursor(12,_data);lcd.print("1"); } } void Relay_off(byte _data ) { byte _data2; Wire.requestFrom(relay, 1); if(Wire.available()) { _data2 = Wire.read(); // } Wire.beginTransmission(relay); Wire.write(_data2 & ~(1<<_data)); Wire.endTransmission(); relaySet[_data]=false; lcd.setCursor(12,_data);lcd.print("0"); } } // Funkcja do ustawiania przekaźników jak należy: void setRelay(){ for (byte i=0;i<=4;i++) { if(relaySet[i]!=relayChan[i]){ if(relayChan[i]) Relay_on(i); else Relay_off(i); }; }; for(byte i=0; i<4 ; i++) {lcd.setCursor(12,i);lcd.print(int(relaySet[i]));} } //############## TEMPERATURA OneWire ds(2); // 1-Wire na pinie 2 byte TempG[8] = { 0x28, 0x45, 0xAB, 0x94, 0x04, 0x00, 0x00, 0xB2 }; // Adres Sensora nr1 byte TempD[8] = { 0x28, 0x5D, 0x15, 0xD8, 0x02, 0x00, 0x00, 0x86 }; // Adres Sensora nr2 byte TempCWU[8] = { 0x28, 0x33, 0x8d, 0x45, 0x04, 0x00, 0x00, 0x72 }; // Adres Sensora nr3 //byte TempSol[8] = { 0x28, 0xB4, 0xED, 0x53, 0x04, 0x00, 0x00, 0x3D }; // Adres tymczasowego sensora nr4 byte TempSol[8] = { 0x28, 0xDB, 0x79, 0x45, 0x04, 0x00, 0x00, 0x43 }; // Adres Sensora nr4 - prawidłowy, juz wstawiony volatile float TempTable[4]; void getTemp(byte addr[],volatile float TempTable[],int index){ byte data[12]; ds.reset(); ds.select(addr); ds.write(0x44, 0); // start conversion, without parasite power on at the end delay(50); // maybe 10ms is enough, maybe not ds.reset(); ds.select(addr); ds.write(0xBE); // Read Scratchpad for (byte i = 0; i < 9; i++) data[i] = ds.read(); int16_t raw = (data[1] << 8) | data[0]; TempTable[index] = (float)raw / 16.0; } void updateTemp(volatile float wynikowa[]){ getTemp(TempG,wynikowa,0); getTemp(TempD,wynikowa,1); getTemp(TempCWU,wynikowa,2); getTemp(TempSol,wynikowa,3); } void printTemp() { lcd.setCursor(6,0);lcd.print(TempTable[0]); lcd.setCursor(6,1);lcd.print(TempTable[1]); lcd.setCursor(6,2);lcd.print(TempTable[2]); lcd.setCursor(6,3);lcd.print(TempTable[3]); } void prep() { lcd.setCursor(0,0);lcd.print("TempG "); lcd.setCursor(0,1);lcd.print("TempD "); lcd.setCursor(0,2);lcd.print("ZbCWU "); lcd.setCursor(0,3);lcd.print("ZbSol "); } void printTime() { if (timeStatus() == timeSet){ lcd.setCursor(14,0); if (hour()<10) lcd.print("0"); lcd.print(hour()); lcd.setCursor(14,1); if (minute()<10) lcd.print("0"); lcd.print(minute()); lcd.setCursor(14,2); if (second()<10) lcd.print("0"); lcd.print(second()); lcd.setCursor(15,3); lcd.print(weekday()); } else { lcd.setCursor(14,0);lcd.print("Na"); lcd.setCursor(14,1);lcd.print("Na"); lcd.setCursor(14,2);lcd.print("Na"); } } void checkTemp( byte level, float temp) { if ( TempTable[level] <= temp-0.2 ) relayChan[level]=true; // Sprawdzenie temperatury i włączenie przekaźnika z histerezą (włączamy 0.2 stopnia poniżej wartości granicznej) else { if (TempTable[level] >= temp+0.2) relayChan[level]=false;} // Sprawdzenie temperatury i wyłączenie przekaźnika z histerezą (wyłączamy 0.2 stopnia powyżej wartości granicznej) } //############ TABLICE CZASU const bool grzanie_pietro[2][24][4]={ { // workdays {0,0,0,0}, // 00 {0,0,0,0}, // 01 {0,0,0,0}, // 02 {0,0,0,0}, // 03 {0,0,0,0}, // 04 {0,0,0,0}, // 05 {1,1,1,1}, // 06 {1,1,1,1}, // 07 {1,1,1,1}, // 08 {0,0,0,0}, // 09 {0,0,0,0}, // 10 {0,0,0,0}, // 11 {1,1,1,1}, // 12 {1,1,1,1}, // 13 {0,0,0,0}, // 14 {0,0,0,0}, // 15 {0,0,0,0}, // 16 {0,0,0,0}, // 17 {1,1,1,1}, // 18 {1,1,1,1}, // 19 {1,1,1,1}, // 20 {1,1,1,1}, // 21 {0,0,0,0}, // 22 {0,0,0,0} // 23 }, { // weekend {0,0,0,0}, // 00 {0,0,0,0}, // 01 {0,0,0,0}, // 02 {0,0,0,0}, // 03 {0,0,0,0}, // 04 {0,0,0,0}, // 05 {0,0,0,0}, // 06 {1,1,1,1}, // 07 {1,1,1,1}, // 08 {0,0,0,0}, // 09 {0,0,0,0}, // 10 {0,0,0,0}, // 11 {1,1,1,1}, // 12 {1,1,1,1}, // 13 {0,0,0,0}, // 14 {0,0,0,0}, // 15 {0,0,0,0}, // 16 {0,0,0,0}, // 17 {1,1,1,1}, // 18 {1,1,1,1}, // 19 {1,1,1,1}, // 20 {1,1,1,1}, // 21 {0,0,0,0}, // 22 {0,0,0,0} // 23 }, }; bool grzanie_parter[24][4]={ {0,0,0,0}, // 00 {0,0,0,0}, // 01 {0,0,0,0}, // 02 {0,0,0,0}, // 03 {0,0,0,0}, // 04 {0,0,0,0}, // 05 {1,1,1,1}, // 06 {1,1,1,1}, // 07 {1,1,1,1}, // 08 {0,0,0,0}, // 09 {0,0,0,0}, // 10 {0,0,0,0}, // 11 {0,0,0,0}, // 12 {0,0,0,0}, // 13 {0,0,0,0}, // 14 {0,0,0,0}, // 15 {0,0,0,0}, // 16 {0,0,0,0}, // 17 {0,0,0,0}, // 18 {0,0,0,0}, // 19 {1,1,1,1}, // 20 {1,1,1,1}, // 21 {1,1,1,1}, // 22 {0,0,0,0} // 23 }; //############ TIMERY volatile int tcwu = 0; volatile int t2 = 0; ISR(TIMER1_COMPA_vect){ if (relayChan[3]==true && tcwu <= 225) tcwu++; // Jeżeli mieszamy CWU, czekamy do końca else { // Jeśli to koniec, to: if (relayChan[3]==true) { // Niestety trzeba upewnić się że mieszamy :( if (TempTable[2]+5 <= TempTable[3]) { // Jeżeli kończymy mieszać, sprawdzamy czy nie lepiej jest mieszać dalej tcwu = 0; // Widocznie warto mieszać dalej, mieszamy więc przez następne 15min return; // Można też ustawić tcwu na więcej niż 0 i dodatkowo mieszać krócej :> } else { relayChan[3]=false; // Jeśli kończymy mieszać i nie warto dalej, wyłączamy przekaźnik tcwu = 0; // I zerujemy licznik, żeby potem mieszać 15min return; } } } } ISR(TIMER2_COMPA_vect){ if( t2 >= 200 ) { if (TempTable[2]+5 <= TempTable[3]) relayChan[3]=true; // Tutaj odpalamy mieszanie CWU if (relayChan[0] || relayChan[1]) relayChan[2]=true; else relayChan[2]=false; t2 = 0; } else t2++; } //############# PROGRAM void setup(void) { setSyncProvider(RTC.get); setRelay(); //zerowanie przekaźników po resecie. lcd.clear(); lcd.begin(kolumny, wiersze); // teraz LCD lcd.setCursor(5,0); lcd.print("Witaj!"); delay(1000); prep(); //ustawienie Timera1 (16bit): cli(); TCCR1A = 0; TCCR1B = 0; TCNT1 = 0; OCR1A = 62500; TCCR1B |= (1 << WGM12); //CTC TCCR1B |= (1 << CS12) | (1 << CS10); TIMSK1 |= (1 << OCIE1A); sei(); //ustawienie Timera2 (8bit): cli(); TCCR2A = 0; TCCR2B = 0; TCNT2 = 0; OCR2A = 157; TCCR2A |= (1 << WGM21); //CTC TCCR2B |= (1 << CS12) | (1 << CS10); TIMSK2 |= (1 << OCIE1A); sei(); wdt_enable(WDTO_8S); } void loop(void) { wdt_reset(); updateTemp(TempTable); printTime(); printTemp(); // Ustawiamy grzanie na pietrze: if ( 1 < weekday() < 7){ if(grzanie_pietro[0][hour()][minute()/15]){ checkTemp(0,21);} else checkTemp(0,18); } else { if(grzanie_pietro[1][hour()][minute()/15]){ checkTemp(0,21);} else checkTemp(0,18); } //Ustawiamy grzanie na parterze: if(grzanie_parter[hour()][minute()/15]){ checkTemp(1,21);} else checkTemp(1,18); setRelay(); delay(200); } |
Jak widać samego kodu jest dość sporo, i nie zawsze dobrze opisany, dlatego pozwolę sobie skomentować program metoda po metodzie.
Na samym początku oczywiście definicja wyświetlacza (jego pinów i geometrii). Ponieważ za namową kolegi dokupiłem kilka dni temu moduł ethernet, prawdopodobnie cała obsługa wyświetlacza zostanie wyeksportowana do I2C (przejściówka ma do mnie dotrzeć lada dzień) – potrzebuję pinów 11, 12 i 13 do SPI.
Następnie definicja adresu RelayShielda, dwie tablice: stanu aktualnego oraz docelowego przekaźników oraz trzy funkcje do zmiany stanu. Adres wpisany na sztywno, ustalony za pomocą pinów na shieldzie. Tablic użyłem aby nie przełączać za każdym razem przekaźników – funkcje przełączania wywoływane są tylko wtedy, kiedy jest to potrzebne (uznałem że bardziej zależy mi na tym, żeby przekaźniki nie cykały bez potrzeby). Osiągnąłem po poprzez przechowywanie aktualnego stanu przekaźników w tablicy RelaySet, a stanu docelowego w tablicy RelayChan. Synchronizacja następuje poprzez wywołanie funkcji SetRelay, która sprawdza w prostej pętli, czy odpowiednie wartości w tablicach są zgodne. Jeśli tak, to znaczy że nie trzeba zmieniać stanu przekaźnika, a jeśli się różnią, to w zależności od stanu docelowego włączamy/wyłączamy przekaźnik i za jednym zamachem wpisujemy aktualny stan zarówno do tablicy docelowej jak i na LCD.
Kolejny blok to obsługa czujników temperatury. Co prawda definicja adresów jest kompletnie książkowa, natomiast już samo wskazanie temperatury (wynik) przechowywane jest w tablicy – wszystko ze względu na łatwość przyporządkowania indeksów – temperatura z indeksu 0 to temperatura, za która odpowiedzialny jest przekaźnik 0, i analogicznie dla przekaźnika 1. To na co pragnę zwrócić uwagę to kompletna definicja funkcji odczytującej temperaturę – zapewne co bardziej spostrzegawczy zauważyli brak biblioteki DallasTemperature. Nie jest mi ona do niczego potrzebna, całość bezpośredniej obsługi czujników posiada jedna jedyna funkcja getTemp, a jej ręczna definicja pozwala mi od razu wpisywać temperaturę do tablicy, oraz – co ważniejsze – łatwą i szybką zmianę czasu oczekiwania na odczyt temperatury – domyślnie funkcja ta czeka aż 750ms, co bez problemu zbiłem do 100ms… Co prawda czasami odczyt jest błędny, ale z drugiej strony łatwiej jest eksperymentować z czasem w samym programie, a nie edytować bibliotekę. Oprócz tego funkcja aktualizująca stan temperatur, osobna do wyświetlania ich na ekranie (może je połączę w jedno jak przepnę wyświetlacz). Ostatnią funkcją w tym bloku jest ta służąca do ustawiania stanu pożądanego przekaźników w zależności od wskazań temperatury. Mam nadzieję że dobrze widać dlaczego przydatne jest posiadanie analogicznych indeksów – przekazuję tylko jeden indeks, a cała magia się dzieje sama :)
Dalej następują tablice grzewcze. Jest to chyba najbardziej trudny do zrozumienia kawałek kodu – trzy gigantyczne tablice wypełnione zerami i jedynkami. Biorą się one z mojej chęci prostego wizualnie zarządzania temperaturą docelową w danej kondygnacji i usunięci zbędnych IFów. Jedynki (wartości true) reprezentują przedział czasu, w którym piec ma grzać do wyższej temperatury, np. kiedy więcej osób przebywa w domu, albo jest wieczór i już nie robi się niczego rozgrzewającego, tylko siedzi i czyta majsterkowo.pl :). Zera z kolei reprezentują stan tzw. przeciwzamrożeniowy – kiedy wyższa temperatura nie jest potrzebna bo albo nikogo nie ma w domu, albo i tak zasuwa się z odkurzaczem z góry na dół :). Na parterze jest tylko jedno ustawienie – każdy dzień wygląda dokładnie tak samo, ponieważ nikt tam chwilowo nie mieszka, a mimo to od czasu do czasu się schodzi, to piec powinien grzać tam tylko wtedy, gdy domownicy tam przebywają – zazwyczaj rano przed wyjściem do pracy i wieczorem przed snem. Postanowiłem, że piętnastominutowe przedziały będą wystarczające do wysterowania takiego działania, stąd cztery kolumny dla każdego z dwudziestu czterech wierszy odpowiadających kolejnym godzinom. Tu również opłaca się sprytne wykorzystywanie indeksów, bo godzinę mam “gratis”, bez zbędnego IFowania :). W przyszłości, jeśli przyjdzie mi na to ochota, mogę łatwo zwiększyć rozdzielczość minutową poprzez dodawanie kolejnych kolumn (rozważałem kroki co 10 lub 5 minut, ale odpuściłem ze względu na ograniczoną ilość pamięci). Również, gdyby bardziej rozbudować ilość tablic w “trzeci wymiar”, można by używać tego indeksu do automatycznego wyboru dnia, bez dodatkowego sprawdzenia tego IFem, jednakże przy aktualnym stopniu złożoności nie jest to możliwe.
W tym miejscu można też pokusić się o zamianę tablicy z bool na float i bezpośrednie wpisywanie temperatur, jednak znów bałem się o rozmiar potrzebnej pamięci, a dwa stany jak dla mnie są póki co zupełnie wystarczające. Ostatnio doczytałem też o dyrektywie PROGMEM, którą postaram się wykorzystać w następnej rewizji.
Dalej następuje definicja timerów do przerwań. Ponieważ na ArduinoProMini mogę z marszu wykorzystać timer1 oraz timer2, z czego jeden generuje przerwanie max co 4 sek a drugi co 16ms, musiałem troszkę nakombinować. Pierwszy if w przerwaniu timera1 pozwala mi odliczać ~30min o ile mam włączoną pompkę mieszającą wodę w zbiornikach CWU. Tutaj akurat postarałem się o dobre komentarze, mam nadzieję że ze zrozumieniem nie będzie problemu. W ten oto sposób dotychczasowy mankament nr 3 mam z głowy. Timer2 służy natomiast do synchronizacji przekaźników (jak podłączę ekran na i2c/spi to postaram się tu wcisnąć też jego odświeżanie). Chciałem zwrócić uwagę na linijkę
1 |
if (relayChan[0] || relayChan[1]) relayChan[2]=true; |
czyli jeśli działa pompka CO na piętrze lub parterze, następuje włączenie pieca (przekaźnikiem 2). W ten prosty sposób załatwiam dotychczasowy mankament nr 1.
W setupie włączam obsługę watchdoga (czytałem gdzieś że arduino może się wiechnąć po ~49 dniach ze względu na przepełnienie funkcji milis(), a nie chciałbym się obudzić któregoś dnia w zimnym domu :). Watchdoga ustalam na 8sekund (po tym czasie powinien restartnąć arduino), bo nie chciałem za często go resetować, a z kolei jak sterownik wiechnie się na 8sek to też nic wielkiego się nie stanie – grunt żeby się zrestartował). Od razu resetuję też rzeczonego watchdoga, tak na wszelki wypadek. Następnie ustawiamy dostawcę zegara, wyłączamy przekaźniki (póki co mają być w stanie deklarowanym na saaamym początku programu, a więc 0), odpalamy LCD, czyścimy, ładnie się witamy (a co!), ustalamy timery 1 oraz 2 na jak największe wartości, aż wreszcie czyścimy ekran (starczy tego przywitania) i odpalamy przerwania.
Pętla loop nie jest zbytnio zamotana, najpierw (przede wszystkim) następuje reset watchdoga. Następnie pobieramy temperatury z czujników (przejrzyście, bo w funkcji jest syf), wyświetlamy czas i temperaturę z czujników. I oczywiście sprawdzamy czy trzebaby grzać czy nie – zauważcie jak dzięki logicznemu wykorzystaniu adresowania tablic omijam dwa IFy – ten od sprawdzenia godziny oraz minut. Niestety nie mogę ominąć IFowania dnia – póki co wystarcza mi rozdzielenie grzania na weekend/dni robocze, dlatego też pisałem wcześniej o małej złożoności programu. Tym niemniej takie rozwiązanie jest IMO o niebo lepsze od ciągnących się w nieskończoność i zagnieżdżonych switch…case, że o nieszczęsnych ifach nie wspomnę. Na sam koniec ustawiamy przekaźniki (synchronizujemy je zgodnie z tablicą RelayChan i grzecznie czekamy 200ms przed następnym obrotem :)
Lutowanie i montaż (jeszcze więcej komplikowania)
No to jak wszystko już było gotowe, nadszedł czas żeby to zmontować. Z góry muszę przeprosić za kiepską jakość zdjęcia, ale podczas składania nie miałem jeszcze porządnego aparatu, a i tak wydawało mi się że robię je zupełnie niepotrzebnie.
Cóż tu widać? Gotową, wytrawioną płytkę z zapiętym do niej ArduinoProMini oraz LCD, RelayShield (po prawej), przejściówkę RS-232 do FTDI (za Mega, z dopiętym szarym kabelkiem), Arduino Mega (używałem do zasilania czujników, nie chciało mi się chwilowo wyprowadzać napięcia z samego ProMini), Zegar się schował za płytką, ale widać kawałek tasiemki do niego idącej. W głębi trafo z gołymi zaciskami na 230V (nie róbcie tak:>) i troszkę bliżej zasilacz 5V. No i na pierwszym planie BreadBoard z powtykanymi czujnikami temperatury (z lewej) i zbędnymi kabelkami po prawej.
Płytka -podstawka, po lewej miejsce na arduino, po prawej LCD
To samo od spodu, zauważcie rezystor 4.7K jest zlutowany równolegle to tego z wierzchniej strony, później wyjaśnię czemu.
A tutaj znów wierzch z włożonym już ProMini. wtyczka z białym i zielonym kabelkiem to wyprowadzenie I2C(TWI) na płytkę – nie za bardzo było jak to zmieścić normalną drogą, stąd taki potworek)
Kiedy stwierdziłem że czas odpalić cały ten majdan, zaczęło się przenoszenie do puszek:
Tutaj widać front-panel z zegarem (górny lewy róg) podstawką z LCD (to duże w środku) i wyjściem do programowania (dolny prawy). wszystko porządnie przyśrubowane (żadnego kleju!), co ciekawe do frontu przymocowany jest ekran LCD, a podstawka z arduino trzyma się na samych goldpinach – wchodzi bardzo ciasno, więc nie ma obaw przed poruszeniem.
Tutaj mamy zbliżenie na LCD z wyciągniętą podstawką (w tle) i moduł zegara.
Pełny przegląd całego bałaganu – przez puszkę z lewej przechodzą wszystkie kable od pieca do pompek, czujników, zasilania, stąd też spory natłok okablowania. Wprawne oko zauważy też radiatory zasilacza (miałem tam doprowadzone 12V DC, wystarczyło tylko dopiąć zasilacz bez trafo i voila). Przed samym założeniem wygląda to jak wyżej. Żółty kabelek to szyna danych 1-wire, czarne kabelki GND, spleciony czerwono-czarny to i2c do RelayShielda (zresztą widać jak się łączy z płytką przy lewym brzegu zdjęcia), para pomarańczowy-czerwony to +5V, a na samym dole mamy tasiemkę do zegara. Zdjęcie zostało wykonane wcześniej, stąd kabelki do FTDI mają jeszcze “kostkę”.
A tutaj z kolei cały ten bałagan w pełnej okazałości – zasilacz po lewej podaje dodatkowe 12V (do czego innego).
Cały sterownik w jednym ujęciu (widać błędy na wyświetlaczu, za chwilę wyjaśnię o co biega)
Piec + sterownik i trochę bałaganu, jak to w piwnicach :)
Problemy i błędy (ghrrr)
Niestety musiałem się zmierzyć z kilkoma nieprzewidzianymi problemami, które na nieszczęście objawiły się dopiero po połączeniu wszystkiego w miejscu docelowym. Kolejny dowód na to, że wszystkiego nie da się przewidzieć…
1. Częste restarty arduino
Okazało się, że jeśli załącza się pompka na parterze, to arduino wykonuje niekontrolowany restart, po którym następuje reset przekaźników, następnie włączana jest pompka na parterze, niekontrolowany restart… ad infinitum. Po grzebaniu i mierzeniu i dalszym testowaniu, winnymi okazały się zakłócenia powodowane przez zbyt bliskie poprowadzenie kabelków z 230V AC i sygnałowego 1-wire do czujnika na parterze. W związku z tym że odległość była spora, a wszystko szło w jednym kablu, zamiast 230V AC jest tam teraz 12V DC – przekaźnik na RelayShieldzie steruje za pomocą tego napięcia innym przekaźnikiem przy samej pompce. Pomogło.
2. Restartowanie arduino w kółko po przeprogramowaniu
To był ciekawy błąd. Używam laptopa z przejściówką USB-RS232 na chipie Prolific (z linuksem działa super), do tego kabel RS232 null-modem i na koniec widoczna na zdjęciach przejściówka RS232-FTDI. Po programowaniu arduino resetowało się w kółko, po chwili kombinowania i zastanawiania się o co właściwie mu chodzi, odpiąłem kabel RS232 od przejściówki przy sterowniku. Pomogło. Teraz leży zwinięty na piecu i czeka na wykorzystanie :)
3. Błędne odczyty temperatur
Tutaj diagnoza była w miarę prosta: albo źle podłączony czujnik (spalony poprzez zamianę biegunów), albo zbyt mały czas oczekiwania na przeliczenie temperatury, albo zbyt duża “waga” magistrali 1-wire. Niestety czujniki (zwłaszcza ten na piętrze i parterze) są znacznie oddalone od arduino, wobec czego należało zmniejszyć wartość rezystora mięczy linią +5V a szyną danych. Wartość dopasowałem “na oko” , z wykorzystaniem potencjometru wlutowanym równolegle do już istniejącego rezystora 4.7K i nastawionym na full, po czym kręciłem aż odczyt temperatury będzie prawidłowy. Następnie wylutowałem potencjometr, zmierzyłem nastawioną wartość, i wlutowałem odpowiedni rezystor. Jak można zauważyć, czasami odczyt nie jest prawidłowy i psuje porządek danych na wyświetlaczu, ale nie przeszkadza to w żaden sposób w działaniu, więc z tym nie walczę. Czujnik podpisany jako ZbCWU nie jest podłączony, stąd -0.0 na odczycie.
Rozwój
No cóż, to jeszcze nie koniec – pozostało podłączenie czujnika do bojlera CWU, a już mam plany jak w tym grzebać – przede wszystkim byłoby super gdyby udało mi się dodać port Ethernet i wypychać dane z czujników oraz stan przekaźników w sieć, tworząc wykresy :) Ciekawym (choć na razie mało realnym) pomysłem byłoby też dopięcie czujnika temperatury z zewnątrz, ale nawet nie mam pomysłu na algorytm wykorzystujący go podczas grzania :)
Tym niemniej sterownik działa jak należy już od dwóch tygodni, jest ciepło, a jeżeli się zawiesił, to watchdog go zrestartował jak należy, dzięki czemu dalej jest ciepło.
Ze swojej strony pozostaje mi tylko podziękować za poświęcony czas, oraz zachęcić Was do komentowania, głosowania i zgłaszania własnych pomysłów :)
Za głupi jestem by zrozumiec to przeczytałem i daje 5:D
Jeśli jest coś niezrozumiałego, pytajcie, jestem po to żeby wyjaśniać, prostować, tłumaczyć :>
Potrzebuje zbudować sterownik oparty na arduino potrzebuje wsparcia proszę o kontakt 501444022
Dobre… dobre… Fajnie się czytało.. Fajnie.. fajnie… Nic nie rozumiem.. ;) Mus dac 5 ;)
Kawał ładnego kodu. Kawał przydatnego programu dla kogoś z podobnym problemem.
Ładny sposób na oszczędzenie na jakimś gotowym rozwiązaniu.
Rewelka! Gratuluje pomysłu i zaangażowania! Takie tematy to powód, dla którego chcę się zapoznać z arduino. Na początek myślę o jakimś systemie do monitorowania temperatur i pracy urządzeń.
Mam piec węglowy na ekogroszek i tu dopiero jest pole do popisu, trzeba sterować wieloma urządzeniami, podtrzymywać ogień itp. polecam temat jak już nie będziesz miał więcej pomysłów na modyfikację przy swoim piecu :) Zewnętrzny czujnik temperatury jest ważny przy węglu, który wolno “startuje” i wolno “hamuje”, więc w obiegu cały czas krąży woda o temp. “podtrzymującej” temp. w pomieszczeniach, którą sterownik wylicza w tzw. krzywej grzewczej zależnej właśnie od temp. zewnętrznej.
Nie rozumiem połowy ,ale co tam, daję 5 :D
Dobry art, mógłbyś coś więcej napisać o histerezie? Daję 5
Pomyślałem o niej dopiero kiedy zauważyłem problem, najlepiej to wyjaśnić na przykładzie: piec ma grzać, jeżeli odczyt (a nie sama temperatura) temperatury będzie niższy od 19 stopni. Teoria jest fajna: temperatura spada poniżej 19 stopni, piec się odpala, grzeje chwilę, czujnik pokazuje powyżej 19, piec (i pompki) się wyłączają, ale ponieważ trochę ciepła jest jeszcze w grzejnikach, to temperatura w pomieszczeniu będzie jeszcze rosła (nieznacznie bo nieznacznie, ale zawsze coś)
Okazuje się jednak, że czujniki DS18B20 są bardzo czułe albo mają kiepską rozdzielczość, tak czy siak lubią skakać przy stanach granicznych – a to pokazuje 18.96, a to już następny odczyt daje 19.12, a kolejny np 18.87, co powoduje z kolei klikanie przekaźników i ogólną dezinformację pieca.
Tutaj wkracza histereza – dzięki niej arduino załącza piec troszkę później, grzeje do wyższej temperatury, i w momencie dogrzania do zadanego odczytu piec jest wyłączany na dłużej, nawet jeśli czujnik pomyli się o ćwierć stopnia.
Dobrym porównaniem jest “martwa strefa” (kto miał stary joystick wie do czego piję) – czas/odczyty w granicach których stan przekaźników, a więc i pieca nie jest zmieniany. który jest jakby przedłużeniem poprzedniego stanu.
Bez histerezy masz strasznie taktowanie pieca. Mój rc35 Buderusa zasadniczo grzeje wg. temp. z zewnątrz, uwzględnia temp. wewnętrzną tylko w celu określenia czy należy przerwać grzanie. Jest to opcja najbardziej optymalna pod kątem zużycia gazu. Ciągłe uruchamianie pieca kondensacyjnego oznacza start z dużą mocą, a co za tym idzie duże zużycie gazu.
Bardzo dobre. Ja bym jeszcze podłączył ethernet do ustawiania z zewnątrz. Moje zabawy z “inteligentną” kotłownią: http://techniczny.wordpress.com/2013/06/09/inteligentna-kotlownia-z-kotlem-na-ekogroszek/
Właśnie mam przed sobą moduł ethernet, ale myślałem przede wszystkim o logowaniu gdzieś temperatur (np do bazy danych albo do zabbixa) i rysowaniu wykresów. Tylko póki co nie ma czasu :(
Przydałaby się jeszcze jakaś klawiatura do wprowadzania ustawień. Robię podobne urządzonko – ma sterować piecem, termą elektryczną, kolektorem słonecznym z naprowadzaniem na słońce (według czasu), drzwi garażowe i innymi gadżetami domowymi.
“Szafa rozdzielcza (master)” – umieszczona w starej obudowie PC, dochodzą do niej wszystkie kable, czujniki itp.. Wyświetlacz z klawiaturą (do wprowadzania ustawień) połączony z “Szafą rozdzielczą (master)” przez I2C.
Nie ma to jak własne konstrukcje. Gdy kable poprowadzone wystarczy zmiana w kodzie i urządzonko działa według naszych oczekiwań. Według mnie bardzo dobre rozwiązanie pokazujące nowe horyzonty ;)
Bardzo fajny opis, na pewno mi pomoże w moim projekcie. Ja z kolei mam zamiar zmajstrować sterownie piecem gazowym. Czujniki temperatury do pomiaru, kontaktrony do sprawdzania czy okna zamknięte i jeszcze parę innych “bajerów”. Ostatnio urodziło się, żeby wszystkie stany temp wysyłać do RPi i robić wykresy. Jak uda mi się to ogarnąć to na pewno wrzucę tutaj opis ;)
Przeczytałem pobieżnie, nie mam centralnego pieca więc mnie nie za bardzo interesuje. Czy w końcu może ktoś mi wyjaśnić jak się wstawia talki ładne listingi. Pytałem w innym temacie ale się nic nie dowiedziałem oprucz zobaczenia printscreena który dalej nic mi nie mówi. Ogónie to pózniej przgląne dokłanie listing może coś ciekawego znajdę.
Z mojego lekkiego lenistwa, albo może i inwencji twórczej wolałbym zrobić to na Logo Siemensa albo Easy Eatona, które można już wyrwać za 200-300PLN na znanym portalu aukcyjnym, ale gratuluje chęci.
Mam kocioł gazowy ze sterownikiem na magistrali opentherm. Czy można wprządz w to arduino żeby zwiększyć funkcjonalość. Marzy mi się sterowanie temperaturą w każdym pomieszczeniu z osobna (za pomocą np. bezprzewodowo sterowanych zaworów przy grzejnikach) oraz podpicie do netu, żeby sobie monitorować status i sterować zdalnie.
ja ten sterownik dał bym już na górze lub na parterze nie schodził bym po to by go przzestawić
Witam!
Na jedno pytanie nie znalazłem odpowiedzi.
Jak zmierzyć temperaturę wody w danym miejscu w instalacji CO?
Czy są czujniki współpracujące z Arduino, które możemy wrzucić do rury?
Z góry dziękuję za odpowiedź!
przewody transmisji danych po Arduino trzeba poprowadzić w ekranowanych przewodach .
I właśnie się okazało, że coś takiego potrzebuje. Tylko ty sterujesz tam piecem gazowym a mi potrzeba sterować piecem węglowym z pompką obiegową i zaworami regulacyjnymi/odcinającymi/3d + bufor itd.
Lektura zatem na weekend jak znalazł. ;)