Dzisiaj uruchamiamy moduł MCP9808, czyli cyfrowy termometr działający w zakresie od -40°C do 125°C przy typowej dokładności 0,25°C, zasilany napięciem od 2.7V do 5.5V. Najpierw zaimplementujemy jego sterownik w oparciu o gotowe funkcje dla komunikacji I2C by móc odczytywać z niego pomiary z mikrokontrolera, pooglądamy tą komunikacje na analizatorze logicznym, a potem uruchomimy też system alarmu/przerwań, który sprawia, że MCP9808 przy przekroczeniu danej temperatury wystawia określony przez nas stan na pin Alert, co pozwala nam użyć po stronie MCU przerwań z GPIO, a może i nawet głębokiego snu (wtedy MCP9808 wybudza MCU).
Temat powstał przy współpracy z kolegą z Serbii, @DeDaMrAz . Zdjęcia pochodzą od kolegi, sam fizycznie tego modułu nie posiadam.
Zakup MCP9808
Gotowy moduł MCP9808 można kupić w wielu sklepach internetowych. W Chinach znalazłem go za około 20 zł z darmową przesyłką:
W naszym kraju jest praktycznie za tyle samo, ale oczywiście z szybszą wysyłką:
Moduł jest malutki i ma wszystko co potrzebne wyprowadzone na goldpiny. SDA i SCL to piny od I2C, a Alert to alarm, do przerwania. A0, A1 i A2 pozwala nam wybrać jego adres.
Co oferuje MCP9808
Producenci nie zawsze opisują szczegółowo ten moduł, ale to nie problem, w razie czego mamy dostępną w sieci notę katalogową:
Oprócz podstawowych parametrów dostępnych na pierwszej stronie noty katalogowej warto jest też poznać budowę rejestrów (pamięci) tego układu. To z nich będziemy korzystać przy odczycie temperatury i konfigurowaniu alarmów.
Rejestry są dwubajtowe, choć adresowane są kolejnymi liczbami, począwszy od 0x01. Mamy tu kolejno:
- CONFIG - konfiguracyjny, tu możemy ustawić tryb alarmu
- Tupper - górna granica temperatury dla alarmu z danego zakresu
- Tlower - dolna granica temperatury dla alarmu z danego zakresu
- Tcrit - granica temperatury dla alarmu po przekroczeniu danej wartości
- Ta - bieżąca temperatura zmierzona przez układ (ambient)
- ManufacturerID - identyfikator producenta
- ID urządzenia
- Rozdzielczość
Do rejestrów można wpisywać wartości, jak i można je z nich odczytywać.
Wybór adresu MCP9808
MCP9808 korzysta z protokołu I2C, co oznacza, że możemy podłączyć wiele tego typu urządzeń na jednej magistrali. Adres urządzenia możemy w pewnym zakresie zmieniać, a dokładniej możemy przestawiać trzy z jego bitów. Stan niski na A0 oznacza zgaszony bit A0, a wysoki oznacza zapalony. Poniższy fragment noty katalogowej pokazuje, jak określić właśnie ten adres:
Co ciekawe, są dwie wersje MCP9808, mają różne adresacje. To może zgubić początkujących. W takich sytuacjach warto polecić skaner I2C, choćby na Arduino. To rozwieje wątpliwości:
Kod: C / C++
Źródło: https://playground.arduino.cc/Main/I2cScanner/
Tutaj adres I2C jest 7-bitowy. To dlatego, że jak rozpoczynamy transakcje I2C, to najmłodszy bit nie pochodzi z adresu, tylko stanowi specjalny bit RW, który określa to, czy odczytujemy z urządzenia czy do niego wysyłamy. Z tego powodu, jak implementujemy I2C, to adres urządzenia jest zasadniczo przesunięty o jeden bit a najmłodszym jego bitem jest właśnie te RW.
Odczyt temperatury MCP9808
Odczyt temperatury z MCP9808 nie jest taki prosty jak w przypadku np. mojego ulubionego TC74, gdzie wystarczy odebrać jeden bajt z magistrali. Tutaj należy wykonać odczyt dwóch bajtów z danego rejestru. Najpierw wysyłamy adres tego rejestru. Przypominam dostępne adresy:
W oparciu o to przygotujmy funkcję odczytującą dwa bajty z danego rejestru MCP9808.
Zatem najpierw musimy wykonać I2C start, wysłać adres urządzenia (najmłodszy bit musi być bitem write, czyli zerowym, kolejne 7 to adres I2C urządzenia), potem adres rejestru, a potem wykonać I2C stop. U mnie, w moim środowisku, wygląda to tak:
Kod: C / C++
Potem musimy ponownie wykonać start, tym razem z bitem read (stan wysoki), odczytać dwa bajty, i wykonać znów stop.
Kod: C / C++
Potem składamy oba bajty w jeden typ 16 bitowy bez znaku i zwracamy rezultat.
Tylko, że temperaturę tutaj chcemy mieć w formacie zmiennoprzecinkowym, i jest ona dodatkowo zakodowana.
Powyższa tabelka przedstawia rozkład poszczególnych bitów w rejestrach. Mamy tu kilka temperatur, też te od alarmu, ale na razie skupmy się na samym formacie temperatury.
Patrzymy na rejestr 0x02.
Pierwsze trzy najstarsze bity są zawsze zerowe, je ignorujemy.
Potem mamy bit znaku, SIGN.
Potem są już bity temperatury, przy czym, dwa najmłodsze bity też są zawsze zerowe, oraz bit trzeci i czwarty odpowiadają za wartość po przecinku.
Co nam to mówi w praktyce?
- musimy sprawdzać ręcznie ten bit znaku w kodzie i sztucznie zamieniać na wartość ujemną
- musimy odczytaną wartość całkowitą przesunąć, a właściwie to przemnożyć przez 1 / 2^4 (tyle bitów jest po przecinku tam), a 1/2^4 to 0.0625.
Wpisujemy to w kod:
Kod: C / C++
Powyższy kod zawiera jeszcze operacje bitową AND na odczytanej wartości by "wyciąć" z niej zbędne bity, bit znaku musi być przez nas tutaj ignorowany, nie możemy go potraktować jak resztę bitów.
Teraz wystarczy użyć już przygotowanej funkcji by odczytać temperaturę z rejestru o adresie 0x05:
Kod: C / C++
Powyższy kod przetestowaliśmy wraz z kolegą, użyliśmy do tego naszej płytki z BK7231N i temperatura została poprawnie odczytana. Dodatkowo do układu podłączyliśmy analizator logiczny Sigrok:
Oto przebieg transakcji I2C, najpierw z bitem W, wysyłamy adres rejestru:
Potem z bitem R, odczyt temperatury (16 bitów):
Alarm - zakres temperatury
MC9808 można skonfigurować do pracy w trybie alarmu. Ustawiamy dany zakres temperatury, a potem MCP9808 sam wystawi na pin ALERT stan wysoki gdy zanotuje temperaturę poza tym zakres (bądź w tym zakresie; jest to konfigurowalne).
Jest to wyjątkowo przydatne, bo pozwala to podłączyć się do pinu mikrokontrolera z przerwaniem i odebrać te przerwanie jak tylko temperatura wyjdzie poza zakres. Jest to wygodniejsze i wydajniejsze niż np. badanie temperatury "na piechotę", w pętli, np. co 250ms i "ręczne" porównywanie jej z zadanym przedziałem w kodzie.
Dodatkowo umożliwia to nam wprowadzić nasz MCU w tryb głębokiego snu (testowałem to z kolegą Serbii na BK7231), i wtedy MCU może być wybudzany przez MCP9808 gdy temperatura wyjdzie poza oczekiwany zakres.
Zakres temperatur ustawiamy poprzez wpisanie ich do poszczególnych rejestrów MCP9808.
Dodatkowo MCP9808 pozwala ustawić prostą histerezę alarmu, tzn. dopuszczalny zakres różnicy między wartościami granicznymi alarmu a wartością bieżącą. Lepiej powinien wyjaśnić to rysunek:
Powyższy rysunek obrazuje wszystkie dostępne tryby alarmu MCP9808. Dostępny jest zarówno tryb alarmu przy wyjściu poza zakres (Tupper i Tlower), jak i tryb alarmu przy przekroczeniu danej wartości (Tcrit). Dodatkowo widzimy tutaj podział na tryb komparatora (wartość na ALERT odzwierciedla przynależność temperatury do oczekiwanego zakresu) oraz tryb przerwania (wartość na Alert tylko chwilowo się pojawia gdy nastąpi zmiana).
Dodatkowo rysunek pokazuje działanie histerezy, zaznaczony jest tam offset tworzony przez ustawienie Thyst, on opóźnia ponowne wykrycie zmiany.
Jeszcze należy doprecyzować, gdzie ustawiamy tryb komparatora bądź przerwania, gdzie ustawiamy poziom na ALERT (wysoki bądź niski w momencie alarmu) i gdzie ustawiamy histerezę:
Czyli potrzebujemy:
- ustawić rejestr z temperaturą Tupper
- ustawić rejestr z temperaturą Tlower
- ustawić rejestr z temperaturą Tcrit (tak, jednak też trzeba, szczegóły w nocie)
- ustawić rejestr konfiguracyjny
Na początek funkcje pomocnicze, funkcja konwertująca liczbę zmiennoprzecinkową na format z MCP9808:
Kod: C / C++
Zasadniczo odwraca ona to co omawialiśmy przy odczycie temperatury.
Teraz funkcje wpisujące Tupper i Tlower do ich rejestrów:
Kod: C / C++
Trochę mnie to zaskoczyło, ale lektura noty pokazała, że nawet do pracy w zakresie należy ustawić Tcritical, więc też na to dodam funkcję:
Kod: C / C++
Teraz dostęp dla rejestru konfiguracyjnego - dla wygody go odczytamy, zmodyfikujemy a potem zapiszemy.
Kod: C / C++
I teraz najważniejsze. Trzeba wszystkie wspomniane tutaj ustawienia zebrać w jedno miejsce i wpisać w kod.
Kod: C / C++
Operacje bitowe raczej powinniśmy znać na tym etapie, ale wygody wyjaśnię tu poszczególne kroki:
- cfg &= ~0x0001; - wykonanie AND z negacją zapalonego najmłodszego bitu po prostu w rezultacie zgasi nam ten bit, czyli tu zerujemy Alert Mod (ustawiamy tryb komparatora):
- cfg &= ~0x0002; - w zależności od oczekiwanych ustawień, zapalamy lub gasimy bit polaryzacji alarmu:
- cfg &= ~0x0004; - czyścimy kolejny bit, który włącza nam pracę przy użyciu Tupper i Tlower:
- cfg |= 0x0008; - ostatecznie, poprzez przypisanie rezultatu bitowego z wynikiem OR, ustawiamy na stan wysoki bit odpowiedzialny za włączenie alarmu:
Teraz jeszcze potrzeba jakiś układ testowy. W sumie starczy dioda LED wraz z rezystorem by zbadać, czy stan na wyjściu jest wysoki czy niski.
Pora oddać głos mojemu testerowi. Czy tak zaprogramowany MCP9808 zgłasza ALERT po zmianie temperatury poza zakres?
Działa. Przetestowaliśmy też odwrócenie poziomów alarmów i również ruszyło bez problemów.
Alarm - próg temperatury
Skoro mamy już kod na zakres temperatury, to bardzo łatwo jest zrobić alarm tylko przy przekroczeniu danego progu. Sprawdzamy w nocie katalogowej co musimy zmienić:
Też nie musimy już w ogóle wysyłać Tupper i Tlower do MCP.
Gotowy kod:
Kod: C / C++
Dodatek - pomocnicze makra
Na koniec jeszcze warto dodać coś, z czego ja tutaj nie korzystałem, ale może się to początkującym przydać. Za pomocą preprocesora możemy utworzyć wygodne makra w które ubierzemy nasze operacje bitowe i zwiększymy czytelność kodu. Oto moja propozycja:
Kod: C / C++
Chyba nie trzeba wyjaśniać co wykonuje które z nich, prawda?
Tu tylko trzeba pamięć, że podane makra przyjmują za argument indeks bitu, a nie samą wartość bitowej flagi...
Biblioteka dla Arduino
Arduino jest tak popularne, że praktycznie do każdego popularniejszego modułu ma już bibliotekę na Githubie. Tak tez jest i tutaj. Poniżej linkuję bibliotekę, którą posiłkowałem się przy opracowywaniu tematu:
https://github.com/RobTillaart/MCP9808_RT
Jest to biblioteka autorstwa Rob Tillaart i jest dostępna na licencji MIT. Przykłady Arduino są też tam zawarte.
Podsumowanie
W miarę spodobał mi się ten moduł. Możliwość ustawienia alarmu pozwala wykorzystać go z urządzeniami zasilanymi bateryjnie, gdzie główny MCU (np. tez moduł WiFi) możemy wprowadzić w tryb głębokiego snu a wybudzać go będzie tylko zmiana temperatury poza zadany zakres. Jest to znacznie lepsze podejście niż badanie temperatury "na piechotę" w pętli czy tam w timerze. A nawet jeśli nie chcemy głębokiego snu, to i tak funkcja alert w połączeniu z przerwaniem może ułatwić nam kod i oszczędzić cenne cykle zegara, które można przeznaczyć na coś ważniejszego.
Dokładność tego czujnika by mogła być lepsza, ale raczej w zastosowaniach które ja dla niego widzę ważniejsze jednak jest to przerwanie alarmowe, dokładność tutaj to sprawa drugorzędna.
Być może wkrótce zrealizuję na nim jakieś DIY i też umieszczę na forum.
To z mojej strony tyle, jeszcze tylko chciałbym podziękować @DeDaMrAz za asystę przy analizie, uruchomieniu i testowaniu tego czujnika.
A teraz pytanie do Was - czy zainteresował Was ten MCP9808, a może ktoś już realizował na nim projekt?
Fajne? Ranking DIY Pomogłem? Kup mi kawę.
