Kurs Arduino – Kłopoty z Arduino Nano
W tym odcinku nadal będziemy zgłębiać temat czujników wilgotności gleby oraz niezmiernie pożytecznego, często wykorzystywanego protokołu MODBUS i łącza RS-485. Aby dodatkowo nauczyć się czegoś nowego, postanowiłem w tym ćwiczeniu wykorzystać malutką płytkę Arduino Nano.
Miał to być kolejny krok w zapoznawaniu się z różnymi płytkami Arduino. W artykule planowałem napisać coś w rodzaju: jak zwykle wszystkie przygotowania przebiegły bez problemów….
Niestety Arduino Nano już na wstępie spłatało mi przykrą niespodziankę!
Wcześniej wykorzystywaliśmy już malutką płytkę Arduino Pro Mini, która nie ma złącza USB i do jej zaprogramowania potrzebny jest zewnętrzny konwerter USB-TTL. W przypadku Arduino Nano jest inaczej (praktycznie tak samo, jak z Arduino Uno): na płytce jest wbudowany układ interfejsu USB-TTL i do programowania wystarczy kabelek z wtyczką USB Mini.
Jak pokazuje rysunek 1, schemat wersji Arduino Nano 3.3, w oryginalnej wersji funkcję konwertera – programatora pełni kostka FTDI FT232RL. W tanich chińskich klonach z powodzeniem zastępuje ją chiński układ CH340.
Należy zwrócić uwagę, że na płytce występuje 5-woltowy stabilizator ‚1117, dzięki czemu można ją zasilać napięciami wyższymi od 5V, podawanymi na końcówką VIN. Końcówka 5V może wtedy pełnić funkcję wyjścia do zasilania innych modułów systemu. Można też zasilić płytkę Arduino Nano napięciem 5V, podanym z zewnątrz na końcówkę oznaczoną 5V – napięcie to podane na wyjście nieczynnego wtedy stabilizatora ‚1117 nie zaszkodzi mu. Trzecią możliwością jest zasilanie płytki z komputera przez kabel USB. Napięcie z linii USB (VBUS) podawane jest z gniazda J3 najpierw na bezpiecznik polimerowy F1, potem na diodę Schottky’ego D1 i na obwód +5V.
Na płytce Arduino Nano mamy też sześć otworów do wlutowania goldpinów tworzących port ICSP (In-Circuit Serial Programming). Rzadko jest to potrzebne, ale może on zostać wykorzystany do programowania z pominięciem konwertera FT232RL (CH340), w szczególności do wgrania bootloadera, czyli niewielkiego (<2kB) programu umożliwiające programowanie przez kabel USB. Warto też pamiętać, że końcówki ICSP można wykorzystać jako port SPI.
Fotografia 2 przedstawia dwa chińskie klony. Schemat tych modułów jest nieco inny niż na rysunku 1 z uwagi na obecność CH340, ale działanie jest takie samo. Jedna z wersji ma wszystkie elementy zamontowane na jednej stronie płytki, co raczej nie ma istotnego znaczenia praktycznego.
Moje dwa egzemplarze nie miały fabrycznie zamontowanych listew goldpinów (które zresztą nie są złocone, więc goldpinami nie są). Aby je zamontować, najlepiej wetknąć listwy w płytkę stykową według fotografii 3 i lutować – na pewno wyjdzie równo.
Po tym wstępnym przygotowaniu zestawiłem układ testowy według fotografii 3 oraz rysunku 4.
Zwróć uwagę na obwody zasilania: zasadniczo wykorzystany jest 5-woltowy zewnętrzny zasilacz, ale dostarcza on energii tylko do czujnika wilgotności FDS-100 oraz do konwertera TTL-RS485. Natomiast płytka Arduino Nano i wyświetlacz OLED nie są zeń zasilane. W tym układzie testowym są one zasilane z komputera przez kabel USB, podobnie jak wielokrotnie zasilaliśmy w ten sposób Arduino Uno.
Czujnik wilgotności gleby FDS-100 pracuje w standardzie MODBUS i ma interfejs RS-485, który jest odmianą szeregowego łącza RS-232, czyli łącza Serial. Procesor ATmega328P ma sprzętowy interfejs łącza RS-232 i w płytce Arduino Nano mamy wyprowadzone łącze szeregowe: to końcówki oznaczone RX0, TX1.
Jak najbardziej, ale my w pierwszych ćwiczeniach chcemy przesyłać dane do konsoli komputera za pomocą kabla USB, więc nie możemy użyć końcówek RX0, TX1. Dlatego konwerter SerialTTL-RS485 dołączony jest do pinów 2 (D2), 3 (D3) Arduino Nano. A to oznacza, że chcemy wykorzystać znaną nam już bibliotekę SoftwareSerial.
W naszym systemie docelowo wykorzystamy też łącze I2C, czyli nóżki A4 i A5 Arduino. W przyszłości planujemy dołączyć tu czujnik wilgotności i temperatury SHT20 (widoczny na fotografii 5, ale na schemacie zaznaczony tylko jako blady cień, ponieważ najpierw trzeba wyjaśnić, czy może on być zasilany napięciem 5V, a są co do tego uzasadnione wątpliwości). Zasadniczo czujnik SHT20, jak i pokrewne czujniki szwajcarskiego Sensiriona, służy do pomiaru wilgotności względnej RH, a nie procentowej wilgotności gleby, jednak zamknięty jest on w specyficznej specjalizowanej obudowie i przewidziany jest do zakopania w ziemi. Zmierzy wprawdzie wilgotność względną RH otaczającego go powietrza, ale wilgotność ta zapewne będzie skorelowana z wilgotnością gleby.
Łącze I2C ma też obsłużyć wyświetlacz. Wcześniej w ćwiczeniach wykorzystywaliśmy wyświetlacze znakowe zgodne z HD44780. Chcemy zrobić kolejny ważny krok, dlatego tym razem będzie to wyświetlacz graficzny, a konkretnie mały, popularny wyświetlacz OLED o przekątnej 0,96 cala.
Wstępne problemy
Po połączeniu kablem USB z komputerem, na płytce Arduino Nano zaświeca się czerwona kontrolka zasilania i zaczyna migać druga czerwona dioda, dołączona do pinu 13. Standardowo do pamięci procesora wpisany jest nie tylko niezbędny bootloader, ale dodatkowo także program Blink, więc miganie drugiej diody świadczy o pracy płytki. To dobra praktyka producentów, natomiast dobrym zwyczajem użytkownika jest sprawdzenie, czy płytka daje się programować. Też za pomocą tego samego szkicu. Ja otworzyłem program Blink.ino, zmieniłem czasy opóźnienia na dłuższe i… nic! Absolutnie nic!
Najpierw podejrzanie długo ten prościutki program się kompilował, a potem jeszcze dłużej trwała próba wgrania wsadu do procesora. Po ponad minucie czekania pojawiał się komunikat błędu jak na rysunku 5.
Miganie diody LED świadczy, że prawidłowo pracuje procesor ATmega328. Za programowanie odpowiedziane są też inne elementy.
Najpierw pomyślałem, że uszkodzona jest płytka i umieszczony na niej konwerter CH340. Jak pokazuje fotografia 6, dołączyłem drugi egzemplarz płytki Arduino Nano. Zawsze warto mieć dwa moduły o jednakowej funkcji, ponieważ pozwala to rozwiać wątpliwości w przypadku problemów.
Niestety, w moim przypadku drugi egzemplarz też nie dał się zaprogramować. To z prawdopodobieństwem bliskim pewności wskazało, że problem nie leżał w płytkach Arduino Nano.
Podejrzenie padło na następny element łańcucha: na kabelek USB Mini. Niektóre kabelki USB służące do ładowania mogą nie mieć linii danych, a tylko dwie linie zasilające. Mam w zbiorach kilka różnych kabelków USB Mini i szybko sprawdziłem, że nie tu leży przyczyna problemu.
Kolejne podejrzenie padło na komputer (laptop). W poprzednim odcinku kursu Arduino opisane były eksperymenty z czujnikiem FDS-100, pracującym w standardzie MOSDBUS i do komunikacji między czujnikiem i komputerem wykorzystany był prosty konwerter, zawierający kostkę CH340. Konwerter ten pracował bez instalacji jakichkolwiek sterowników dla CH340. Teraz w Arduino Nano też miałem konwerter CH340, więc raczej instalacja sterowników nie powinna być potrzebna. Niemniej dla pewności pobrałem i zainstalowałem najnowsze ze strony producenta: www.wch.cn/download/CH341SER_ZIP.html.
Oczywiście nic nie pomogło.
Kolejnym tropem był fakt, że kilka dni wcześniej eksperymentowałem z płytką ESP32 i przed tymi eksperymentami z ESP32 na laptopie zainstalowana została najnowsza dostępna wersja Arduino IDE (1.8.9). Pojawiło się przypuszczenie, że podczas eksperymentów z ESP32 coś się poprzestawiało w konfiguracji pakietu Arduino IDE.
Posprawdzałem wszystkie dostępne okienka i ustawienia w Arduino IDE i… nic. Znów brak efektów!
Wszystko wskazywało na to, że w Arduino IDE muszą być jakieś ustawienia dotyczące programowania, które nie są dostępne dla użytkownika. Utwierdził mnie w tym przekonaniu fakt, że obie płytki Arduino dały się zaprogramować na drugim komputerze (stacjonarnym), gdzie jest zainstalowana polskojęzyczna wersja 1.8.5, która nie była wykorzystywana do eksperymentów z ESP32.
Nie ulegało wątpliwości, że problem tkwi w ustawieniach pakietu Arduino IDE niedostępnych dla użytkownika. I wszystko wskazywało, że to moduł ESP32 coś „namieszał za zasłoną”, jaką jest Arduino. Aby wrócić do pierwotnych ustawień, odinstalowałem i ponownie zainstalowałem pakiet Arduino IDE (1.8.9). Nic to nie pomogło, a pewne szczegóły mogły sugerować, że odinstalowanie nie kasuje wszystkich plików konfiguracyjnych.
W zasadzie mogłem dać sobie spokój i do programowania wykorzystać komputer stacjonarny, jednak ciekawość i chęć rozwiązania problemu do końca spowodowały, że zacząłem szukać informacji w Internecie. Tropów było wiele.
Najpierw szukałem informacji, czy płytka ESP32 może „popsuć konfigurację” i nie wiązałem tego z Arduino Nano. Takie poszukiwania rezultatu nie przyniosły, więc zacząłem szukać informacji o komunikacie, jaki pakiet Arduino IDE wypisywał po próbie wpisania wsadu do płytki – rysunek 7.
Niewątpliwie Pakiet Arduino IDE nie mógł nawiązać kontaktu z płytką Arduino Nano, co potwierdzało tezę o nieprawidłowych ustawieniach.
W końcu okazało się, że eksperymenty z ESP32 nie miały z tym nic wspólnego. Problem związany jest z Arduino Nano i jest to kolejny przykład specyfiki Arduino i braku pełnej dokumentacji. Otóż w Arduino IDE można zmieniać prędkość transmisji monitora portu szeregowego (Narzędzia – Monitor portu szeregowego) według rysunku 8, jednak dotyczy to pracy Arduino IDE tylko w roli terminalu.
Te ustawienia nie dotyczą programowania. Ustawienia dotyczące programowania są gdzieś zapisane na stałe i użytkownik nie ma do nich dostępu. Problem w tym, że jakiś czas temu w oryginalnych płytkach Arduino Nano zmieniono bootloader, co oznaczało między innymi zmianę (zwiększenie) częstotliwości transmisji podczas programowania za pomocą łącza szeregowego. Natomiast w tanich chińskich klonach nadal wykorzystywany jest stary bootloader.
W związku ze zmianą w oficjalnych płytkach Arduino Nano wprowadzono też odpowiednie modyfikacje w nowych wersjach pakietu Arduino IDE. I tak w najnowszej wersji 1.8.9 (a także najbliższych wcześniejszych) wprowadzono dodatkowe opcje. W starszej wersji 1.8.5, którą mam zainstalowaną na komputerze stacjonarnym, w menu: Narzędzia – Procesor są tylko dwie pozycje: ATmega328P i ATmega168, jak pokazuje rysunek 9.
Wersja ta przy wybraniu ATmega328P prawidłowo obsłużyła moje chińskie klony Arduino Nano. Takie same ustawienie miałem w nowszej wersji Arduino IDE 1.8.9, ale tam dostępna jest trzecia opcja: ATmega328P (Old Bootloader).
Po wybraniu tej właśnie opcji według rysunku 10 problem zniknął i programowanie moich modułów przebiegło prawidłowo! Wcześniej planowałem w artykule napisać, że realizacja przedstawianego właśnie ćwiczenia jest lekka, łatwa i przyjemna, a tymczasem okazało się, iż już na te wstępne przygotowania i usunięcie w sumie drobnego problemu straciłem mnóstwo czasu. I jeszcze raz podkreślam, że dużą pomocą w wyjaśnieniu wątpliwości są posiadane po dwa egzemplarze: płytek – modułów, kabelków i dwóch komputerów z (różnymi wersjami) Arduino IDE. Z reguły zaleca się uaktualnianie oprogramowania do najnowszej wersji. Paradoksalnie w tym przypadku przyczyną problemu okazała się praca z najnowszą wersją oprogramowania.
Po usunięciu tych wstępnych przeszkód zająłem się pojemnościowym czujnikiem wilgotności gleby FDS-100.
MODBUS
W poprzednim odcinku dowiedzieliśmy się, że tani czujnik FDS-100 ma interfejs RS-485 i działa zgodnie ze standardem MODBUS RTU. Do transmisji niezbędny jest więc moduł sprzętowego konwertera Serial TTL-RS485, który zastosowany jest w układzie z rysunku 4.
Oczywiście do stworzenia programu znów wykorzystamy gotowe biblioteki. Po wybraniu Szkic – Dołącz bibliotekę – Zarządzenie bibliotekami, wpisujemy w okno wyszukiwania MODBUS. W naszym skromnym systemie MODBUS, Arduino Nano ma pracować jako master i komunikować się z (jednym) urządzeniem slave, konkretnie z czujnikiem FDS-100. W opisie systemu Arduino znajdziemy bibliotekę Arduino – Modbus, ale przewidziana ona jest do współpracy ze specjalizowaną nakładką (szyldem – schield). Nie mamy takiego szyldu, a zamiast tego w naszym systemie jest konwerter TTL-RS485.
Na liście bibliotek „modbusowych” niektóre realizują tylko tryb pracy Slave, a nam potrzebny jest master, więc z listy bibliotek wybierzemy: ModbusMaster by Doc Walker (4-20ma).
Angielskojęzyczny opis biblioteki i opisy na różnych stronach nie zapowiadają kłopotów czy wątpliwości, jednak pewne rozczarowanie przynoszą załączone przykładowe pliki .ino. Są tylko trzy, a osoby mniej zorientowane przy pierwszej lekturze przykładów prawdopodobnie niewiele z nich zrozumieją.
Do tej pory z powodzeniem modyfikowaliśmy przykładowe pliki z bibliotek, a teraz mogą pojawić się poważne wątpliwości. Dlatego musimy wyjaśnić parę szczegółów. Otwórz zawarty w bibliotece ModbusMaster plik przykładowy Basic.pde. W spolszczonej postaci znajdziesz go na szkicu 1:
#include <ModbusMaster.h>
ModbusMaster modbusik; // obiekt „modbusik”
void setup() {
Serial.begin(19200); //sprzętowy Serial inicjalizacja
modbusik.begin(2, Serial); }
//inicjalizacja „modbusik”: urządzenie 2 przez Serial
void loop() {
static uint32_t i; //zmienna 32-bitowa
uint8_t j, result; // dwie zmienne 8-bitowe
uint16_t data[6]; //tablica: 6 16-bitowych słów
i++;
modbusik.setTransmitBuffer(0, lowWord(i));
modbusik.setTransmitBuffer(1, highWord(i));
result = modbusik.writeMultipleRegisters(0, 2);
//teraz odczytaj 6 rejestrów począwszy od adresu 2:
result = modbusik.readHoldingRegisters(2, 6);
if (result == modbusik.ku8MBSuccess)
{ for (j = 0; j < 6; j++) {
data[j] = modbusik.getResponseBuffer(j);} } }
Na początku oczywiście dołączamy bibliotekę: #include <ModbusMaster.h>
i tworzymy obiekt, który ja swojsko nazwałem „modbusik” (a dlaczego nie…)
Potem w jednorazowej funkcji setup() dokonujemy inicjalizacji. Najpierw sprzętowego łącza Serial, które w tym przypadku będzie pracować z prędkością 19200bps, a reszta parametrów jest ustawiona domyślnie (19200, 8, N, 1). Sprzętowy Serial połączony jest z konwerterem TTL-RS485, więc ustawiamy tu parametry transmisji łącza Modbus, wykorzystującego linię RS-485.
Następnie incjalizujemy nasz obiekt „modbusik”. W linii modbusik.begin(2, Serial); podajemy dwa parametry: jeden to numer ID urządzenia slave (1…255) systemu MODBUS, z którym chcemy się komunikować. W tym wypadku jest to urządzenie slave o adresie ID równym 2. Drugi parametr to informacja o łączu szeregowym, które będzie współpracować z obiektem „modbusik”. W tym wypadku jest to sprzętowe łącze szeregowe Serial. W „większych” procesorach jest więcej niż jedno sprzętowe łącze szeregowe i tam można byłoby powiązać obiekt „modbusik” z dowolnym z nich. W Arduino Nano pracuje ATmega328P, więc łącze szeregowe jest jedno i jest ono wykorzystywane do programowania za pomocą kabla USB. Nawet gdybyśmy podczas pracy programu nie korzystali z łącza Serial do współpracy z konsolą na komputerze, należałoby się zastanowić, czy dołączenie konwertera TTL-RS485 nie zaburzy pracy wejścia RX0 Arduino (dla pewności na czas programowania należałoby odłączać konwerter od wejścia RX0). My chcemy sprzętowy Serial wykorzystać do współpracy z konsolą na komputerze, więc zdecydowanie nie możemy wykorzystać go do współpracy z obiektem „modbusik”. I tu pojawia się wątpliwość: niestety, w materiałach zawartych w bibliotece ModbusMaster nie ma informacji, że można wykorzystać łącze programowe, czyli SoftwareSerial. A można, co zastosujemy za chwilę. W każdym razie podczas inicjalizacji obiektu „modbusik” trzeba podać nazwę obiektu, obsługującego łącze szeregowe (linię RS485).
W szkicu 1 mamy dalej w pętli loop() deklarację czterech zmiennych: 32-bitowej zmiennej o nazwie i, dwóch ośmiobitowych zmiennych j, result oraz tablicy z sześcioma 16-bitowymi komórkami data[6]. Dalej w programie mamy instrukcję zwiększania wartości o 1 w każdym obiegu pętli (i++) i jakieś trzy tajemnicze linie wyróżnione kolorem niebieskim. Za chwilę do tego wrócimy, a na razie dalsze linie. Obiekt „modbusik” klasy ModbusMaster oferuje kilka pożytecznych funkcji, ściślej metod. Jedną z nich jest najbardziej nas interesująca metoda .readHoldingRegisters(), czyli realizacja „modbusowego” rozkazu 03. Rozkaz ten pozwala za jednym razem odczytać dowolną liczbę rejestrów typu Holding Register. Trzeba tylko podać, od którego rejestru zacząć odczytywanie oraz liczbę rejestrów do odczytania. W tym przykładzie mamy
modbusik.readHoldingRegisters(2, 6);
Wcześniej ustawiliśmy, że obiekt „modbusik” komunikuje się z urządzeniem o numerze-adresie ID równym 2, a teraz uściślamy, że z tego urządzenia należy odczytać 6 rejestrów, począwszy od rejestru o adresie 2.
I to akurat jest proste i jasne.
Wątpliwości może budzić cała linia:
result=modbusik.readHoldingRegisters(2, 6);
Mianowicie wygląda na to, że dane odczytane z 6 rejestrów (16-bitowych Holding Registers) wpisujemy do 8-bitowej zmiennej result! Nie!
Do 8-bitowej zmiennej result NIE wpisujemy zawartości odczytanych rejestrów. Jeżeli zajrzymy do plików ModbusMaster.h i ModbusMaster.cpp, to okaże się, iż większość funkcji-metod zwraca ośmiobitowy kod błędu. W szczególności jeśli błędu nie ma, zwracana jest liczba zero. A co z odczytanymi danymi?
Ponieważ wykorzystujemy sprzętowe lub programowe łącze szeregowe Serial, korzystamy też z bufora danych, związanego z tym łączem. Dla najmniej zorientowanych: procesor pracuje bardzo szybko (taktowanie 16MHz), a w łączu szeregowym dane są wysyłane i odbierane nieporównanie wolniej (tu 1920 bitów na sekundę). Dlatego realizacja łącza szeregowego, zarówno sprzętowego, jak programowego, zawiera dwa bufory danych, czyli rodzaj poczekalni. Bufor nadawczy (Transmit Buffer) i bufor odbiorczy (Response Buffer). Standardowo mają one długość po 64 bajty. Przy nadawaniu paczka danych do wysłania (nie więcej niż te 64 bajty) jest wpisywana do bufora, a następnie „jednorazową” komendą paczka danych jest wysyłana w kolejnych ramkach o określonych parametrach (tu 19200, 8, N, 1).
W analizowanym programie mamy:
modbusik.setTransmitBuffer(0, lowWord(i));
modbusik.setTransmitBuffer(1, highWord(i));
result=modbusik.writeMultipleRegisters(0, 2);
i właśnie najpierw ładujemy do bufora nadawczego zawartość 32-bitowej zmiennej i. Najogólniej biorąc, w systemie MODBUS rejestry są 16-bitowe, więc podstawowym rozmiarem „modbusowym” jest 16-bitowe słowo (z możliwością łączenia w większe zestawy). Dlatego zawartość 32-bitowej zmiennej i ładujemy do bufora nadawczego w dwóch 16-bitowych porcjach: lowWord(i), highWord(i). Następnie 32-bitową zawartość bufora wysyłamy na linię RS-485 za pomocą metody:
.writeMultipleRegisters(0, 2); w tym wypadku do dwóch (16-bitowych) rejestrów, począwszy od rejestru o adresie 0. Czyli w urządzeniu slave dane zostaną wpisane do rejestrów (Holding registers) o adresach 0, 1. Oczywiście w płytce Arduino te 32 bajty zostaną podzielone na ośmiobitowe paczki i wysłane jako znaki-ramki o parametrach 19200, 8, N, 1, ale my tymi szczegółami wcale nie musimy zawracać sobie głowy.
Natomiast przy odbiorze informacji ze współpracującego urządzenia slave, kolejne znaki-ramki, też o parametrach 19200, 8, N, 1, pojawiające się asynchronicznie na wejściu RX0, są automatycznie odbierane, dekodowane, a ośmiobitowe porcje są kolejno umieszczane w rejestrze – buforze odbiorczym procesora ATmega328P. Oznacza to, że linia:
result=modbusik.readHoldingRegisters(2, 6);
spowoduje wysłanie do urządzenia slave „modbusowego” rozkazu 03 i to urządzenie Slave w odpowiedzi wyśle zawartość 6 rejestrów, począwszy od rejestru o adresie 2, czyli wyśle zawartość swoich 16-bitowych Holding Registers o numerach 2, 3, 4, 5, 6, 7; razem 12 bajtów = 96 bitów. Dane te trafią do (64-bajtowego) bufora odbiorczego w procesorze ATmega328P. Trafią do bufora i… I nic!
Już wiemy, że użyta metoda .readHoldingRegisters(); zwraca kod błędu i w tym przykładowym programie wpisuje go do zmiennej result. Prawidłowa transmisja daje kod błędu 0 (0x00) i w następnej linii:
if (result == modbusik.ku8MBSuccess)
następuje sprawdzenie, czy błąd jest równy zeru. Realizowane jest to w dziwny sposób, ponieważ wyrażenie modbusik.ku8MBSuccess to nie żadne 8 megabajtów, tylko po prostu wartość 0, liczba zero (sprawdź w pliku ModbusMaster.h).
Jeśli błędu nie było, to w pętli for(), która będzie mieć sześć obiegów, zawartość bufora odbiorczego zostanie przepisana do sześciu komórek tablicy data[6]. Tu wątpliwości może wzbudzić fakt, że procesor ATmega328P jest ośmiobitowy, a ciąg 96 bitów z bufora ma być podzielony na sześć części. O to zadba kompilator, który wie, że tablica data[6] ma zawierać komórki 16-bitowe (uint16_t).
Mniej zaawansowanym te szczegóły nadal mogą wydawać się niejasne, jednak podstawowe zasady są proste: metody klasy ModbusMaster zwracają (ośmiobitowy) kod błędu, natomiast dane do wysłania i dane odebrane są obsługiwane przez bufor nadawczy i odbiorczy współpracującego łącza szeregowego.
Wyposażeni w taką wiedzę możemy spróbować napisać program dla naszego czujnika wilgotności gleby o schemacie z rysunku 4. Wcześniejsze eksperymenty pokazały, iż czujnik FDS-100 reaguje, jeśli ustawimy parametry portu Serial (Software Serial) następująco: 9600bps, brak kontroli parzystości (N) i dwa bity stopu (9600 8 N 2). Ustaliliśmy wcześniej, że badany czujnik FDS-100 ma fabrycznie wgrany adres urządzenia (Device ID) równy 2.
Urządzenia MODBUS mogą mieć różną liczbę czterech rodzajów rejestrów, w tym 16-bitowych Holding Registers. Ustaliliśmy, że w czujniku FDS-100 w pierwszym takim rejestrze, o numerze-adresie zero zawarta jest liczba, która po podzieleniu przez 10 jest zmierzoną wilgotnością gleby w procentach, natomiast w drugim rejestrze o adresie 1 zawarta jest liczba, która najwyraźniej jest surową, nieobrobioną wartością z przetwornika ADC. Teraz chcemy wykorzystać te uzyskane wcześniej informacje, żeby płytka Arduino Nano odczytywała informacje z czujnika FDS-100 i wyświetlała najpierw na konsoli monitora na komputerze, potem na wyświetlaczu OLED.
Zachęcam, żebyś taki prosty szkic spróbował napisać od zera. Sprzętowe łącze Serial wykorzystaj do komunikacji z konsolą komputera. Zrealizuj programowe łącze (SoftwareSerial) z pinami 2, 3 Arduino. W zasadzie powinieneś ustawić parametry transmisji szeregowej 9600 8 N 2. W przypadku sprzętowego łącza Serial można napisać:
Serial.begin(9600,SERIAL_8N2);
Niestety, dla łącza programowego Arduino nie możemy napisać:
SoftwareSerial.begin(9600, SERIAL_8N2);
Na szczęście system będzie też pracował przy domyślnych ustawieniach 9600, 8, N, 1, czyli napiszemy:
SoftwareSerial.begin(9600);
Z czujnika FDS-100 o fabrycznie wpisanym adresie ID równym 2 trzeba odczytać zawartość tylko pierwszego z Holding Registers (o adresie 0). Zawarta tam będzie 16-bitowa liczba całkowita w zakresie 0…1000, która po podzieleniu przez 10 da wartość wilgotności gleby w procentach. Wartość tę chcemy wyświetlić na konsoli dołączonego komputera.
Gorąco zachęcam, żebyś przynajmniej spróbował samodzielnie, od zera napisać stosowny program-szkic!
I co? Spróbujesz?
Program mógłby wyglądać jak w szkicu 2 (i w szkicu A1802.ino):
#include <SoftwareSerial.h>
#include <ModbusMaster.h>
ModbusMaster modbusik; // obiekt „modbusik”
SoftwareSerial programowy = SoftwareSerial(2,3);
//programowy serial z pinami RXD = 2, TXD = 3
void setup() { //inicjalizacja
Serial.begin(115200); // Serial sprzętowy
programowy.begin(9600); //programowy
modbusik.begin(2, programowy); }
//”modbusik”: urządzenie 2 przez programowy
void loop() {
int wilg; //16-bitowazmienna na wartość wilgotności
//odczytaj 1 rejestr, począwszy od adresu 0:
modbusik.readHoldingRegisters(0, 1);
wilg = modbusik.getResponseBuffer(0);
Serial.print(„Wilgotnosc gleby: „);
Serial.print(wilg/10); Serial.println(„%”);
delay(1000); }
W naszym programowym łączu softwareSerial pin 2 Arduino jest wejściem danych RXD, a pin 3 – wyjściem TXD. Sprzętowy serial ma pracować z prędkością 115200 i taką samą prędkość trzeba ustawić w oknie konsoli monitora na komputerze. W metodzie .getResponseBuffer(0) liczba zero wskazuje, że zawartość bufora odbiorczego należy odczytywać od pierwszego bajtu o numerze-indeksie 0.
Szkic 2 jest zaskakująco krótki!
Niemniej taka minimalistyczna wersja jak najbardziej pracuje. Gorzej, gdy pojawi się jakiś błąd, jak zdarzyło się to w moim przypadku.
Po pierwsze przez przeoczenie nie połączyłem na płytce stykowej masy zewnętrznego zasilacza z masą Arduino Nano. Po drugie, pierwotnie dołączyłem konwerter RS485 według rysunku 11, czyli wyjście TXD z wejściem RXD, jak to jest w przypadku połączeń zwanych „null modem”. Problem będzie szerzej opisany w artykułach o łączu RS-232.
Tymczasem widoczny na fotografii 12 konwerter RS485 trzeba połączyć według rysunku 4. Jego końcówka oznaczona TXD jest wejściem, a RXD – wyjściem, jak pokazuje znaleziony gdzieś w Internecie schemat, zamieszczony na rysunku 13.
Dlatego w pierwszym podejściu, przy połączeniu według rysunku 11, na konsoli komputera zobaczyłem przedziwne, ogromne wartości wilgotności, co w rzeczywistości było śmieciami, pozostającymi w niewyzerowanej zmiennej wilg.
W sumie przyczynę problemów znalazłem dzięki pomiarom oscyloskopem, jednak dobrym zwyczajem jest, by przynajmniej część błędów móc zidentyfikować w sposób programowy.
A w szkicu 2 w ogóle nie wykorzystujemy faktu, że metody klasy ModbusMaster zwracają kody błędów. Poprawmy to!
Przy okazji zwróćmy uwagę, że zmienna wilg jest typu int, więc zawiera liczbę całkowitą 16-bitową (ze znakiem, w kodzie U2, co w tym przypadku nie ma znaczenia). Dlatego po podzieleniu uzyskanej z czujnika liczby przez 10 też otrzymujemy liczbę całkowitą, czyli wartość wilgotności gleby z rozdzielczością 1%. A przecież czujnik FDS-100 oferuje zakres wskazań 0…1000, czyli rozdzielczość 0,1%.
Koniecznie trzeba to jakoś poprawić! Na pewno możemy zastosować konwersję typów danych (casting), żeby odczytane dane zamienić na liczbę zmiennoprzecinkową (typu float) i ją podzielić przez 10.
Można też to zrobić prościej, jednak ze świadomością tego, co robimy, bo takie sposoby czasem sprawiają kłopoty: od razu zadeklarować zmienną wilg typu float i dokonywać konwersji „w locie”, wpisując 16-bitową zawartość bufora do tej 32-bitowej zmiennej typu float.
Program po uzupełnieniu o procedury kontroli „modbusowych” błędów mógłby wyglądać jak w szkicu 3 (i w szkicu A1802.ino):
#include <SoftwareSerial.h>
#include <ModbusMaster.h>
ModbusMaster modbusik; // obiekt „modbusik”
SoftwareSerial programowy = SoftwareSerial(2,3);
//programowy serial z pinami RXD = 2, TXD = 3
void setup() { //inicjalizacja
Serial.begin(115200); // Serial sprzętowy
programowy.begin(9600); //programowy
modbusik.begin(2, programowy); }
//”modbusik”: urządzenie 2 przez programowy
void loop() {
float wilg; byte blad; //deklaracja zmiennych
//odczytaj 1 rejestr, począwszy od adresu 0:
blad = modbusik.readHoldingRegisters(0, 1);
if (blad == 0)
{wilg = modbusik.getResponseBuffer(0);
Serial.print(„Wilgotnosc gleby: „);
Serial.print(wilg/10, 1); Serial.println(„%”);}
else
{Serial.print(„Jest problem! Blad nr: „);
Serial.println(blad, HEX);}
delay(1000); }
W tej poprawionej wersji po próbie kontaktu z czujnikiem, czyli po zastosowaniu metody
modbusik.readHoldingRegisters(0, 1)
zwracaną wartość, mianowicie kod błędu wpisujemy do zmiennej blad. Potem sprawdzamy kod błędu. Tylko wtedy, gdy jest równy zeru, przepisujemy zawartość bufora do zmiennej wilg typu float, uzyskaną wartość dzielimy przez 10 i wyświetlamy na konsoli monitora z dokładnością jednego miejsca po przecinku. Gdy wystąpi jakiś błąd, wyświetlamy jego numer w postaci szesnastkowej, ponieważ w bibliotece ModbusMaser są one właśnie tak podane – patrz tabela 1:
Modbus exception codes:
ku8MBSuccess = 0x00;
ku8MBIllegalFunction = 0x01;
ku8MBIllegalDataAddress = 0x02;
ku8MBIllegalDataValue = 0x03;
ku8MBSlaveDeviceFailure = 0x04;
ku8MBInvalidSlaveID = 0xE0;
ku8MBInvalidFunction = 0xE1;
ku8MBResponseTimedOut = 0xE2;
ku8MBInvalidCRC = 0xE3;
Najbardziej prawdopodobnym błędem będzie E2 – przekroczenie czasu oczekiwania, czyli brak odpowiedzi od urządzenia slave.
Czujnik wilgotności gleby nie jest może najbardziej popularnym urządzeniem slave pracującym w standardzie MODBUS, niemniej podane informacje pomogą za pomocą Arduino obsłużyć dowolne inne urządzenia z takim interfejsem.
W kolejnych odcinkach zajmujemy się obsługą graficznego wyświetlacza OLED.
Piotr Górecki