Opis funkcji dotyczących transmisji szeregowej jest ostatnim artykułem z serii kursu ARDUINO. Kolejne artykuły będą już o konkretnych możliwościach, bibliotekach do obsługi komponentów, czy projektach, które udało nam się doprowadzić do końca. Podstawowe funkcje umożliwiające komunikację z komputerem przewijały się przez kolejne artykuły kursu umożliwiając kontrolę wykonywanego kodu. W poniższym artykule postaram się przedstawić najważniejsze elementy umożliwiające komunikację zarówno z komputerem, jak i drugim zestawem ARDUINO, czy innym układem umożliwiającym wykonanie transmisji szeregowej.

Każdy układ ARDUINO wyposażony został w przynajmniej jeden port szeregowy, który umożliwia proste przesyłanie danych do i ze sterownika. ARDUINO UNO posiada jeden sprzętowy port szeregowy podłączony do portu USB. Z pozycji ArduinoIDE możliwe jest uruchomienie terminala, za pomocą którego można odczytać dane wysyłane poprzez port szeregowy ARDUINO, jak również wysłać informacje do układu. ARDUINO MEGA posiada dodatkowe trzy porty szeregowe, które można wykorzystać do własnych celów. Do poszczególnych portów szeregowych odwołujemy się poprzez obiekty Serial. Wszystkie układy posiadają zaimplementowany obiekt Serial, który odpowiada za transmisję poprzez pierwszy dostępny port szeregowy. Układy ARDUINO MEGA posiadają zaimplementowane dodatkowo obiekty Serial1, Serial2, Serial3. Wszystkie prezentowane poniżej przykłady dla Serial będę dotyczyły również Serial1, Serial2, Serial3.  

Serial.begin()

Podstawowym elementem wykorzystywanym przy transmisji szeregowej jest inicjalizacja portu szeregowego. Należy określić prędkość przesyłanych informacji oraz rodzaj ramki. Prędkość transmisji podajemy w bodach, czyli bitach na sekundę. Format ramki możemy określić podając liczbę przesyłanych bitów, parzystość i bity stopu. Do ustawienia parametrów transmisji Wykorzystujemy metodę Serial.begin(), która przyjmuje jeden lub dwa parametry. Pierwszy wymagany parametr to prędkość transmisji, drugi opcjonalny to rodzaj ramki. Poniżej przedstawiono przykład zastosowania metody begin():

void setup()
   {
     Serial.begin(9600);
     Serial2.begin(9600);    // Tylko wersja ARDUINO MEGA
     Serial.begin(1440,SERIAL_8N1);
   }

Jak widać na powyższym przykładzie można dokonać inicjalizacji portu szeregowego za pomocą jednego lub dwóch argumentów. W przypadku, gdy nie poda się drugiego argumentu system zastosuje domyślnie 8N1 czyli osiem bitów danych, bez kontroli parzystości, jeden bit stop. Możliwe typowe prędkości transmisji to: 300, 600, 1200, 2400, 4800, 9600, 14400, 19200, 28800, 38400, 57600, lub 115200. Możliwe jest również uzyskanie innej nietypowej prędkości transmisji (poprzez podanie jej wartości). Dopuszczalne predefiniowane stałe dla ramki to:

  • SERIAL_5N1
  • SERIAL_6N1
  • SERIAL_7N1
  • SERIAL_8N1 (domyślnie)
  • SERIAL_5N2
  • SERIAL_6N2
  • SERIAL_7N2
  • SERIAL_8N2
  • SERIAL_5E1
  • SERIAL_6E1
  • SERIAL_7E1
  • SERIAL_8E1
  • SERIAL_5E2
  • SERIAL_6E2
  • SERIAL_7E2
  • SERIAL_8E2
  • SERIAL_5O1
  • SERIAL_6O1
  • SERIAL_7O1
  • SERIAL_8O1
  • SERIAL_5O2
  • SERIAL_6O2
  • SERIAL_7O2
  • SERIAL_8O2

W przypadku, gdy nie ma konieczności zmiany sposobu wysyłania danych zachęcam do pozostawienia domyślnego kształtu ramki. Istnieją jednak sytuacje, gdy zmiana jest konieczna, żeby odebrać dane z innego urządzenia, lub wysłać gdzieś dane w konkretnie zdefiniowanym formacie.

Jak ważne jest odpowiednie zdefiniowanie ramki i prędkości sprawdzimy na przykładzie:

void setup()
{
Serial.begin(9600,SERIAL_7O1);
}

void loop()
{
  delay(500);
Serial.println("test");
}

Po uruchomieniu terminala w ArduinoIDE zamiast przesyłanego tekstu "test" otrzymamy "jakieś krzaczki". Zmodyfikujmy więc program:

void setup()
{
Serial.begin(9600,SERIAL_8N1);
}

void loop()
{
  delay(500);
Serial.println("test");
}

W tym momencie w terminalu powinno się pojawić zgodnie z oczekiwaniami słowo test wypisywane dwa razy na sekundę. Zmieńmy w terminalu prędkość odbioru danych z 9600 na 19200bps. Znów pojawią się krzaczki, zamiast oczekiwanego tekstu. Jak widać ustalenie identycznych parametrów przesyłu informacji jest niezbędne do prawidłowego jej zrealizowania.

Serial.print(), Serial.println()

Metoda Serial.print() umożliwia  wysłanie informacji poprzez port szeregowy. Możliwe jest przesłanie zarówno tekstów jak i wartości zmiennych, czy bezpośrednio liczb. Dane przesyłane są w postaci kodów ASCII. Oznacza to, że liczba np. 12 zostanie przesłana jako dwa znaki 1 i 2. Poniżej przedstawiono typową składnię Serial.print():

Serial.print(78)              // liczba 78
Serial.print(1.23456)         // liczba  1.23
Serial.print('N')             // znak:  N
Serial.print("Hello world.")  // tekst:  Hello world. 

Serial.print(78, BIN)         // liczba binarna: 1001110
Serial.print(78, OCT)         // liczba ósemkowa: 116
Serial.print(78, DEC)         // liczba dziesiętna: 78
Serial.print(78, HEX)         // liczba szesnastkowa: 4E
Serial.println(1.23456, 0)    // pierwsza cyfra: 1
Serial.println(1.23456, 2)    // trzy pierwsze cyfry: 1.23

Pojedyncze znaki przesyłamy podając je w apostrofach. Teksty umieszczamy w cudzisłowiu. Liczby wpisujemy same. Możliwa jest konwersja liczb na odpowiedni format - domyślnie ustawiono format dziesiętny. 

Odmianą metody Serial.print() jest metoda Serial.println(), która różni się od pierwszej dodaniem znaku końca wiersza po przesłaniu danego komunikatu. Poniższy przykład pokazuje różnice w użyciu poszczególnych metod:

Serial.print('A');               
Serial.print('B');
Serial.println('C');            //    ABC
Serial.print('D');              //    DE
Serial.println('E');            //    F
Serial.println('F');            //    G
Serial.print('G');

Serial.write()

W odróżnieniu od  metod Serial.print()Serial.println() metoda Serial.write() umożliwia przesłanie jednego bajtu informacji (liczby). Poniżej znajduje się składnia Serial.write():

Serial.write(liczba);
Serial.write("tekst");
Serial.write(bufor,długość);

 

Przykłady wykorzystania metody Serial.write():

byte a[]={65,66,67,68,69};
void setup()
  {
   Serial.begin(9600);
  }

void loop()
  {
   Serial.print(65);        // prześle do terminala dwa znaki 6 i 5
   Serial.write(65);        // przesle do terminala kod 65, (A w kodzie ASCII)
   Serial.write(a,3);       // prześle do terminala kody 65, 66, 67 (A, B, C)
   delay(800);
  }

 Jak widać z porównania metod wysłanie liczby np. 65 za pomocą metody Serial.print() spowoduje wysłanie dwóch znaków 6 i 5, natomiast za pomocą Serial.write() zostanie wysłana liczba 65, co w terminalu zostanie zinterpretowane jako kod ASCII 65 czyli litera "A".

Serial.available() 

Metoda Serial.available() umozliwia sprawdzenie, czy można odczytać dane z portu szeregowego.  Arduino posiada 64-bajtowy bufor portu szeregowego. Metoda wywołaywana jest bez parametrów, zwraca liczbę dostępnych do odczytania bajtów. Metoda Serial.available() umożliwia wykonanie części programu odczytującej dane z portu szeregowego dopiero, gdy te dane się pojawią. Przykłady wykorzystania Serial.available():

if(Serial.available())
   {
      //wykonaj, jeśli dostępne dane
   }

while(!Serial.available());       // czekaj na dane z portu szeregowego

Pierwszy przykład pokazuje zastosowanie sprawdzenia dostępnych danych i w przypadku, gdy dane odczytano wykona się fragment kodu. Składnia taka umieszczona w funkcji loop() nie blokuje jej wykonania. W drugim przykładzie program zostanie zatrzymany do czasu, aż w buforze pojawi się informacja odczytana z portu szeregowego. Poniżej przedstawiono praktyczne wykorzystanie metody Serial.available():

void setup()
  {
   Serial.begin(9600);
  }
void loop()
  {
   if(Serial.available())
      {
        int x=Serial.available();
        Serial.println(x);
      }
   delay(80);
  }

Program sprawdza, czy są do odczytania dane z portu szeregowego. W przypadku, gdy bufor zawiera informację program sprawdza ile bajtów jest do odczytania i przesyła tą informację na terminal. Po uruchomieniu programu i włączeniu terminala żadne informacje nie są przesyłane. Po wpisaniu tekstu, czy liczby i wysłaniu poprzez terminal arduino zacznie w pętli wysyłać ilość znaków dostępnych w buforze portu szeregowego. Dzieje się tak, ponieważ żadne informacje nie zostały odczytane z bufora, a metoda Serial.available() za każdym razem zwraca aktualną liczbę bajtów do odczytania.

serialEvent()

Funkcja serialEvent() to specjalna funkcja taka jak loop(), czy setup(), która zostanie wywołana w momencie, gdy do bufora szeregowego trafi przesłana informacja. Umożliwia ona wykonanie odpowiedniej akcji niezależnie od wykonywanego właśnie kodu. najczęściej w funkcji tej umieszcza się instrukcje odczytu danych z portu szeregowego. Przykład zastosowania serialEvent() podano poniżej:

void setup()
   {
       // ustawienia wstępne
   }
void loop()
   {
      // kod programu
   }
void serialEvent()
   {
      // kod do wykonania podczas odbioru danych
   }

UWAGA!!!: Zgodnie z dokumentacją ARDUINO funkcja ta nie występuje w układach Esplora, Leonardo oraz Micro.

Serial.read()

Odczytanie danych z bufora portu szeregowego umożliwia metoda Serial.read(). Pobiera ona po jednym bajcie dane zmniejszając liczbę dostępnych informacji w buforze. Poniżej przedstawiono przykład wykorzystania metody Serial.read():

void setup()
  {
   Serial.begin(9600);
  }
void loop()
  {
   if(Serial.available())
      {
        byte liczba=Serial.read();  //odczyt liczby
        Serial.println(liczba);
        char znak=Serial.read();    //odczyt kodu znaku
        Serial.println(znak);
      }
   delay(80);
}

Powyższy program odczytuje dane z portu szeregowego i przesyła je na terminal. W przykładzie zastosowano dwa sposoby interpretacji danych. W pierwszym przypadku bajt odczytany z portu traktowany jest jako liczba. Na terminalu wyświetli się odpowiadający znakowi kod ASCII, w drugim przypadku odczytany bajt traktowany jest jako kod ASCII. Po uruchomieniu programu i wpisaniu litery "a" w pierwszym przypadku otrzymamy kod "97", a w drugim literę "a".

Serial.readBytes()

Metoda Serial.readBytes() umożliwia odczytanie większej ilości bajtów z portu szeregowego i umieszczenie danych w buforze. Metoda zakończy działanie w momencie, gdy zostanie odczytana odpowiednia liczba bajtów, lub upłynie określona ilość czasu. Składnia Serial.readBytes() przedstawiona została poniżej:

Serial.readBytes(bufor, długość);

Metoda zwraca liczbę bajtów odczytanych do bufora, lub zero w sytuacji, gdy żadne dane nie zostały odczytane.

Serial.readBytesUntil()

Metoda Serial.readBytesUntil() jest odmianą metody Serial.readBytes(), umożliwia odczytanie większej ilości bajtów z portu szeregowego i umieszczenie danych w buforze. Metoda zakończy działanie w momencie, gdy zostanie przesłany zadeklarowany kod końca danych, zostanie odczytana odpowiednia liczba bajtów, lub upłynie określona ilość czasu. Składnia Serial.readBytes() przedstawiona została poniżej:

Serial.readBytesUntil(znak, bufor, długość);

Metoda zwraca liczbę bajtów odczytanych do bufora, lub zero w sytuacji, gdy żadne dane nie zostały odczytane.

Serial.setTimeout()

Metoda Serial.setTimeout() umożliwia zdefiniowanie czasu oczekiwania przez metody Serial.readBytes()Serial.readBytesUntil() na dane z portu szeregowego. Domyślnie w systemie ustawiono 1000ms (1s).

Serial.setTimeout(czas)     // czas w minisekundach (typ long) 

Poniżej przedstawiono przykład wykorzystania metod Serial.readBytes(), Serial.readBytesUntil() oraz Serial.setTimeout():

void setup()
   {
    char buf[10];
    Serial.begin(9600);
    Serial.setTimeout(10000);              // limit czasu 10s
    while(!Serial.available());            // czekaj na znak z portu
    Serial.println("Start");
    int x=Serial.readBytes(buf,4);         // odczytaj 4 bajty
    Serial.print("Przeslano znakow: ");
    Serial.println(x);

    while(!Serial.available());
    Serial.println("Start");
    x=Serial.readBytesUntil('X',buf,4);    // odczytaj 4 bajty
    Serial.print("Przeslano znakow: ");
    Serial.println(x);
   }

void loop(){}

W pierwszej części powyższego przykładu nastąpi odczyt czterech bajtów danych, pod warunkiem, że zmieści się ta operacja w czasie 10 sekund. W drugiej części dodatkowo może zostać przerwane wykonywanie odczytu, gdy w buforze pojawi się określony kod (tu zdefiniowany jako "X").

Serial.find(), Serial.findUntil()

Metoda Serial.find() odczytuje zawartość bufora w poszukiwaniu określonego ciągu znaków. Metoda zwraca prawdę, gdy ciąg znaków zostanie odnaleziony i fałsz, gdy dane nie zostaną odnalezione. Poniżej przedstawiono przykładowy kod programu:

void setup()
  {
   Serial.begin(9600);
  }
void loop()
  {
   if(Serial.find("test")) Serial.println("Ok.");
  }

Program odczytuje dane z bufora i wyświetla komunikat, gdy dopasuje szukany ciąg znaków do przesłanych informacji. Serial.find() oczekuje na dane przez czas zdefiniowany za pomocą Serial.setTimeout().  W przypadku, gdy w buforze znajduje się większa ilość danych możliwie jest prawie natychmiastowe ich pobranie, gdy bufor jest pusty metoda czeka na kolejną odczytaną daną przez pewien czas i kończy zwracając odpowiednią wartość.

Serial.findUntil() jest odmianą Serial.find(), która różni się od poprzednika dodatkowym argumentem umożliwiającym przerwanie odczytu bufora portu szeregowego. Przykładowa składnia pokazana została poniżej:

Serial.findUntil("tekst","K");

Metoda będzie odczytywała dane z portu szeregowego do czasu aż odczyta znak "K", zdefiniowany ciąg znaków (tekst) lub upłynie zdefiniowany czas (domyślnie sekunda).

Serial.flush()

Metoda Serial.flush() umożliwia wstrzymanie wykonywania programu do czasu wysłania danych poprzez port szeregowy. Starsze wersje ArduinoIDE jednocześnie czyściły bufor odbiorczy portu szeregowego.

Serial.peek()

Metoda Serial.peek() umożliwia odczytanie kolejnego bajtu znajdującego się w buforze odbiorczym bez jego usunięcia. Wielokrotny odczyt danych spowoduje odczytanie tej samej informacji do czasu użycia Serial.read() lub pochodnych. W przypadku, gdy bufor jest pusty metoda zwraca wartość -1.

Serial.parseInt()

Metoda Serial.parseInt() umożliwia odczytanie kolejnej prawidłowej wartości całkowitej. W przypadku przekroczenia czasu zwracana jest wartość 0.

Serial.parseFloat()

Metoda Serial.parseFloat() umożliwia odczytanie kolejnej prawidłowej wartości rzeczywistej. W przypadku przekroczenia czasu zwracana jest wartość 0.

Podsumowanie

W ten sposób dotarliśmy do końca kursu programowania ARDUINO. Pozostało jeszcze wiele bibliotek, zarówno dołączonych do ArduinoIDE, jak i do ściągnięcia z sieci. Opis ich wszystkich nie ma jednak większego sensu. Część z nich pojawi się w projektach na naszej stronie i wtedy zostaną częściowo opisane. Zachęcam do eksperymentów i uruchamiania różnych przykładów, z których można nauczyć się wiele....