Do napisania tego artykułu skłonił mnie osobiście zmarnowany czas na "walkę z wiatrakami", a dokładnej z różnymi wersjami naszego ulubionego urządzenia;) Wszystko wydawało się lekkie, łatwe i przyjemne do czasu, gdy kupiłem układy Arduino Nano wyposażone w układ scalony CH340G. Do tej pory pracowałem na modelach Uno oraz Pro Mini i nie zdawałem sobie sprawy (pewnie jak wielu innych użytkowników) ze specyfiki działania poszczególnych układów. Przejdźmy do konkretów. Zbudowałem sterownik, który kontroluje pewne urządzenia w domu (potrafi włączyć oświetlenie ledowe po zmroku i wyłączyć je o świcie, kontroluje temperaturę wody CWU i steruje pompą cyrkulacyjną, zdalnie włącza drukarkę sieciową, gdy chcę coś wydrukować). Sterownik pracuje autonomicznie, ale czasem trzeba się z nim połączyć, żeby zmienić parametry, lub wykonać konkretną akcję (włącz/wyłącz). Przetestowałem pomysł na Arduino UNO, wszystko zgodnie z założeniami zadziałało, więc postanowiłem zbudować gotowy sterownik w oparciu o Arduino Nano. No i się zaczęło. Program przeniesiony do mikrokontrolera działał, ale komunikacja z komputerem nagle przestała działać. Diody sygnalizujące transmisję radośnie świeciły podczas komunikacji, ale efektu w postaci wykonanego polecenia, czy odczytanych danych nie było...
Diagnoza
Długo to trwało, ale znalazłem przyczynę dziwnego zachowania układu Arduino Nano. Okazało się, że podczas otwarcia portu szeregowego następuje restart układu Aduino. W efekcie mikrokontroler zamiast odebrać dane i odesłać odpowiedź tracił czas na rozpoczynanie wykonywanego programu od zera. Pierwszą rzeczą jaka mi przyszła do głowy, to wadliwie działający układ CH340G, który odpowiada za transmisje z komputerem. Szybko jednak okazało się, że tą samą "wadę" (w rzeczywistości to normalne zachowanie) posiada moje Arduino MEGA wyposażone w konwerter FTDI FT232 oraz wszystkie inne układy. Jestem osobą, która nie lubi niewyjaśnionych zdarzeń, zwłaszcza jeżeli dotyczą one zagadnień informatycznych lub elektronicznych. Znalazłem kilka schematów układów Arduino i okazało się, że wszystko, co uważałem za wadę jest prawidłowym celowym działaniem układu. Problem w sygnale DTR, który jest jednym z elementów sprzętowej kontroli przepływu danych. W typowych urządzeniach, które możemy podłączyć do komputera transmisja szeregowa odbywa się na dwa sposoby:
- z wykorzystaniem sprzętowej kontroli danych (RX, TX oraz sygnały RTS, CTS, DTR, DSR),
- bez sprzętowej kontroli danych (wyłącznie RX, TX).
Konwertery USB-RS takie jak CH340G, czy FTDI FT232 umożliwiają transmisję szeregową z wykorzystaniem sprzętowego sterowania danymi. Z jakiego powodu to jest ważne i potrzebne, to nie temat na ten artykuł. Wracając jednak do tematu... Projektanci Arduino wykorzystali linię sygnałową DTR do restartowania mikrokontrolerów, co umożliwia wgrywanie nowego programu bez ręcznego restartowania układu. Każdy, kto spotkał się z układem Arduino Pro Mini wie, że podczas jego programowania należy nacisnąć przycisk reset, żeby w układzie uaktywnił się bootloader. Sygnał DTR we wszystkich Arduino zbudowanych z wykorzystaniem sprzętowych układów CH340G, FTDI FT232 i pochodnych powoduje reset układu podczas otwarcia portu szeregowego. Poniżej znajduje się prosty program, który umożliwi nam sprawdzenie, czy nasz układ Arduino restartuje się podczas otwarcia portu szeregowego.
void setup(){
int i;
pinMode(13,OUTPUT);
for(i=0;i<10;i++)
{
digitalWrite(13,HIGH);
delay(150);
digitalWrite(13,LOW);
delay(150);
}
}
void loop(){
}
Program jest prymitywnie prosty, ale jednoznacznie sprawdza, czy układ się resetuje. Po uruchomieniu układu wbudowana dioda podłączona do portu 13 naszego Arduino zacznie migać. Następnie program nie robi już nic. W tym momencie możemy uruchomić monitor portu szeregowego, który jest wbudowany w ArduinoIDE. Jeżeli dioda znów zacznie migać oznacza to, że Arduino zostało zresetowane przez program. Zamykamy monitor portu szeregowego i otwieramy go ponownie. Dioda w Arduino zacznie migać.
Rozwiązanie
Znamy sposób działania Arduino przy nawiązywaniu połączenia szeregowego z komputerem. Pora na zastanowienie się, co z tą wiedzą zrobić...
Podstawą do dalszego działania jest ustalenie algorytmu komunikacji z Arduino oraz funkcjonowania samej aplikacji. Musimy odpowiedzieć sobie na podstawowe pytania:
- czy restart układu Arduino w trakcie jego działania ma wpływ na nasz cały układ?
- czy aplikacja nadzorująca uruchomiona na komputerze będzie pracowała cały czas w trakcie działania układu?
W sytuacji, gdy restart Arduino w trakcie pracy nie spowoduje utraty danych, czy przerwy w funkcjonowaniu jakiegoś z elementów układu, to nie musimy się przejmować. Jeżeli aplikacja pracująca na komputerze będzie funkcjonowała tak długo jak długo będzie pracował układ Arduino, również nie ma problemu. W innym przypadku należy zabezpieczyć układ Arduino przed przypadkowym restartem.
Sprzętowa blokada resetu
Sprzętowa blokada resetu Arduino podczas inicjalizacji połączenia jest możliwa. W sieci można odnaleźć wiele porad dotyczących tego problemu. Prawie wszystkie opierają się na przerwaniu linii łączącej sygnał DTR z sygnałem RESET mikrokontrolera. Zazwyczaj wykonuje się to poprzez usunięcie kondensatora 100nF na styku tych dwóch sygnałów. Czasem proponuje się wlutowanie zworki, czy przełącznika, co umożliwi programowanie układu. Ja polecam zastosowanie metody prostszej, mniej inwazyjnej, która da nam podobny efekt bez ingerencji w sam układ Arduino. Rozwiązanie polega na podłączeniu rezystora ok. 100Ω pomiędzy linię RESET, a zasilanie (5V lub 3,3V w zależności od wersji Arduino). Poniżej pokazano schemat podłączenia rezystora do układu.
Jak widać schemat układu jest bardzo skomplikowany... W rzeczywistym układzie można zamontować rezystor bezpośrednio w złączu Arduino, lub dolutować do listwy PIN. Układ zabezpieczony rezystorem nie będzie restartowany za pomocą sygnału DTR. Ma to jednak swoją wadę w postaci braku automatycznego programowania. Zastosowana listwa PIN umożliwi wyjęcie rezystora na czas programowania, co powoduje, że proponowane rozwiązanie jest bardzo funkcjonalne. Wartość rezystora należy dobrać doświadczalnie, ponieważ w niektórych egzemplarzach wystarczy rezystor 120Ω, a w innych 68Ω.
Rozwiązania programowe
Programowo możemy zaradzić naszemu problemowi na kilka sposobów. Pierwszy z nich to polubienie restartu Arduino... Tak poważnie, to czasami nie ma znaczenia, że Arduino zrestartuje się podczas nawiązywania połączenia, bo wszystkie niezbędne dane ma zapisane w pamięci nieulotnej i startuje odczytując z niej dane. Jedynym problemem może być wtedy czas uruchomienia układu po restarcie. Rozwiązaniem problemu jest odczekanie przez aplikację uruchomioną na komputerze odpowiedniej ilości czasu pomiędzy otwarciem portu, a pierwszą transmisją danych. W zależności od wersji Arduino i programu może to być od 0,5s do kilku sekund.
Jeżeli powyższe rozwiązanie nie jest do zaakceptowania, to można w systemie uruchomić aplikację działającą w tle, która nawiąże połączenie i będzie je utrzymywać. Należy pamiętać przy tym, żeby nasza aplikacja nie blokowała dostępu do portu dla innych aplikacji. Nie podam tu dokładnego przepisu dla systemu Windows, ponieważ biblioteka, z której obecnie korzystam (serialib), nie umożliwia takiego funkcjonowania aplikacji. W Linuksie wystarczy napisać program:
#include <stdio.h>
#include "serialib.h"
#define DEVICE_PORT "/dev/ttyUSB0"
int main(int argc, char *argv[])
{
serialib Arduino;
Arduino.Open(DEVICE_PORT,9600);
while(1)
sleep(1000);
return 0;
}
Program uruchomiony w tle będzie funkcjonował do czasu wyłączenia systemu, lub zabicia procesu (nieskończona petla while(1);). Jak widać jest to rozwiązanie proste i skuteczne, a co ważne, nie wymaga żadnej interwencji w elektronikę. W sieci znalazłem również kilka metod konfiguracji portu szeregowego w taki sposób, aby to nasza aplikacja zadbała o brak resetu Arduino, niestety na chwilę obecną nie udało mi się zastosować tych porad w praktyce. Gdy uda mi się to zrobić, to na pewno podzielę się tą "tajemną" wiedzą w dalszej części tego artykułu. Prace trwają...