Kurs Arduino – czujnik BME280
W czwartym odcinku kursu wskoczymy w głęboką wodę: wykorzystamy nowoczesny czujnik BME280, który mierzy trzy ważne wielkości: ciśnienie, wilgotność i temperaturę. Czujnik ten ma interfejs I2C (I2C), który jest w pełni kompatybilny z TWI (Two Wire Interface) oraz SMBus. Poznamy i wykorzystamy ten interfejs.
Wykorzystujemy gotowy maleńki chiński moduł, pokazany na fotografii 1.
Dołączenie go do płytki Arduino jest banalnie proste – wystarczą cztery kabelki, jak na fotografii 2.
Czujnik ten ma interfejs I2C (I2C), który jest w pełni kompatybilny z TWI (Two Wire Interface) oraz SMBus. Procesor ATmega328P, a więc i Arduino Uno, ma wbudowany sprzętowy interfejs TWI (I2C). Linia SDA to nóżka A4 Arduino, linia SCL to nóżka A5, które są też wyprowadzone na dodatkowe piny w pobliżu pinu AREF jak pokazuje rysunek 3.
PW chwili pisania artykułu potrzebny moduł czujnika można kupić w kraju za 20…30zł plus koszty przesyłki, albo ściągnąć z Chin, gdzie kosztuje około 3 dolarów (ok. 10zł) z darmową przesyłką (3…4 tygodnie czekania).
Tytułem wyjaśnienia warto dodać, że oprócz „potrójnego” czujnika BME280, dostępne są też pokrewne „podwójne”, oznaczone BMP, które nie mierzą wilgotności, a tylko ciśnienie i temperaturę. Wersja BMP280 jest zdecydowanie bardziej dokładna niż wcześniejsza dość popularna BMP180. Zasady wykorzystania „podwójnych” BMP280/180 są bardzo podobne jak omawianego „potrójnego” BME280.
I jeszcze jeden ważny szczegół: same czujniki BMP i BME, w tym BME280, nie mogą być zasilane napięciem wyższym niż 3,6V. My bez obaw zasililiśmy moduł z fotografii 1 i 2 napięciem 5V gdyż oprócz układu BME280, na drugiej stronie płytki (fotografia 4) zawiera on stabilizator 3,3V oraz układ translacji poziomów 5V/3,3V.
Rysunek 5 pokazuje schemat wykorzystanego modułu BME280. W dwukierunkowym translatorze poziomów (który jest wyczerpująco opisany w artykule DR090) pracują dwa tranzystory MOSFET typu BSS138. Są dostępne wersje w podwójnej obudowie sześcionóżkowej i taka wersja występuje w wykorzystanym module. Warto wiedzieć, że na rynku są też inne moduły z czujnikiem BME280 i pokrewnymi.
Fotografia 6 pokazuje wersję produkowaną przez Adafruit (w kraju dostępna za około 100zł), gdzie schemat jest praktycznie taki sam, ale zastosowano dwa oddzielne MOSFET-y. Na rynku są też inne moduły z kostkami BME280 i BMP180/280, które nie zawierają stabilizatora i translatora poziomów.
Przykład z fotografii 7 to „podwójny” moduł z BMP280. Takie moduły, zawierające „goły czujnik”, nie mogą pracować w obwodach zasilanych napięciem 5V! Przy ich współpracy z Arduino Uno trzeba wykorzystać dostępne na płytce zasilanie 3,3V i potrzebny jest translator poziomów.
W każdym przypadku aby w Arduino wykorzystać interfejs TWI (I2C), potrzebna jest też biblioteka, np. zawarta w pakiecie Arduino IDE biblioteka o niefortunnej nazwie Wire (niefortunnej, bowiem bardziej kojarzy się z interfejsem 1-Wire, niż z I2C i z TWI – Two Wire Interface ).
W naszym przypadku nie musimy mieć jakiejkolwiek wiedzy o interfejsie I2C, ani o drobnych różnicach między I2C oraz TWI i SMBus/PMBus; nie trzeba też niczego instalować. Trzeba natomiast zainstalować dodatkową bibliotekę, która obsłuży „podtrójny” czujnik BME280. Nie musimy szukać takiej biblioteki „na piechotę”. W tym celu w pakiecie Arduino możemy wybrać: Szkic – Dołącz bibliotekę – Zarządzaj bibliotekami (Sketch – Include Library – Manage Libraries) według rysunku 8.
W okienko wyszukiwania wpisujemy BME280 i, jak pokazuje rysunek 9, po chwili mamy do dyspozycji sześć różnych bibliotek.
Ja na początek zainstalowałem wskazaną czerwonymi strzałkami bibliotekę Tylera Glenna, ponieważ realizuje ona dodatkowo interesujące mnie obliczenia „środowiskowe” (m.in. punktu rosy i wilgotności bezwzględnej). Po kliknięciu „More Info” instalujemy domyślną, najnowszą dostępną tam wersję (2.3.0).
Od tej chwili w pakiecie Arduino dostępna jest nie tylko sama biblioteka, ale też gotowe przykładowe szkice – programy. Znajdziemy je wybierając Plik – Przykłady (File – Examples) i po przewinięciu listy, na samym dole: BME280, gdzie mamy szereg przykładów. Wybieramy gotowy szkic BME_280_I2C_Test (rysunek 10).
Otwieramy w komputerze monitor szeregowy (Ctrl+Shift+M) i zgodnie z ustawieniami w szkicu, zmieniamy prędkość łącza Serial z 9600 na 115200 – rysunek 11.
Kompilujemy program i notujemy, że program zajął 34%, czyli jedną trzecią pamięci FLASH i 32% pamięci RAM – rysunek 12.
Gdy wgramy program do procesora, na konsoli monitora zobaczymy wyniki pomiaru jak na rysunku 13.
Po chuchnięciu na czujnik temperatura i wilgotność zdecydowanie rosną, jak pokazuje rysunek 14, co potwierdza, że czujnik jest sprawny.
REWELACJA!
Dosłownie w ciągu kilku minut zrealizowaliśmy stację meteo!
Zainstalowana biblioteka BME280 oferuje też dodatkowe możliwości.
Wróć do przykładów z rysunku 10 i otwórz gotowy program – szkic Environment_Calculations.ino. Gdy go uruchomisz, rozszerz okno monitora, bo wypisuje on wiele dalszych parametrów wyliczonych na podstawie dokonanych pomiarów, jak pokazuje rysunek 15.
I tak oprócz temperatury, wilgotności względnej i ciśnienia, w oknie monitora mamy wysokość (Altitude), wyliczoną na postawie aktualnego ciśnienia, Dew Point, czyli tak zwany punkt rosy oraz ciśnienie przeliczone na wartość na poziomie morza.
Wyliczenia punktu rosy wyrażone w stopniach Celsjusza są miarodajne i absolutne (w sumie jest to parametr bardziej wartościowy i przydatny w praktyce, niż podawana w procentach wartość wilgotności względnej: % RH). Natomiast obliczenia wysokości i przeliczonego ciśnienia są przykładowe i należałoby je dostosować do konkretnych warunków, zmieniając w programie pewne parametry. W każdym razie rysunek 15 udowadnia, że informacje z czujnika BME280 można wykorzystać w różny sposób, także do budowy wysokościomierza (altimetru), co może być przydatne nie tylko w modelarstwie lotniczym, ale też na przykład… w grach. Z uwagi na ogromną rozdzielczość pomiaru ciśnienia, można wykrywać zmiany wysokości nawet rzędu centymetrów! Wtedy trzeba zastosować uśrednianie i filtrowanie pomiarów. Te szczegóły wykraczają jednak poza ramy tego odcinka kursu.
Zachęcam Cię do przeanalizowania kodu w tych dwóch wykorzystanych gotowych plikach .ino. Zasadniczo nie jest on trudny do analizy, ale zastosowano specyficzny sposób wyprowadzania danych przez łącze szeregowe do konsoli komputera. Kwestiami tymi będziemy zajmować się później. W programach występują też inne zagadkowe szczegóły (choćby obecność w zmiennych float „wartości” NAN, nie reprezentujących wartości liczbowych), które mogą namącić w głowie. A na razie spróbujmy napisać własny program prościutkiej stacji meteo, żeby opanować podstawowe sposoby korzystania z czujnika BME280.
Złóż układ według rysunku 16. Do zestawu z poprzedniego odcinka z wyświetlaczem znakowym LCD dodajemy czujnik BME280 (usuwając diodę, która była wcześniej czujnikiem temperatury). Zasilanie modułu BME280 bierzemy z pinu IOREF, gdzie w płytce Arduino Uno występuje napięcie zasilania +5V.
Program naszej stacji meteo (dostępny tutaj = ZIP A04) znajdziesz na listingu 1 (A0401).
//BME280 – czujnik ciśnienia,temperatury i wilgotności względnej
// Wykorzystamy biblioteki: BME280I2C, Wire oraz LiquidCrystal
// dołączamy biblioteki – klasy:
#include <BME280I2C.h> //obsługa BME przez łącze I2C
#include <Wire.h> // obsługa TWI = I2C
#include <LiquidCrystal.h> //obsługa wyświetlacza LCD
//tworzymy i konfigurujemy obiekt wyświetlacz o nazwie: „lcd”
LiquidCrystal lcd(2, 3, 4, 5, 6, 7);
//tworzymy obiekt o nazwie „czujnik” z domyślnymi parametrami
BME280I2C czujnik; // Default: forced mode, standbyTime=1000ms
//Oversampling=pressure×1,temperature×1,humidity×1, filter off
float tmp, wilg, cis; //trzy zmienne globalne typu float:
void setup() { //jednorazowo incjalizujemy obiekty:
lcd.begin(16, 2); // wyświelacz
Wire.begin(); // łącze TWI = I2C
//czujnik – i czekamy, aż czujnik będzie prawidłowo podłaczony:
while (!czujnik.begin()) //jeśli brak kontaktu z czujnikiem:
{lcd.clear(); lcd.print(„brak kontaktu”); lcd.setCursor(0,1);
lcd.print(„z sensorem”); delay(2000); }
lcd.clear(); //czyścimy wyświetlacz i sprawdzamy czujnik:
switch (czujnik.chipModel()) {
case BME280::ChipModel_BME280:
lcd.print(„Sukces!”); lcd.setCursor(0, 1);
lcd.print(„Jest czujnik BME280! „); break;
case BME280::ChipModel_BMP280:
lcd.print(„znalazłem BMP280”); lcd.setCursor(0, 1);
lcd.print(„Bez pomiaru wilgotnosci”); break;
default:
lcd.println(„Blad! Nieznany czujnik!”); } // koniec switch
delay(5000); } // koniec setup()
void loop() { //w petli co około 1 sekundę:
// odczytujemy zmierzone wartości do zmiennych:
//albo ustawiamy jednostki temperatury i ciśnienia
// BME280::TempUnit tempUnit(BME280::TempUnit_Celsius);
// BME280::PresUnit presUnit(BME280::PresUnit_hPa);
// czujnik.read(cis, tmp, wilg, tempUnit, presUnit);
//albo „idziemy na skróty”:
czujnik.read(cis, tmp, wilg, 0, 1); //cisnienie w hPa
// czujnik.read(cis, tmp, wilg, 0, 0); // //cisnienie w Pa
// czujnik.read(cis, tmp, wilg); // //cisnienie w Pa
// albo „czytamy pojedynczo”:
// tmp = czujnik.temp(0);
// cis = czujnik.pres(1);
// wilg = czujnik.hum();
//i wyświetlamy wyniki na wyświetlaczu LCD:
lcd.clear(); lcd.print(„T=”); lcd.print(tmp, 1);
lcd.write(223); lcd.print(’C’); //symbol stopnia
lcd.print(” RH=”); lcd.print(wilg, 0); lcd.print(’%’);
lcd.setCursor(0, 1); //w dolnej linii:
lcd.print(„Cisnienie”); lcd.print(cis, 0); lcd.print(„hPa”);
delay(1000); } // koniec pętli
Na początku masz dyrektywy dla kompilatora, dołączające biblioteki: BME280I2C (czujnik BME280 z komunikacja przez I2C), Wire oraz LiquidCrystal. Następnie tworzymy i od razu konfigurujemy dwa obiekty. Jeden (lcd) to stary znajomy wyświetlacz LCD, podłączony dokładnie tak, jak w poprzednim odcinku. Drugi obiekt to reprezentacja w programie naszego modułu pomiarowego. Obiekt ten utworzy prościutka instrukcja:
BME280I2C czujnik;
BME280I2C to nazwa biblioteki (klasy) i na podstawie tej biblioteki zostanie stworzona „programowa reprezentacja” modułu BME280, którą w naszym programie – szkicu nazwałem swojsko: czujnik. Celowo w wielu miejscach będę stosował polskie nazwy (ale bez „polskich” liter), by odróżnić to, co wynika z wymagań programu, a co jest naszym wyborem. W instrukcji
BME280I2C czujnik;
nie ma dodatkowych parametrów, więc bez naszej wiedzy czujnik na początku pracy programu zostanie skonfigurowany domyślnie, co jest jak najbardziej odpowiednie dla naszej stacji meteo.
W naszym programie mamy zadeklarowane trzy zmienne globalne typu float, o krótkich nazwach nawiązujących do polskich określeń mierzonych wielkości (temperatury, wilgotności i ciśnienia).
W jednorazowo wykonywanej funkcji setup() musimy zainicjalizować urządzenia. Standardowo realizujemy to dla wyświetlacza LCD (lcd.begin(16, 2);) i łącza I2C (Wire.begin();). Inicjalizacja naszego czujnika jest bardziej złożona. Zasadniczo wystarczyłoby analogiczne polecenie: czujnik.begin();
Ale zaczerpnęliśmy z przykładów sposób, w którym program sprawdza, co zwraca funkcja – metoda czujnik.begin().
Jeśli wszystko jest w porządku, zwraca ona wartość true (1). Jeśli nie ma kontaktu z czujnikiem, zwraca false (0), co powoduje wyświetlenie napisu: brak kontaktu z sensorem.
Jeśli czujnik reaguje, program wykonuje funkcję – metodę czujnik.chipModel(), która odczytuje informacje o modelu układu scalonego. Program rozróżnia kostki BME280 i BMP280 i wyświetla stosowny napis. Następnie przechodzi do pętli loop(), gdzie mamy trzy główne elementy:
– odczyt zmierzonych wartości z czujnika,
– wyświetlenie tych wartości
– sekundę opóźnienia.
Dwa ostatnie są jasne. Wyjaśnienia wymaga pierwszy: odczyt wartości z czujnika. Można to zrobić na kilka sposobów, co widać w programie, gdzie mamy szereg zakomentowanych linii z rozmaitymi sposobami odczytu. Na pierwszy rzut oka wygląda to dość groźnie. Aby zrozumieć istotę sprawy, trzeba wrócić do czujnika BME280. Jest to maleńki (2,5 x 2,2 x 1mm) układ z ośmioma wyprowadzeniami.
Jak pokazuje rysunek 17 zawiera trzy czujniki i układy współpracujące z zasilaczem i stabilizatorem, multiplekser, przetwornik ADC oraz bloki logiki i interfejsu. Układ ma dwie oddzielne końcówki zasilania VDD i VDDIO. Układ logiki jest dość rozbudowany. Kostka może pracować z interfejsem SPI albo I2C i wtedy może mieć jeden z dwóch adresów (0x76, 0x77), co pozwala na współpracę dwóch czujników BME280 na jednym łączu I2C.
Układ zawiera wewnątrz szereg rejestrów – komórek pamięci, co pokazane jest na rysunku 18.
Nie trzeba wgłębiać się w te szczegóły dzięki gotowej bibliotece Arduino. Ale trzeba wiedzieć, że z układem BME280 jest nieco inaczej, niż z innymi prostszymi czujnikami. Mianowicie w wielu czujnikach wystarczy po prostu odczytać odpowiednie wartości z rejestrów i tak uzyskane liczby są finalnymi wartościami mierzonych wielkości. Tu sytuacja jest znacznie bardziej skomplikowana. Owszem, trzeba odczytać wartości z rejestrów, gdzie zawarte są aktualne dane z przetwornika ADC. Ale liczby te należy w dość skomplikowany sposób przetworzyć, wykorzystując współczynniki kalibracyjne, też odczytane z danego egzemplarza czujnika. Biblioteka Arduino podczas pracy po pierwsze odczytuje dane z rejestrów czujnika, i po drugie wykonuje dość skomplikowane obliczenia. W efekcie uzyskiwane są wyniki o zaskakująco dużej rozdzielczości i dość dużej dokładności bezwzględnej. Z punktu widzenia naszego programu stacji meteo, wygląda to bardzo prosto: wykorzystujemy biblioteczną funkcję – metodę:
czujnik.read()
która hurtowo odczytuje co trzeba, oblicza co trzeba i wpisuje do trzech zmiennych typu float wartości ciśnienia, temperatury i wilgotności względnej. Wilgotność względna wyrażana jest w procentach i tu nie ma wątpliwości. Natomiast ciśnienie i temperaturę ta funkcja – metoda może nam podać w różnych jednostkach.
Wykorzystując biblioteczną funkcję – metodę czujnik.read(), trzeba przede wszystkim podać jej nazwy zmiennych typu float, do których zostaną zapisane wyniki pomiarów, a dodatkowo też podać, w jakich jednostkach mają być wyrażone temperatura i ciśnienie.
Do określenia jednostek można wykorzystać pewne zdefiniowane stałe, np. widoczne w szkicu TempUnit_Celsius czy PresUnit_hPa. Ale można też „pójść na skróty”: otóż takim stałym przyporządkowane są kolejno liczby. począwszy od zera (wykorzystano do tego tryb wyliczeniowy enum).
I tak temperaturę można uzyskać w:
0 – stopniach Celsjusza,
1 – stopniach Fahrenheita.
A ciśnienie uzyskamy do wyboru w:
0 – paskalach (Pa),
1 – hektopaskalach (hPa),
2 – calach słupa rtęci (inHg),
3 – atmosferach (atm),
4 – barach (bar),
5 – torach (torr)
6 – funtach na cal kwadratowy (psi – pound per square inch).
Dlatego w naszym programie mamy polecenie – instrukcję:
czujnik.read(cis, tmp, wilg, 0, 1);
która do zmiennej cis wpisuje wartość ciśnienia w hektopaskalach, do zmiennej tmp wartość temperatury w stopniach Celsjusza i do zmiennej wilg – wartość wilgotności względnej w procentach.
Jeśli podamy tylko nazwy zmiennych:
czujnik.read(cis, tmp, wilg);
to zostaną zastosowane wartości domyślne (0, 0), czyli ciśnienie będzie podane w paskalach, a temperatura w stopniach Celsjusza. W programie masz gotowe, tylko zakomentowane różne inne opcje – zachęcam do ich przetestowania.
Po skompilowaniu i wgraniu programu mój model podczas pracy wyglądał jak na fotografii 19. I tak oto wykorzystanie znakomitego czujnika BME280 okazało się banalnie proste!
W następnym odcinku UR005 nadal będziemy zajmować się pomiarami ciśnienia, temperatury i wilgotności. Jeśli chcesz, zajrzyj do poszczególnych plików biblioteki BME280, wtedy łatwiej przyswoisz sobie dalsze informacje.
Piotr Górecki