
Sterownik włącza/wyłącza do 8 urządzeń grzewczych i chłodniczych.
Stan wyjść zależy od:
- temperatury mierzonej w dowolnej liczbie punktów,
- wewnętrznego zegara czasu rzeczywistego,
- programu grzania i chłodzenia przechowywanego w pamięci EEPROM.
Architektura sterownika
Sterownik jest oparty na mikrokontrolerze AT89C2051. Układ ten ma 2 kB pamięci flash na program, ma też sprzętowy port UART. Programowo został zaimplementowany:
- master magistrali I²C
- master magistrali 1-wire - łącznie z algorytmem wyszukiwania slave'ów, weryfikacją CRC, ale bez zasilania pasożytniczego
- zegar czasu rzeczywistego - na przerwaniach (zamiast zewnętrznego chipa RTC)
Następujące układy peryferyjne są podłączone poprzez magistralę I²C:
- pamięć EEPROM 24C02 256 bajtów (adresy A2h, A3h)
- czujnik temperatury wewnętrznej TMP75 (adresy 90h, 91h)
Magistrala 1-wire obsługuje teoretycznie dowolną liczbę zewnętrznych czujników temperatury (DS18B20, DS18S20 i DS1820). Sterownik sam wykrywa wszystkie podłączone czujniki, jednak - ze względu na strukturę danych w pamięci EEPROM - program grzania/chłodzenia można przypisać co najwyżej do 255. Czujniki można podłączać i odłączać na gorąco (podczas pracy).
Sterownik nie obsługuje zasilania pasożytniczego, co oznacza, że oprócz linii 1-wire do czujników należy doprowadzić nie tylko masę, ale i napięcie zasilania (w sumie trzy przewody).
Sterownik podaje zasilanie na 1-wire wtedy, kiedy trzeba - czujniki nie są zasilane stale.
Hardware

(Ten sam schemat jest też załączony jako PDF.)
Sterownik został umieszczony w obudowie Z-3A. Jest to obudowa o wymiarach 110x90x69 mm, czyli ~3,5 raza mniejsza objętościowo od lansowanej KM-85.
W obudowie znajdują się 2 płytki: główna i zasilająca. Płytka główna to docięta na wymiar, jednostronna, wiercona płytka uniwersalna 70x90mm PI02. Na niej znajdują się następujące zaciski śrubowe zwrócone na zewnątrz obudowy:
- Potrójny zacisk z wyprowadzeniami N, L, L' (przekaźnik podłączony do P1.4 zwiera L' z L)
- 3 podwójne zaciski z wyprowadzeniami styków kolejnych przekaźników
- pin-header 1-wire (o wyprowadzeniach: +5V, 1-wire, GND) - wyprowadzony na zewnątrz na kostkę
- pin-header UART (o wyprowadzeniach: TxD, RxD, GND) - wyprowadzony na mini-jack
- gniazdo rozszerzeń (o wyprowadzeniach: +5V, GND, SDA, SCL) - do którego jest wetknięta płytka z czujnikiem temperatury wewnętrznej
Na płytce zasilającej jest umieszczony układ zasilacza niestabilizowanego (bezpiecznik, transformator, prostownik i kondensator). Jest umieszczona pod kątem prostym w stosunku do płytki głównej.





Na zdjęciach powyżej jest widoczna prototypowa płytka rozszerzeń z układami DS1629 (RTC + czujnik temp.) i 24C32 (EEPROM 16kB), zrobiona na bazie płytki prototypowej MAJSTAR DIP/S02. Zamiast niej, docelowo została użyta płytka z czujnikiem TMP75, zrobiona na bazie WOJART PDU-03.



Magistrala 1-wire
Czujniki zostały podłączone w topologii magistrali (szyny), co nie jest zalecane dla dłuższych sieci 1-wire, ale w przypadku sumarycznej długości rzędu kilkunastu metrów i przy 5 czujnikach zdało egzamin. Konieczne było jednak dołączenie drugiego rezystora 4,7k równolegle do R4, bo bez tego już przy 3 czujnikach zdarzały się błędy transmisji polegające na fałszywym odczycie stanu 0 (urządzenie wystawiające 0 "puściło" magistralę, ale ta nie zdążyła się naładować do 1 przed następnym slotem czasowym).
Firmware
Oprogramowanie sterownika zostało napisane w asemblerze. Zajmuje 1955 bajtów z 2048 dostępnych (95%).
Firmware składa się z następujących plików źródłowych:
- firmware.asm (1382 wiersze) - część główna, dostępna poniżej jako załącznik; jest tu prawie wszystko poza niskopoziomowymi funkcjami do obsługi 1-wire, I²C i liczenia CRC-8
- 1wire.asm (102 wiersze) - reset, odczyt i zapis bitu i bajtu
- crc8.asm (23 wiersze) - obliczanie sumy kontrolnej CRC-8 używanej do zabezpieczania transmisji na 1-wire
- i2c.asm (118 wiersze) - start, stop, odczyt i zapis bajtu, wysyłanie potwierdzenia (ACK i NAK)
Główny plik firmware.asm załączam poniżej; zachęcam do przeglądania i zgłaszania bugów

Fragmentów kodu można używać w celach edukacyjnych i na własne potrzeby, w pozostałych przypadkach wszelkie prawa autorskie pozostają zastrzeżone (PW).
Jeśli ktoś potrzebuje pozostałe pliki źródłowe, to chętnie je udostępnię na PW (pod warunkiem, że nie staną się przedmiotem plagiatu).
Załączam też plik wsadu (HEX). Liczę na komentarze dotyczące jego działania/niedziałania w razie, gdyby ktoś go użył.
Zegar czasu rzeczywistego
Mikrokontroler jest taktowany kwarcem 22 118 400 Hz. Częstotliwość zegara dla timera 0 wynosi 1/12 cz. kwarcu, czyli 1 843 200 Hz. Timer 0 działa w trybie 16-bitowym, zatem przepełnia się 225 razy co 8 sekund. Najłatwiej byłoby więc zliczać czas co 8 sekund.
Sterownik odlicza od 225 do 0 w przerwaniu timera 0. Jednak za każdym razem, gdy wartość jest podzielna przez 28, dodaje kolejną sekundę. Dzięki temu stan zegara RTC zwiększa się jakby co sekundę, tylko co ósma "sekunda" jest trochę dłuższa od pozostałych siedmiu.
Sterownik działa w cyklu tygodniowym i utrzymuje w pamięci numer dnia tygodnia, godzinę, minutę i sekundę (w kodzie BCD). Numer dnia tygodnia jest używany do znajdowania właściwego programu dobowego, a godzina i minuta do znajdowania właściwego przedziału czasu w tym programie dobowym.
Program grzania i chłodzenia
Program grzania i chłodzenia jest bardzo elastyczny - umożliwia określenie przedziału temperatury od -128°C do +127.99609375°C z krokiem 1/256 °C na dowolny przedział czasu w ciągu doby podany z dokładnością do 1 minuty. Każda pozycja programu zawiera przedział temperatury, czyli histereza może być w każdym przedziale inna (i jest nieograniczona). Takie programy dobowe są przypisane do poszczególnych czujników na każdy dzień tygodnia - sterownik działa w cyklu tygodniowym. Każdy czujnik ma przypisany zbiór przekaźników, którymi steruje.
Realny przykład zastosowania: jednoczesne sterowanie lodówką, piecem C.O. i elektrozaworami przy kaloryferach w pokojach, z wykorzystaniem czujników umieszczonych w lodówce i w każdym z pokojów. Program dla lodówki byłby niezmienny w czasie (zawsze np. od 3°C do 6,5°C), zaś dla każdego z pokojów - dopasowany do sposobu korzystania z nich (inny w sypialni, inny dla pokoju dziennego). Stopień skomplikowania programu jest ograniczony wielkością pamięci EEPROM.
Sterownik włącza dane urządzenie, jeśli z programu dla choćby jednego czujnika wynika potrzeba włączenia go. Dzięki temu poprawnie będzie działał system, w którym 2 czujniki w dwóch pokojach włączają ten sam piec C.O. + otwierają jeden właściwy sobie elektrozawór.
Struktura programu w pamięci EEPROM jest następująca:
- opis programu dla czujnika wewnętrznego
- 1 bajt - liczba czujników zewnętrznych (n)
- n razy powtórzone:
- 6 bajtów - 48-bitowy unikalny kod czujnika zewnętrznego 1-wire (środkowe 6 bajtów ID czujnika, bez pierwszego bajtu family code i ostatniego z CRC-8)
- opis programu dla czujnika zewnętrznego
Opis programu dla każdego czujnika ma 15 bajtów i jest to:
- 1 bajt - maska stanu przekaźników sterowanych tym czujnikiem (przekaźnik podłączony do portu P1.X jest włączany/wyłączany, jeśli odpowiadający mu bit X jest tutaj zapalony)
- 14 bajtów - 7 16-bitowych offsetów programów dobowych na każdy dzień tygodnia, od niedzieli do soboty. Offsety są zapisane w porządku MSB (najpierw starszy bajt)
Offsety programów dobowych są 16-bitowe, mimo że przy pamięci EEPROM 256-bajtowej wystarczyłyby 8-bitowe, ponieważ pierwotnie użyłem pamięci 24C32 o pojemności 16 kB i pozostawiłem możliwość łatwego powrotu do niej, gdyby 256 B przestało wystarczać.
Program dobowy ma zmienną długość - zależną od liczby przedziałów czasu w ciągu doby (n). Najpierw jest zapisanych n początków przedziałów czasu (2 bajty: godzina i minuta w kodzie BCD), po czym jest zapisany bajt kończący o wartości 255 (nie ma takiej godziny), po czym następuje n bloków po 4 bajty z zakresami pożądanych temperatur dla każdego przedziału czasu. Zakres temperatury składa się z dwóch 16-bitowych wartości stałoprzecinkowych w kodzie uzupełnieniowym do 2, z przecinkiem w połowie (na 8. bicie). Jeśli pierwsza wartość jest mniejsza od drugiej, to jest to program ogrzewania, w przeciwnym razie jest to program chłodzenia. Czyli struktura jest następująca:
- 1 bajt - godzina początku 1. przedziału czasu, w kodzie BCD
- 1 bajt - minuta początku 1. przedziału czasu, w kodzie BCD
- 1 bajt - godzina początku 2. przedziału czasu, w kodzie BCD
- 1 bajt - minuta początku 2. przedziału czasu, w kodzie BCD
- ...
- 1 bajt - godzina początku ostatniego przedziału czasu, w kodzie BCD
- 1 bajt - minuta początku ostatniego przedziału czasu, w kodzie BCD
- 1 bajt równy 255 - terminator
- 2 bajty - temperatura minimalna (dla ogrzewania) lub maksymalna (dla chłodzenia) w 1. przedziale czasu, zapisana w kodzie uzupełnieniowym do 2 z przecinkiem między bajtami (na 8. bicie)
- 2 bajty - temperatura maksymalna (dla ogrzewania) lub minimalna (dla chłodzenia) w 1. przedziale czasu, zapisana w kodzie uzupełnieniowym do 2 z przecinkiem między bajtami (na 8. bicie)
- 2 bajty - temperatura minimalna (dla ogrzewania) lub maksymalna (dla chłodzenia) w 2. przedziale czasu, zapisana w kodzie uzupełnieniowym do 2 z przecinkiem między bajtami (na 8. bicie)
- 2 bajty - temperatura maksymalna (dla ogrzewania) lub minimalna (dla chłodzenia) w 2. przedziale czasu, zapisana w kodzie uzupełnieniowym do 2 z przecinkiem między bajtami (na 8. bicie)
- ...
- 2 bajty - temperatura minimalna (dla ogrzewania) lub maksymalna (dla chłodzenia) w ostatnim przedziale czasu, zapisana w kodzie uzupełnieniowym do 2 z przecinkiem między bajtami (na 8. bicie)
- 2 bajty - temperatura maksymalna (dla ogrzewania) lub minimalna (dla chłodzenia) w ostatnim przedziale czasu, zapisana w kodzie uzupełnieniowym do 2 z przecinkiem między bajtami (na 8. bicie)
Przedziały czasu muszą być posortowane - początek następnego jest końcem poprzedniego.
Północ musi być między ostatnim a pierwszym (lub pierwszy musi zaczynać się o północy).
Rozważmy najprostszy możliwy program - chłodzenie samego sterownika wentylatorem (sztuczny przykład, bo sterownik nie wymaga aktywnego chłodzenia). Mamy tylko czujnik wewnętrzny i wentylator podłączony do przekaźnika przez port P1.4. Wentylator ma się włączać po przekroczeniu np. 55°C a wyłączać, gdy spadnie do 50°C.
Program jest identyczny na każdy dzień tygodnia i przez całą dobę taki sam. Będziemy mieli więc tylko jeden program dobowy. Będzie on pod offsetem 10h, bo 16 bajtów zajmuje opis programu dla czujnika zewnętrznego i informacja, że nie ma czujników zewnętrznych. Czyli zawartość EEPROM:
10 - bo sterujemy przekaźnikiem na porcie P1.4, 2 do potęgi 4 to 16, szesnastkowo 10
00 10 00 10 00 10 00 10 00 10 00 10 00 10 - 7 razy powtórzony offset 0010h
00 - liczba czujników zewnętrznych
Zapisaliśmy już 16 bajtów, czyli jesteśmy pod offsetem 10h - początek programu dobowego:
00 00 - północ
FF - terminator - mamy tylko jeden przedział czasu obejmujący całą dobę
37 00 32 00 - chłodzenie, zakres od 50°C do 55°C
(Oczywiście nic nie stoi na przeszkodzie, żeby ten program dobowy umieścić w dowolnym miejscu EEPROM, nie musi być zaraz w pierwszym wolnym miejscu.)
Cykl działania
Gdy nie przychodzą żadne żądania z portu UART do sterownika, budzi się on co 8 sekund i realizuje następujące zadania:
- jeśli ostatnio nie był podłączony czujnik wewnętrzny, sprawdź, czy został on podłączony i jeśli tak, skonfiguruj go do ciągłego pomiaru z maksymalną rozdzielczością
- skonfiguruj wszystkie czujniki zewnętrzne do pomiaru z najwyższą rozdzielczością i zleć im pomiar temperatury
- jeśli się udało, czekaj, aż wszystkie czujniki skończą pomiar
- jeśli czujnik wewnętrzny został prawidłowo skonfigurowany, odczytaj z niego temperaturę i wykonaj jego program (ewentualnie ustawiając wymuszenia przekaźników - na razie w RAM)
- jeśli pomiar na czujnikach zewnętrznych został przeprowadzony, wykryj po kolei wszystkie czujniki zewnętrzne, odczytaj z każdego temperaturę i wykonaj jego program (ewentualnie ustawiając wymuszenia przekaźników - na razie w RAM)
- zastosuj zgromadzone w RAM wymuszenia przekaźników do portu P1
Wymuszenia na przekaźnikach to 2 bajty z flagami - jeden do zsumowania logicznego (OR), drugi do maskowania (AND). W przypadku przeciwstawnych wymuszeń sterownik wybiera włączenie przekaźnika.
Podczas wykonywania powyższych czynności, w ramach jednego cyklu (co 8 sekund) sterownik wysyła na UART jedną linijkę tekstu w formacie CSV (dane oddzielone średnikiem). W linijce są następujące pola danych:
- dzień tygodnia (2 cyfry, od 0=niedziela do 6=sobota)
- godzina:minuta:sekunda - dziesiętnie (po 2 cyfry)
- licznik zegara (wartość odliczana od 225 do 0) - 2 cyfry szesnastkowe
- T=temperatura - dla wewnętrznego czujnika temperatury; temperatura jest liczbą rzeczywistą zapisaną dziesiętnie, w razie potrzeby z kropką dziesiętną i następującą po niej częścią ułamkową
- ID czujnika=temperatura - dla zewnętrznego czujnika temperatury, ID czujnika to 16 cyfr szesnastkowych stanowiących 64-bitowy identyfikator urządzenia na magistrali 1-wire
- ID urządzenia - 16 cyfr szesnastkowych stanowiących 64-bitowy identyfikator urządzenia na magistrali 1-wire (dla urządzeń innych niż czujniki temperatury)
- /, - lub \ - zaraz po ID czujnika=temperatura, jeśli w pamięci EEPROM znaleziono program grzania/chłodzenia dla danego ID czujnika; / oznacza włączenie skojarzonych przekaźników, \ - wyłączenie, a myślnik - brak wymuszenia, czyli pozostawienie poprzedniego stanu (temperatura mieści się wewnątrz żądanego przedziału, jesteśmy w pętli histerezy)
- MXX - maska flag sterowania automatycznego; XX to 2 cyfry szesnastkowe; zgaszone bity blokują sterowanie odpowiadającymi im przekaźnikami na podstawie programu ogrzewania i chłodzenia
- PP&AA|RR=NN - PP = poprzedni stan przekaźników, AA = maska wyłączająca przekaźniki, RR = flagi włączające przekaźniki, NN = wynikowy (bieżący) stan przekaźników; PP, AA, RR, NN to po 2 cyfry szesnastkowe
Przykładowy meldunek:
04;20:55:33;D0;103875C400080049=12.25;28971DA80000000F=17.5;MFE;01&FF|00=01
Możemy z niego wyczytać, że:
- jest czwartek, 20:55:33
- licznik zegara ma wartość D0h=208, czyli od obudzenia sterownika upłynęło 17 pełnych cykli timera, tj. 604-640 ms - tyle zajęło wykonanie pomiarów na wszystkich czujnikach
- nie ma czujnika wewnętrznego
- jest czujnik 103875C400080049 z temperaturą 12.25°C
- jest czujnik 28971DA80000000F z temperaturą 17.5°C
- w pamięci EEPROM nie ma programu dla żadnego z czujników (lub nie ma podłączonej pamięci EEPROM)
- maska sterowania automatycznego ma zablokowane wyjście P1.0 (w prototypowym sterowniku jest pod to wyprowadzenie podłączony klucz zasilania 1-wire zamiast kolejnego układu sterującego przekaźnikiem)
- stan przekaźników 01 nie zmienia się (nie włączamy ani nie wyłączamy żadnego przekaźnika)
Sterowanie przez UART
Sterownik jest czarną skrzynką, nie posiada żadnego przycisku ani wyświetlacza, ma jednak port UART, za pośrednictwem którego można go konfigurować i nadzorować jego działanie.
Sterownik pozostawiony sam sobie cyklicznie mierzy temperatury, odczytuje programy ogrzewania i chłodzenia z EEPROM i steruje przekaźnikami, wysyłając raporty na UART zgodnie z opisem w poprzednim punkcie. Można go jednak z tego stanu "wybić" wysyłając znak na UART. Wówczas sterownik wstrzymuje normalne działanie i realizuje odpowiednią funkcję. Brak danych przychodzących z UART przez 2 cykle (8-sekundowe) wznawia standardowy tryb działania.
Poniżej znajduje się opis dostępnych komend UART.
Komenda | Opis | Odpowiedź sterownika |
I | START transmisji na magistrali I²C | @ jeśli sukces, ! w razie błędu |
S | STOP transmisji na magistrali I²C | @ |
A | Wysyła ACK (ACKnowledge) na magistralę I²C | @ |
N | Wysyła NAK (Negative AcKnowledge) na magistralę I²C | @ |
WXX | Wysyła bajt XX (podany jako 2 cyfry szesnastkowe) na magistralę I²C | @ jeśli sukces, ! w razie błędu |
R | Czyta bajt z magistrali I²C | odczytany bajt (2 cyfry szesnastkowe) |
i | RESET na magistrali 1-wire | @ jeśli sukces, ! w razie błędu |
wXX | Wysyła bajt XX (podany jako 2 cyfry szesnastkowe) na magistralę 1-wire | @ jeśli sukces, ! jeśli podano zły argument |
r | Czyta bajt z magistrali 1-wire | odczytany bajt (2 cyfry szesnastkowe) lub ! w razie błędu |
MXX | Ustawia flagi ręcznego sterowania przekaźnikami. Zgaszone bity blokują sterowanie odpowiadającymi im przekaźnikami na podstawie programu ogrzewania i chłodzenia. | @ jeśli sukces, ! jeśli podano zły argument |
&XX | Natychmiast wyłącza przekaźniki, którym odpowiadają zgaszone bity w podanym bajcie. Nie wpływa na automatyczne sterowanie, które może za chwilę przywrócić stan wynikający z programu ogrzewania i chłodzenia. | @ jeśli sukces, ! jeśli podano zły argument |
|XX | Natychmiast włącza przekaźniki, którym odpowiadają zapalone bity w podanym bajcie. Nie wpływa na automatyczne sterowanie, które może za chwilę przywrócić stan wynikający z programu ogrzewania i chłodzenia. | @ jeśli sukces, ! jeśli podano zły argument |
dXX | Ustawia zegar RTC - dzień tygodnia (00-06). | @ jeśli sukces, ! jeśli podano zły argument |
hXX | Ustawia zegar RTC - godzinę (00-23). | @ jeśli sukces, ! jeśli podano zły argument |
mXX | Ustawia zegar RTC - minuty (00-59). | @ jeśli sukces, ! jeśli podano zły argument |
sXX | Ustawia zegar RTC - sekundy (00-59). | @ jeśli sukces, ! jeśli podano zły argument |
! | Natychmiast przywraca normalny tryb działania sterownika. | <LF> |
bXX | Czyta bajt z pamięci RAM mikrokontrolera spod podanego adresu XX (2 cyfry szesnastkowe). | odczytany bajt (2 cyfry szesnastkowe) lub ! w razie błędu |
Np. aby zapisać do EEPROM przykładowy najprostszy program chłodzenia (podany powyżej), czyli
10 00 10 00 10 00 10 00 10 00 10 00 10 00 10 00 00 00 FF 37 00 32 00
wysyłamy na UART rozkazy zapisu pod adres pamięci EEPROM 24C02, czyli A2h, zgodnie ze specyfikacją tego układu - wierszami po 8 bajtów:
IWA2W00W10W00W10W00W10W00W10W00S
IWA2W08W10W00W10W00W10W00W10W00S
IWA2W10W00W00WFFW37W00W32W00S
pilnując, aby po każdej komendzie dostać w odpowiedzi @, i aby dać EEPROM-owi czas na zapisanie wiersza (5 ms).
Nadzór nad sterownikiem
Opisane powyżej funkcje dostępne przez UART nie służą do bezpośredniego używania na codzień, ale pozwalają stworzyć drugie urządzenie, z przyciskami i wyświetlaczem, które pełniłoby rolę interfejsu użytkownika. Można też wykorzystać w tym celu komputer PC, co otwiera ogromne możliwości. Np.:
- automatyczne zbieranie wyników pomiarów temperatur i włączeń/wyłączeń sterowanych urządzeń; późniejsza obróbka i analiza tych danych pozwala na ciekawe obserwacje dotyczące np. strat ciepła w mieszkaniu, oszczędności wynikające z okresowego wyłączania ogrzewania, czasu osiągania zadanej temperatury itd.,
- usługa sieciowa dostępna w domowej sieci Wi-Fi, pozwalająca sprawdzać stan czujników i urządzeń i zmieniać program ogrzewania i chłodzenia np. na smartfonie czy tablecie.
I nie musi to być żarłoczny, typowy PC, wystarczy tani, poleasingowy cienki klient z Linuxem, pobierający kilka watów i dodatkowo pełniący wiele innych funkcji domowego serwera.
Na takim komputerze pracuje program nadzorca termostatu, który poprzez port UART sterownika, z wykorzystaniem opisanego wyżej protokołu:
- aktualizuje program ogrzewania i chłodzenia w pamięci EEPROM sterownika
- monitoruje zegar czasu rzeczywistego sterownika i koryguje go wg wskazań zegara PC (a ten jest z kolei synchronizowany z zewnętrznym serwerem czasu przez protokół NTP)
- odczytuje raportowane przez sterownik temperatury wszystkich podłączonych czujników temperatury oraz zmiany stanu sterowanych urządzeń
- wyniki pomiaru temperatury uśrednia i co jakiś czas wysyła na zewnętrzny serwer, gdzie są zapisywane w bazie danych (zmiany stanu urządzeń - również)
Przykładowy wykres, wygenerowany jako SVG przez skrypt napisany w JavaScript na podstawie danych otrzymanych z niniejszego sterownika i zgromadzonych w bazie danych:
Przykładowy wykres (SVG)
Załączniki
- termostat-schemat.pdf - schemat sterownika, ten sam, co w treści powyżej w PNG
- termostat-schemat.zip - schemat sterownika (również ten sam) w Eagle
- termostat-firmware.asm.txt - główny plik kodu źródłowego sterownika, do wykorzystania w celach edukacyjnych i na własne potrzeby (mile widziane zgłaszanie bugów
)
- termostat-firmware.hex.txt - wsad do mikrokontrolera, do wykorzystania na własne potrzeby
Cool? Ranking DIY