Kurs Arduino – czujnik dwutlenku węgla MH-Z19B
W ósmym odcinku kursu Arduino nadal będziemy zajmować się pomiarami parametrów atmosferycznych. Spróbujemy wykorzystać bardzo atrakcyjny czujnik dwutlenku węgla MH-Z19B. Omówimy, jak powinna wyglądać jego prawidłowa kalibracja. Będziemy się też uczyć, jak walczyć z problemami występującymi podczas korzystania z Arduino.
Czujnik dwutlenku węgla o oznaczeniu MH-Z19B pokazany jest na fotografii 1. Nie jest to czujnik tlenku węgla (o symbolu chemicznym CO), który jest bardzo silnie trującym gazem, wydzielającym się podczas niepełnego spalania węgla, tylko czujnik dwutlenku węgla, czyli CO2 – gazu, będącego wynikiem „pełnego” spalania.
Co istotne, człowiek jest też „producentem” dwutlenku węgla, który wydziela z każdym oddechem. W efekcie w zamkniętych pomieszczeniach zawartość dwutlenku węgla rośnie. Nadmierny wzrost zawartości dwutlenku węgla jest dla człowieka szkodliwy. Latem problem ze wzrostem zawartości CO2 w mieszkaniu czy miejscu pracy jest nieistotny, bowiem można skutecznie wietrzyć pomieszczenia. W praktyce problem pojawia się głównie zimą, gdy trzeba pomieszczenia ogrzewać. Z jednej strony wietrzenie (wentylowanie) jest konieczne, żeby nie dopuścić do nadmiernego wzrostu zawartości CO2, a z drugiej nadmierne wietrzenie to niepotrzebne straty ciepła i wzrost kosztów ogrzewania. Czujniki CO2 są coraz częściej wykorzystywane w nowoczesnych systemach klimatyzacji i wentylacji. W mieszkaniu elektronika hobbysty czujnik zawartości dwutlenku węgla może pełnić co najmniej dwie ważne funkcje. Po pierwsze czujnik wyposażony w obwody alarmu pozwoli na bieżąco monitorować poziom CO2 i w jakikolwiek sposób obniżyć jego stężenie do pożądanej wartości (choćby przez otwieranie okien po usłyszeniu sygnału ostrzegawczego).
Po drugie, pomiar zawartości CO2 pozwoli zrealizować automatyczne czy półautomatyczne instalacje wentylacyjne i klimatyzacyjne zmniejszające koszty ogrzewania.
Od razu trzeba jednak dodać, że sam pomiar zawartości dwutlenku węgla to za mało. Aby prawidłowo ocenić i regulować warunki atmosferyczne w mieszkaniu, trzeba też koniecznie uwzględnić zawartość pary wodnej (wilgotność powietrza) i temperaturę. O kwestii tej wspominaliśmy wcześniej, gdy wstępnie testowaliśmy czujniki wilgotności i gdy omawialiśmy zagadnienia związane z wilgotnością względną, bezwzględną oraz punktem rosy.
W każdym razie dobry czujnik dwutlenku węgla jest bardzo ważnym elementem w systemie współczesnego inteligentnego domu.
Oczywiście trzeba pamiętać, że świeże powietrze też zawiera niewielkie ilości dwutlenku węgla. Dwutlenek węgla jest niezbędny dla roślin. Taka „naturalna” zawartość jest bardzo mała i mierzymy ją nie w procentach ani nawet w promilach, tylko w znanych elektronikom jednostkach ppm, czyli parts per million (części na milion). Zawartość dwutlenku węgla w świeżym powietrzu wynosi mniej więcej 400ppm (plus minus kilkadziesiąt ppm), czyli około 0,04%.
Ogólnie biorąc, jako dopuszczalną granicę przyjmuje się 2000ppm, czyli 0,2% CO2 w powietrzu. Absolutnie nie jest to dawka śmiertelna ani nawet groźna, a tylko przyjęta górna granica, powyżej której człowiek się trudniej koncentruje, szybciej męczy, mniej wydajnie pracuje. Zaleca się, by w pomieszczeniach zawartość CO2 nie przekraczała 1000ppm (0,1%). I właśnie 1000ppm można przyjąć jako maksymalną wartość zalecaną, natomiast 2000ppm jako próg alarmowy.
A teraz parę słów o czujniku. Od wielu lat znane są czujniki różnych gazów, pracujące na zasadzie elektrochemicznej, zawierające grzałkę, utrzymującą w wysokiej temperaturze chemiczną substancję czynną. Elementy takie mają liczne wady. Tego rodzaju czujniki, nie tylko takie, które mierzą poziom dwutlenku, nie są niestety selektywne, czyli reagują też na różne inne gazy i opary.
Jednak od jakiegoś czasu na rynku dostępne są czujniki dwutlenku węgla oznaczane NDIR (nondispersive infrared sensor). Działają one na takiej zasadzie, że określone gazy, w tym dwutlenek węgla, w związku ze swoją chemiczną budową pochłaniają promieniowanie świetlne o określonych długościach fal. Najogólniej biorąc, czujnik NDIR mierzy stopień pochłaniania promieniowania podczerwonego przez dwutlenek węgla. Tańsze wersje tego rodzaju czujników jako źródło promieniowania podczerwonego wykorzystują miniaturowe żaróweczki, nowsze i lepsze czujniki, (oznaczane COZIR albo CO2IR) mają zamiast tego diodę LED – IRED.
Obecnie można kupić tanie chińskie czujniki NDIR produkcji Winsen, o oznaczeniach MH-Z19, MH-Z14, a także inne pokrewne. Na fotografii 1 pokazany jest czujnik MH-Z19, natomiast MH-Z14 ma postać z fotografii 2.
Dostępne są czujniki o różnych zakresach pomiarowych. Najpopularniejsze są „domowe” o zakresie pomiarowym do 2000ppm. Wersje o zakresie do 5000ppm są mniej popularne (stosowane np. w szklarniach), a wersje o szerszych zakresach nawet do 15% (150 000ppm) bywają potrzebne w specyficznych zastosowaniach, na przykład w przechowalniach owoców.
Niewielki czujnik z rodziny MH-Z1x zawiera żaróweczkę (!) i fotodiodę, a do tego filtr optyczny i układ elektroniczny oparty na mikrokontrolerze STM32. Zainteresowani znajdą fotografie wnętrza czujnika na stronie:
https://forum.mysensors.org/topic/7761/mh-z19-teardown.
Dokumentacja czujników dostępna jest na stronie producenta:
www.winsen-sensor.com/sensor/carbon-dioxide-sensor.html.
W każdym razie czujnik MH-Z1x zasilany jest napięciem 5V±0,5V. Do tej pory w ramach kursu Arduino dołączaliśmy różne „cyfrowe” czujniki z wykorzystaniem łączy I2C oraz SPI. W tym przypadku jest inaczej i mamy do wyboru dwie albo trzy możliwości.
Czujnik rodziny MH-Z1x ma co najmniej dwa wyjścia cyfrowe (i ewentualnie także wyjście analogowe).
Dostępne są czujniki MH-Z19 oraz MH-Z19B, niewiele się różniące.
Pochodzący z katalogu rysunek 3 pokazuje układ wyprowadzeń obu czujników. Według katalogu, nóżka 1 w wersji MH-Z19 jest wyjściem stabilizatora 3,3V o obciążalności do 10mA, a w wersji MH-Z19B jest wyjściem analogowym (0.4…2V lub 0…2.5V)
Na nóżce 9, czyli na wyjściu cyfrowym PWM, występuje sygnał impulsowy PWM o okresie około 1 sekundy, a jego współczynnik wypełnienia zależy od zmierzonej wartości koncentracji CO2, według wzoru podanego w karcie katalogowej. Można więc dołączyć czujnik za pomocą trzech przewodów według rysunku 4a. Wyjście PWM oraz działające na podobnej zasadzie ewentualne wyjście analogowe dają tylko informację o stężeniu CO2. Znacznie szersze możliwości daje dołączenie czujnika za pomocą łącza szeregowego (UART) według rysunku 4b.
W ramach kursu Arduino bardzo często wykorzystujemy łącze szeregowe, które nazywamy Serial i które służy do komunikacji naszej płytki z konsolą (Serial Monitor) na ekranie komputera. Sprzętowy Serial wykorzystuje interfejs (UART), wbudowany w procesor ATmega328, związany też z cyfrowymi pinami Arduino o numerach 0 i 1 (oraz obwodami pośredniczącego łącza USB i oddzielnego procesora ATmega8U2 albo kostki CH340 na płytce Arduino). Ponieważ w większości ćwiczeń istniejące w płytce Arduino sprzętowe łącze szeregowe „zużywamy” na potrzeby komunikacji z konsolą na komputerze, do podłączenia czujnika MH-Z19(B) trzeba wykorzystać programową wersję tego łącza. W środowisku Arduino IDE mamy wbudowaną potrzebną bibliotekę SoftwareSerial, która pozwala zrealizować łącze szeregowe (jedno lub kilka) w sposób programowy, z wykorzystaniem niemal dowolnych pinów Arduino.
Mój model z czujnikiem dwutlenku węgla pokazany jest na fotografii 5.
Zrealizowany jest on w bardzo prosty sposób według rysunku 6.
Czujnik MH-Z19 zasilany jest napięciem 5V z Arduino. Jego wewnętrzny procesor zasilany jest napięciem 3,3V, ale wejście cyfrowe Rx (RXD) jest odporne na napięcie 5V (5V tolerant).
Komunikacja za pomocą łącza szeregowego polega na tym, że Arduino wysyła do czujnika polecenie dość długie, bo składające się z dziewięciu bajtów. Gdy czujnik odbierze zrozumiałe dla niego polecenie, odpowiednio reaguje. W szczególności gdy otrzyma polecenie odczytu stężenia CO2, odsyła odpowiedź, też składającą się z dziewięciu bajtów.
Rysunek 7 pokazuje strukturę ramek rozkazu (request) i odpowiedzi (response). Jak widać, pierwszy bajt zawsze ma postać 0xFF, czyli dwójkowo 11111111. Drugi bajt rozkazu to numer sensora (równy 1). Trzeci bajt zawiera rozkaz, który dla odczytu pomiaru ma postać 0x86. Następne bajty rozkazu są (mają być) „puste” – równe zeru, a ostatni bajt to suma kontrolna wykorzystywana do sprawdzania ewentualnych błędów transmisji.
Struktura odpowiedzi (response) jest podobna, tylko drugi bajt odpowiedzi to odesłany numer wykonanego polecenia (0x86), a właściwe dane, czyli liczba wskazująca zawartość dwutlenku węgla w „ppmach” zawarta jest w bajtach trzecim i czwartym (Byte2, Byte3). Rysunek 7 wskazuje, że odpowiedź, poza nagłówkiem, powtórzonym numerem rozkazu i bajtem kontrolnym, zawiera sześć bajtów danych, z czego tylko dwa są wykorzystane.
Mając takie informacje, można byłoby „na piechotę” próbować zrealizować program do obsługi czujnika CO2 z wykorzystaniem biblioteki SoftwareSerial. Jednak dostępnych jest kilka gotowych bibliotek Arduino, które obsługują wcześniej wymienione odmiany czujników i oprócz omówionego tu odczytu zawartości CO2, mogą też w prosty sposób zrealizować inne rozkazy – polecenia, których na razie nie będziemy szczegółowo omawiać. Wspomnijmy tylko, że w karcie katalogowej wersji MH-Z19B jest informacja o rozkazie 0x99, który pozwala samodzielnie zmienić zakres pomiarowy 2000ppm/5000ppm. W obu wersjach dostępne są też rozkazy związane z kalibracją.
Koniecznie trzeba też dodać, że dokumentacja udostępniona przez producenta jest, najdelikatniej mówiąc, skromna. Rysunek 7 sugeruje, że kolejne bajty odpowiedzi (response) są „puste”, na co wskazują kreski w odpowiednich okienkach. W rzeczywistości te bajty odpowiedzi zawierają jakieś liczby.
Dociekliwi elektronicy przeprowadzili własne dodatkowe badania, przypominające „reverse engineering”, czyli inżynierię wsteczną, by zbadać dodatkowe, nieudokumentowane funkcje i możliwości takich czujników. Bardzo interesujące efekty takich badań zainteresowani znajdą choćby na stronie:
https://revspace.nl/MHZ19.
Jeszcze bardziej wnikliwe analizy i rozważania są dostępne na rosyjskojęzycznej stronie:
https://habr.com/post/401363/.
Okazuje się, że odpowiedź na rozkaz odczytu 0x86 w bajcie piątym (Byte4) zawsze zawiera wartość temperatury czujnika w stopniach Celsjusza, tylko zwiększoną o 40. W bajcie szóstym (Byte5) zawarta jest liczba, którą można różnie interpretować. Pozostałe dwa bajty też zawierają jakąś tajemniczą treść.
Piszę o tym dlatego, że większość dostępnych arduinowych bibliotek dla czujników MH-Z1x wykorzystuje niektóre z tych nieudokumentowanych właściwości i podaje też wartość temperatury czujnika (w pełnych stopniach Celsjusza).
Niektóre biblioteki obsługujące czujniki MH-Z19(B) są uboższe, inne oferują dodatkowe funkcje, w tym możliwość wysyłania wspomnianych rozkazów kalibracyjnych i konfiguracyjnych przez łącze szeregowe. Niektóre biblioteki oferują funkcje-metody dla łącza szeregowego i dla wyjścia PWM, a inne obsługują tylko jeden z tych sposobów komunikacji.
Ja na początek próbowałem zainstalować jakąś stosowną bibliotekę za pomocą Menedżera bibliotek wbudowanego w Arduino IDE. Niesety, nie dało rezulatów ani wpisywanie MH-Z, ani MHZ. Natomiast przeszukanie strony Github.com dało kilkanaście rezultatów, w tym kilka bibliotek Arduino dla czujników rodziny MH-Z1x. Przejrzałem opisy, zajrzałem do plików bibliotecznych .h i .cpp. Zdecydowałem się na bibliotekę, którą niedawno zrealizował Tobias Schürg, dostępną pod adresem:
https://github.com/tobiasschuerg/MH-Z19B.
Zainteresowała mnie bowiem możliwość włączenia trybu debug, polegającego na dokładniejszym przedstawieniu danych otrzymanych z czujnika CO2, co wprawdzie niewiele daje, ale po niedużej modyfikacji programu pozwoliłoby badać nieudokumentowane funkcje czujnika. Ściągnąłem plik MH-Z19B-master.zip, który rozpakowałem do katalogu …/Dokumenty/Arduino/libraries.
W pakiecie znajduje się jeden przykładowy plik MH-Z19B.ino, który otworzyłem, a następnie zapisałem jako A0801.ino. Wpisałem aktualne numery pinów Arduino według rysunku 6 i odkomentowałem niewykorzystane fragmenty szkicu.
Niestety, coś było nie tak. Ekran konsoli wyglądał jak na rysunku 8.
Nie badałem szczegółów, tylko zacząłem jeszcze raz przeglądać biblioteki w Githubie. Tym razem zdecydowałem się na dużo obszerniejszą bibliotekę użytkownika strange-v, dostępną pod adresem:
https://github.com/strange-v/MHZ19.
Pakiet zawiera oddzielną bibliotekę do odczytu sygnału PWM i oddzielną dla łącza szeregowego UART, a do tego szereg przykładów.
W katalogu sw_get_values jest gotowy przykładowy szkic, wykorzystujący programowe łącze szeregowe (SoftwareSerial). Otworzyłem zawarty tam plik .ino i zapisałem jako A0802.ino, który dostępny jest w Elportalu wśród materiałów dodatkowych do tego numeru oraz pokazany jest w prościutkim szkicu 1:
#include <SoftwareSerial.h>
#include <MHZ19.h>
SoftwareSerial ss(A2, A1); //było: ss(17,16);
MHZ19 mhz(&ss);
void setup() {
Serial.begin(9600); // było więcej
Serial.println(F(„Starting…”));
ss.begin(9600); }
void loop() {
MHZ19_RESULT response = mhz.retrieveData();
if (response == MHZ19_RESULT_OK) {
Serial.print(F(„CO2: „));
Serial.println(mhz.getCO2());
Serial.print(F(„Temperature: „));
Serial.println(mhz.getTemperature());
Serial.print(F(„Accuracy: „));
Serial.println(mhz.getAccuracy()); }
else {
Serial.print(F(„Error, code: „));
Serial.println(response); }
delay(15000); }
Wykorzystujemy tu programowe łącze szeregowe, więc na początku szkicu dołączamy arduinową bibliotekę SoftwareSerial oraz bibliotekę czujnika MHZ19 (strange-v).
W trzeciej linii
SoftwareSerial ss(A2, A1);
tworzymy obiekt o nazwie ss, który będzie obsługiwał programowe łącze szeregowe na liniach A2, A1 Arduino.
W następnej linii
MHZ19 mhz(&ss)
na podstawie biblioteki strange-v/MHZ19 tworzymy obiekt o nazwie mhz, który będzie wykorzystywał nasze programowe łącze szeregowe i reprezentujący je obiekt ss.
Analogicznie także i w tym programie wykorzystujemy sprzętowy Serial do komunikacji z konsolą na komputerze i trzeba zadbać, żeby ustawienia w programie i w konsoli były jednakowe – ja w szkicu zmniejszyłem prędkość transmisji z 115 200 na 9600bps.
Po takich modyfikacjach program, skompilowany i załadowany do Arduino, dał na konsoli efekt pokazany na rysunku 9. Program odczytał informacje z czujnika!
Radość była jednak niepełna. Uzyskane wyniki były prawdziwe, jeśli chodzi o temperaturę. Natomiast liczby dotyczące zawartości CO2 budziły wątpliwości, i to nie tylko w pierwszej chwili po włączeniu, gdy czujnik nie jest jeszcze nagrzany. Świadczył o tym prościutki test: czujnik wystawiony na świeże powietrze pokazywał wartości koncentracji CO2 w zakresie od 500 do ponad 1000ppm, a tymczasem powinien pokazywać około 400ppm.
Wcale nie byłem tym zdziwiony, bo czujnik dwutlenku węgla NDIR to urządzenie bardzo czułe i podatne na błędy, łatwo ulegające rozkalibrowaniu. Nie wiadomo, kiedy zakupiony przeze mnie czujnik został wyprodukowany i w jakich warunkach był transportowany z Chin do Polski. Nic dziwnego, że mógł się rozkalibrować.
Producent czujnika przewidział bardzo sprytny sposób automatycznej okresowej kalibracji, zwany ABC (Automatic Baseline Correction). Możemy wrócić do tych szczegółów w oddzielnym artykule (proszę o informacje przez stronę Zapytaj, Odpowiedz), a teraz w ramach elementarnego kursu Arduino wspomnę tylko, że każdy czujnik MH-Z1x ma też możliwość programowej kalibracji za pomocą łącza szeregowego zarówno „zera”, jak i zakresu (span).
Do kalibracji „zera” wykorzystuje się fakt, że w świeżym, zewnętrznym powietrzu zawartość CO2 wynosi około 400ppm. W karcie katalogowej znajdziemy informacje o takiej kalibracji, która polega na wysłaniu do czujnika rozkazu 0x87 po wcześniejszym umieszczeniu go na świeżym powietrzu przez co najmniej 20 minut.
Nazywa się to kalibracją „zera”, ale nie trzeba do tego wcale zerowej zawartości dwutlenku węgla (co laboratoryjnie realizuje się przez wykorzystanie czystego azotu lub innego neutralnego gazu). Zalecana w tym przypadku kalibracja „zera” w sumie polega na ustawieniu wskazania 400ppm dla świeżego powietrza. Kalibracja taka nie jest precyzyjna, bo zawartość CO2 w świeżym powietrzu zwykle nie jest dokładnie równa 400ppm, tylko może się różnić nawet kilkadziesiąt ppm. Jednak przyjęcie jako minimalnej zawartości CO2 równej 400ppm w praktyce i tak jest naprawdę bardzo sensowne. Różnice o kilkanaście czy nawet kilkadziesiąt ppm nie mają znaczenia. W praktyce ważne są bowiem progi 1000ppm jako granicy zalecanej i 2000ppm jako granicy alarmowej.
W pakiecie biblioteki strange-v/MHZ19 w przykładach zawarty jest też katalog i plik-szkic o obiecującej nazwie sw_zero_point_calibration.ino.
Pozwala on w bardzo prosty sposób przeprowadzić taką programową kalibrację „zera”.
Po otwarciu tego szkicu trzeba go oczywiście zmodyfikować, podając aktualne numery nóżek łącza programowego SoftwareSerial (A2, A1) i uzgadniając prędkość łącza sprzętowego Serial według skróconego szkicu 2 (dostępnego w pełnej wersji tutaj =plik ZIP A08o jako plik A0803.ino):
#include <SoftwareSerial.h>
#include <MHZ19.h>
SoftwareSerial ss(A2, A1); //? TX, RX ?
MHZ19 mhz(&ss);
unsigned long _time = 0; char _command[255]; byte _idx = 0; bool _readCommand = false;
void setup() {
Serial.begin(9600); //sprzętowy „serial”
Serial.println(F(„Starting…”));
ss.begin(9600); } //programowy „serial”
// definicja funkcji printCurrentValues() :
void printCurrentValues() {
float k = 2.0/5.0;
MHZ19_RESULT response = mhz.retrieveData();
if (response == MHZ19_RESULT_OK) {
Serial.print(F(„CO2: „));
Serial.println(mhz.getCO2());
Serial.println(mhz.getCO2() * k);
Serial.print(F(„Temperature: „));
Serial.println(mhz.getTemperature());
Serial.print(F(„Accuracy: „));
Serial.println(mhz.getAccuracy()); }
else {
Serial.print(F(„Error, code: „));
Serial.println(response); }
Serial.println(); }
void loop() {
unsigned long ms = millis();
if (ms – _time > 15000 || _time > ms)
{ _time = ms; printCurrentValues(); }
while (Serial.available() > 0) {
char c = Serial.read();
if (c == 13) { continue; }
else if (c == 10) {
_command[_idx] = '\0′;
_readCommand = true; _idx = 0; }
else {_command[_idx] = c; _idx++; } }
if (_readCommand) { _readCommand = false;
if (strcmp(_command, „calibrate”) == 0)
{ Serial.println(F(„Calibration…”));
mhz.sendCommand(0x87); } } }
Na początek standardowo dołączamy biblioteki, tworzymy obiekty na podstawie tych bibliotek, a dodatkowo definiujemu kilka globalnych zmiennych. W jednorazowej funkcji setup() ustawiamy parametry obu łączy szeregowych (9600) i wypisujemy na ekranie komunikat. Następnie definiujemy też funkcję printCurrentValues(), która wypisze na ekranie wartości odczytane z czujnika albo numer błędu, gdy coś będzie nie tak z komunikacją. Natomiast w pętli loop() mamy najpierw odliczanie czasu i co około 15 sekund na ekranie konsoli wypisywane są wartości odczytane z czujnika. Cały czas dzięki obecności pętli
while (Serial.available() > 0) { }
program nasłuchuje też, czy może nadeszły jakieś znaki z konsoli komputera i analizuje ich treść. Jeżeli w buforze odbiorczym łącza Serial pojawi się jakiś znak ASCII, zostaje przekazany do zmiennej c (typu char). Generalnie odebrany znak jako kolejna litera zostaje za pomocą licznika znaków _idx dopisany do zmiennej tekstowej _command[_idx]. Odebrane kolejno znaki–litery zostają więc zbierane i składane w łańcuch tekstowy.
Pojawienie się w buforze znaków ASCII o numerach 13 i 10 powoduje, że zmienna _readCommand _ zmienia stan na true (prawda), a to powoduje sprawdzenie, czy wcześniej odebrane znaki stworzyły tekst „calibrate”. Jeżeli tak, do czujnika zostanie wysłany rozkaz kalibracji „zera” o numerze 0x87 z wykorzystaniem bibliotecznej metody
mhz.sendCommand(0x87);.
Kody ASCII o numerach 13 i 10 to nie znaki drukowalne, tylko kody sterujące. Kod 13 ASCII to polecenie CR (carriage return – powrót karetki), a kod 10 to LF (line feed – new line – nowa linia). Są stosowane (nie zawsze oba) jako znak końca transmisji jednej linii tekstu (klawisz Enter).
Po skomplikowaniu, wgraniu i uruchomieniu tak dostosowanego programu trzeba oczywiście otworzyć na komputerze konsolę monitora szeregowego, na której co około 15 sekund zaczną się pojawiać aktualne wartości zmierzone przez czujnik.
W związku z treścią programu w Arduino trzeba też coś zmienić w konsoli na ekranie komputera. Otóż na dole okna konsoli monitora zapewne i u Ciebie w jednym z małych okienek jest opcja „No line ending”. Trzeba ją zmienić na „Both NL & CR”, a następnie w górnym okienku wpisać calibrate, i kliknąć Send, jak pokazuje rysunek 10. Taką kalibrację należy przeprowadzić po co najmniej 20 minutach pracy czujnika na świeżym powietrzu.
Mój „zaokienny” zestaw wyglądał wtedy jak na fotografii 11.
Na ekranie konsoli wyświetli się napis Calibration i zaczną się pojawiać nowe wartości stężenia CO2, równe albo bardzo zbliżone do 400 (ppm). U mnie wyglądało to jak na rysunku 12.
W ten sposób mamy czujnik dwutlenku węgla „wyzerowany” i gotowy do normalnej pracy.
Prosto, łatwo i przyjemnie!
Nie tylko dla orłów
Lektura przedstawionego właśnie opisu być może stwarza wrażenie, że wszystko jest prosto, łatwo i przyjemnie.
Niestety, w rzeczywistości bardzo często, a właściwie to zawsze, i to wcale nie tylko mniej zaawansowani, ale wszyscy korzystający z Arduino napotykają liczne problemy.
Tak, problemy są liczne, choć często bardzo drobne. Jednak zwykle okazują się dokuczliwe, trudne do znalezienia, zwłaszcza dla osób, które nie mają szczegółowej wiedzy o sprzęcie i o programowaniu. Nie sposób podać recept, jak sobie z takimi problemami radzić.
Ścisłych recept nie ma. A właściwie to jest jedna. Tylko praktycznie niedostępna: trzeba wiedzieć wszystko i być doskonale spostrzegawczym.
W rzeczywistości nikt taki nie jest i trzeba jakoś sobie radzić. Od początku przygody z Arduino należy unikać coraz powszechniejszej postawy: to ja zapytam na forum.
Owszem w ostateczności można poprosić o pomoc, ale przede wszystkim należy próbować samodzielnie.
Jednym z celów niniejszego kursu jest też pokazanie przykładów, jak z napotkanymi problemami mogą walczyć osoby, mające skromną wiedzę na temat sprzętu i programowania.
W przypadku omawianego czujnika dokumentacja ze strony producenta jest wprawdzie skąpa i niezbyt precyzyjnie przetłumaczona z chińskiego na angielski. Ale jest, co nie jest regułą dla chińskich wyrobów. Jednak warto i trzeba dodatkowych informacji szukać w sieci.
Inny problem to fakt, że dostępne biblioteki i przykładowe szkice-programy są niedoskonałe w mniejszym lub (częściej) większym stopniu. Owszem, istnieją dopracowane, dobrze sprawdzone i „dopieszczane” biblioteki oraz szkice. Jednak wszystko, co jest związane z Arduino, ma status „open source” i całe oprogramowanie jest darmowe. Z jednej strony jest to wielkim błogosławieństwem, bo mamy dostęp do kodu źródłowego. Ale z drugiej strony bardzo łatwo się przekonać, iż wiele, jeśli nie większość tych darmowych programów i bibliotek jest pisanych przez domorosłych programistów. To dobrze, że udostępniają swoje prace w Internecie, jednak trzeba do nich podchodzić z ostrożnością. Jeśli wszystko działa, trzeba się cieszyć, ale niedoskonałość polega między innymi na braku obsługi błędów.
Inny często spotykany rażący przykład niedoskonałości to brak sensownych opisów i komentarzy. Przykład to opisana wcześniej „prosta, łatwa i przyjemna” kalibracja zera. Niestety, w pakiecie nie ma absolutnie żadnych wskazówek, jak ją przeprowadzić – jest tylko katalog i plik o zachęcającej nazwie sw_zero_point_calibration.ino.
Jedynym ratunkiem jest analiza tego programu, a w innych przypadkach także plików bibliotecznych z rozszerzeniami .h oraz .cpp. Dla mniej zaawansowanych jest to czarna magia. Ale nie trzeba rozumieć wszystkich szczegółów! W tym przypadku nie trzeba znać naprawdę niełatwych zagadnień związanych z przetwarzaniem tekstów (do czego będziemy jeszcze niejednokrotnie wracać). Ale trzeba znać podstawy i rozumieć kod ASCII. Wystarczy sprawdzić w Internecie, że wykorzystana w szkicu „niearduinowa” funkcja języka C++ o nazwie strcmp() służy do porównywania łańcuchów znaków-napisów (string compare). Zwraca ona zero, gdy łańcuchy – napisy są jednakowe. W treści programu mamy linię
if (strcmp(_command, „calibrate”) == 0)
z tekstem „calibrate”. A wcześniej sprawdzanie liczb 13 i 10 za pomocą if. Tu niestety trzeba znać podstawy kodu ASCII i wiedzieć, że kody sterujące CR + LF o numerach 13, 10 to popularny sposób wskazywania przejścia do nowej linii (stosowany w Windows i Internecie).
To wystarczy, żeby nawet początkujący rozszyfrował problem. Nie trzeba natomiast rozumieć szeregu innych interesujących szczegółów.
W każdym razie należy śmiało sięgać do szkiców, a także do bibliotek. Zrozumienie, co tak naprawdę jest w bibliotekach, jest trudne, bo zwykle jest to czysty (albo mniej czysty) język C++. Jednak nie trzeba rozumieć wszystkiego – wystarczy się zorientować, jakie funkcje (metody) są tam definiowane, bo to pomoże te funkcje wykorzystać w szkicach. Do tych kwestii pomału dojdziemy w artykułach „Wokół Arduino”, ale już teraz śmiało sięgaj do treści bibliotek, choćby tylko po to, żeby się oswoić z różnymi sposobami i stylami ich pisania.
Niestety, są i inne problemy. Na przykład autor biblioteki strange-v w szkicach, w szczególności w sw_get_values.ino, jako programowy port szeregowy wykorzystał piny cyfrowe 17, 16, jak wskazuje początkowy fragment tego szkicu:
#include <SoftwareSerial.h>
#include <MHZ19.h>
SoftwareSerial ss(17,16);
MHZ19 mhz(&ss);
Już tu początkujący może sądzić, że potrzebne jest jakieś „większe Arduino”, na przykład Arduino Mega, bo jego UNO ma piny tylko 0…13. Niekoniecznie! Arduino Uno ma też piny cyfrowe 14…19 (A0 = Pin 14, A1 = Pin 15, A2 = Pin 16, A3 = Pin 17, A4 =Pin 18, A5 = Pin 19).
Ale jest i inny problem: Czy 17 to Tx, czy Rx? W tym szkicu nie ma żadnej wskazówki.
„Wskazówka” jest w szkicu sw_zero_point_calibration.ino, gdzie analogiczne linie wygladają tak:
#include <SoftwareSerial.h>
#include <MHZ19.h>
SoftwareSerial ss(17,16); //TX, RX
MHZ19 mhz(&ss);
A więc pin 17 (A3) to Tx, czyli… wyjście.
Dobrze, tylko czy ma to być wyjście Tx (TXD) tworzonego właśnie programowego portu szeregowego na płytce Arduino, czy też może pin 17 Arduino ma być podłączony do wyjścia Tx czujnika MH-Z19?
Ten sam problem jest ze szkicem z wcześniej wspomnianej biblioteki
https://github.com/tobiasschuerg/MH-Z19B
pokazanym poniżej:
// #include <ESP8266WiFi.h>
#include <SoftwareSerial.h>
#include „MHZ.h” // Tobias Schrueg library
#define CO2_IN A0 //było D2
// dwie możliwości interpretacji:
//#define MH_Z19_RX A2
//#define MH_Z19_TX A1
// albo:
//#define MH_Z19_RX A1
//#define MH_Z19_TX A2
MHZ co2(MH_Z19_RX, MH_Z19_TX, CO2_IN, MHZ19B);
W tym przypadku dość łatwo to określić, bo wystarczy znaleźć opis arduinowej biblioteki SoftwareSerial, np, pod adresem:
https://www.arduino.cc/en/Reference/SoftwareSerialConstructor
w skrócie: https://bit.ly/2Pmw3bI.
Okazuje się, że pierwszy argument przy tworzeniu obiektu to numer pinu, który będzie wejściem (rxPin), a nie wyjściem!
Niestety, tego rodzaju pułapek i wątpliwości jest wiele. Aby nie zniechęciły Cię one do Arduino i do programowania, po pierwsze na początku korzystaj z dobrze opisanych „gotowców”, ale je jak najbardziej modyfikuj i próbuj zrozumieć, jak działają te programy i wykorzystane w nich biblioteki. W następnym odcinku UR009 zajmiemy się wykorzystaniem modułów służących do odmierzania czasu.
Piotr Górecki