C++ --- Komunikacja RS232

Krótkie mikroporadniki dotyczące określonych sytuacji i problemów , z którymi się spotykamy na co dzień pisząc programy
w VS C++ , oraz minimalistyczne podejście do rozwiązania problemu.
ODPOWIEDZ
Awatar użytkownika
SunRiver
Użytkownik
Posty: 939
Rejestracja: 08 paź 2017, 11:27
Lokalizacja: Festung Oppeln
Kontakt:

C++ --- Komunikacja RS232

Post autor: SunRiver »

Tym razem w języku C++  poznamy podstawy komunikacji przez port COM. Wielu zakrzyknie że przecież portu COM już niema w żadnym komputerze
, tak to prawda , ale są przejściówki na układach FTDI czy Microchipa i innych , które to udostępniają virtualny port COM poprzez USB. Ponadto
połączenie RS232 przez port COM  to nadal podstawa wymiany informacji między wieloma urządzeniami z komputerem.
Obiektem naszego zainteresowania będzie sterowanie diodą z komputera  podłączoną do płytki Arduino , czy też innego klona tej platformy.  
Nasz pierwszy program popełnimy w konsoli dla ułatwienia. To co zaczynamy ??

Przejdź do FILE-NEW-Project i wybierz nowy projekt CLR CONSOLE APP jak widać na obrazku niżej. Nadaj nazwę projektowi np.
"ArduinoComm" i kliknij OK.

Obrazek

Wybierz Plik ArduinoComm.cpp i otwórz go dwuklikiem :

Obrazek

Uff.... teraz musimy sobie ustalić co właściwie ma robić nasz program :)

> Poprosi użytkownika o wprowadzenie portu szeregowego który chcemy używać i przechowa jego nazwę w zmiennej PortName
> Następnie Spyta czy chcemy włączyć światło czy nie
-- jeśli tak zostanie wysłane 1 włączenia portu
-- jeśli nie zostanie wysłane 0 wyłączenie portu
-->> Program będzie pracował w pętli czekając na zadanie od użytkownika lub jego zakończenie
-->> Musimy tez ustalić szybkość transmisji użytej do komunikacji z naszą płytką

W tym celu dodamy do naszego programu parę linijek kodu z podstawowymi funkcjami:
  1. String^ answer;
  2.         String^ portName;
  3.         int baudRate=9600;
  4.         Console::WriteLine("Podaj nazwę portu COM i wcisnij ENTER");
  5.         portName=Console::ReadLine();

I w ten prosty sposób już działa nasz port COM:) ale .... warto nadmienić, że ...

.NET Framework zawiera gotowe klasy zdefiniowane dla portu szeregowego. Nazywa sie ona SerialPort i jest dostępna w przestrzeni nazw
"SYSTEM:: IO:: Ports " Będziemy używać również innych metod w przestrzeni nazw SYSTEM dlatego na początek użyjemy tej:
dopisując do naszego kodu linijki:
  1. using namespace System;
  2. using namespace System::IO::Ports;
Port szeregowy jako obiekt potrzebuje nazwy portu i szybkości transmisji w swoim konstruktorze , dodamy więc następujące wiersze do kodu.
W naszym przypadku Obiekt Serial PORT będzie się nazywał "ARDUINO".
  1. SerialPort^ arduino;
  2. arduino = gcnew SerialPort(portName, baudRate);
Otwarcie PORTU:

podobnie jak w przypadku plików tekstowych pierwszą rzeczą jaką musimy zrobić jest ich otwarcie. 
Open jest metodą klasy portu szeregowego, która jest częścią stworzonego obiektu ARDUINO. Dopiszemy teraz więc metodę otwarcia:
  1. arduino->Open();
W ten sposób możemy już otworzyć port i nawiązać połączenie.

PĘTLA:

W naszym Programiku użyjemy pętli "do while" W pętli tej zmiennej string przypiszemy przechowywanie odpowiedzi na 2 pytania :

>Czy chcesz kontynuować?
>Włączyć lub Wyłączyć diodę ?

  1. do
  2. {}
  3. while(String::Compare(answer,"yes")==0);
Nasza pętla jest prosta , ale nieco pusta więc czas na ...

Ciało pętli:

Wysyłanie danych ze zmiennej String do tablicy obiektu ARDUINO odbędzie się za pomocą metody WriteLine.
Dodamy następne linijki kodu, myślę że ich znaczenie jest oczywiste:
  1. //  on lub off
  2. Console::WriteLine("Wpisz \"on\" by włączyć lub \"off\" by wyłączyć");
  3. // uzyskana odpowiedź
  4. answer=Console::ReadLine();
  5. //sprawdzenie czy wpisano jedną z opcji
  6. if(String::Compare(answer,"on")==0)
  7. arduino->WriteLine("1"); // wysyła 1 do sunduino
  8. else if(String::Compare(answer,"off")==0)
  9. arduino->WriteLine("0"); // wysyła 0 do Sunduino
  10. else
  11. Console::WriteLine(answer+" nie wybrano żadnej opcji");
  12. // Pytanie czy kontynuować
  13. Console::WriteLine("Jeszcze RAZ? yes/no");
  14.                        
  15. answer=Console::ReadLine();
  16. // czyszczenie ekranu
  17. Console::Clear(); // czyszczenie ekranu konsoli
Jak widzicie to jest banalnie proste, a jakże skuteczne. Jednak żeby nie było niedomówień ....

NASZ pełny program w C++ :

powinien wyglądać następująco:
  1. #include "stdafx.h"
  2.  
  3. using namespace System;
  4. using namespace System::IO::Ports;
  5.  
  6. int main(array<System::String ^> ^args)
  7. {
  8.  
  9.         String^ answer;
  10.         String^ portName;
  11.         int baudRate=9600;
  12.         Console::WriteLine("Wpisz nazwę portu i wcisnij ENTER");
  13.         portName=Console::ReadLine();
  14.         //
  15.         SerialPort^ arduino;
  16.         arduino = gcnew SerialPort(portName, baudRate);
  17.        
  18.         // otwarcie portu
  19.         try
  20.         {
  21.                 arduino->Open();
  22.  
  23.                 do
  24.                 {
  25.                        
  26.                 Console::WriteLine("Wpisz \"on\" by właczć lub \"off\" by wyłączyć");
  27.                        
  28.                         answer=Console::ReadLine();
  29.                        
  30.                         if(String::Compare(answer,"on")==0)
  31.                                 arduino->WriteLine("1");
  32.                         else if(String::Compare(answer,"off")==0)
  33.                                 arduino->WriteLine("0");
  34.                         else
  35.                                 Console::WriteLine(answer+" nie wybrano żadnej opcji");
  36.                                                 Console::WriteLine("Jeszcze raz? yes/no");
  37.                        
  38.                         answer=Console::ReadLine();
  39.                        
  40.                         Console::Clear();
  41.                 }while(String::Compare(answer,"yes")==0);
  42.                
  43.                 arduino->Close();
  44.         }
  45.         catch (IO::IOException^ e  )
  46.         {
  47.                 Console::WriteLine(e->GetType()->Name+": Port nie gotowy!");
  48.         }
  49.         catch (ArgumentException^ e)
  50.         {
  51.                 Console::WriteLine(e->GetType()->Name+": nie prawidłowa nazwa portu musi sie zaczynac od COM");
  52.         }
  53.        
  54.         Console::Write("Wciśnij ENTER by skończyć program");
  55.         Console::Read();
  56.     return 0;
  57. }


I to wszystko możemy projekt zapisać i skompilować , program od strony PC jest gotowy. teraz jednak musimy napisać program dla
naszej płytki żeby sprawdzić czy wszystko działa tak jak chcieliśmy:

Obrazek

Otwieramy zatem Arduino IDE lub VSC czy VS z wtyczkami i piszemy :
  1. int ledPin = 13;
  2. int state=0;
  3. void setup() {
  4.     pinMode(ledPin, OUTPUT);
  5.     Serial.begin(9600);
  6. }
  7.  
  8. void loop() {
  9.   if (Serial.available() > 0)
  10.   {
  11.     state = Serial.read();
  12.  
  13.     switch(state)
  14.     {
  15.       case '1':
  16.         digitalWrite(ledPin,HIGH);
  17.       break;
  18.       case '0':
  19.         digitalWrite(ledPin,LOW);
  20.       break;
  21.       default:
  22.       break;
  23.     }
  24.   }
  25. }
W zasadzie program jest tak banalny, ze nie musimy go omawiać , ale warto wspomnieć, że połączenie RS232 nawiązujemy
z szybkością 9600bps. Teraz możemy skompilować nasz program i wgrać do mikrokontrolera.
I to na tyle , najprostsza metoda sterowania z poziomu komputera diodą led na naszej płytce ładnie działa.
Pobawcie się trochę :)

oczywiście w Visual Studio C++ Express lub Community , możemy stworzyć coś poważniejszego i ładniejszego ...
dlatego teraz zajmiemy się trochę bardziej zaawansowaną obsługą połączenia RS232 z naszą płytką, gdyż nie tylko będziemy
wysyłać komendę do płytki , ale też i odbierać i prezentować wyniki pomiaru na komputerze w okienkowej aplikacji ....

Zaczynamy:

Po uruchomieniu VC++ wybieramy typ projektu tym razem zamiast jak wcześniej projekcie CLR Console Application wybieramy :
Windows Form Application . Dzięki temu nasz program będzie prawdziwie Winzgrozowy i przyprawiał niektórych o mdłości ...
Nadajemy też naszemu projektowi nazwę co widać na screenie poniżej np: Woltomierz i klikamy ok.

Obrazek

Teraz po dłuższej chwili system wygeneruje całe stado na pozór zbędnych plików.

Obrazek

Wszystko ładnie widać po lewej stronie. Dla nas szczególnie interesujący jest Header Files i zawarty tam plik Form1.h,
który otwarty widać pośrodku ekranu. Jak widać jest to puste okienko windowsowe. Możemy tu dowolnie modyfikować jego rozmiar,
oraz inne parametry jak nazwa co czynimy w okienku Properities po prawej stronie ekranu -- na fotce schowany za toolbox,
jak też dodawać nowe elementy, które są dostępne w widocznym właśnie po prawej Toolbox-ie.

Przystępujemy do budowy/rysowania aplikacji:

W naszym okienku musimy pododawać potrzebne nam klawisze i pola oraz opisy zatem do dzieła:

Jako że będziemy używać portu serial -- wyszukujemy na toolboxie w sekcji Components kontrolki serialPort i przeciągamy na nasze okienko
jak widać kontrolka układa się u dołu ekranu, podobnie postępujemy z kontrolka Timer. Teraz poumieszczamy przyciski checkboxy i labele
wszystkie znajdziemy w toolboxie w sekcji Common Controls - wybieramy nam potrzebne i przeciągamy na nasze okienko gdzie teraz
możemy modyfikować ich parametry jak rozmiar i opisy w properities,  a nasza aplikacja może wyglądać np tak:

Obrazek

Właściwie już na tym etapie możemy skompilować nasz program i będzie on działał , ale nie tak jak chcemy bo przecież mamy tylko okienko z przyciskami i nic więcej. Teraz więc musimy pokazać elementom do czego mają służyć.

Zatem do dzieła :

na pierwszy ogień weźmiemy nasz przycisk połącz klikamy go prawym klawiszem myszki i wybieramy View Code gdzie w pustej sekcji:
  1. private: System::Void button1_Click(System::Object^  sender, System::EventArgs^  e) {
  2.  
  3. }
musimy pokazać przyciskowi co ma robić więc opisujemy fragment kodu opisujący zachowanie naszego przycisku, a całość powinna wyglądać tak:
  1.  private: System::Void button1_Click(System::Object^  sender, System::EventArgs^  e)
  2. {
  3.  
  4.                                  if (!port_open) {
  5.  
  6.  
  7.                                          this->serialPort1->PortName=this->comboBox1->Text;
  8.                                          serialPort1->Open();
  9.                                          this->button1->Text="Zamknij";
  10.                                                  port_open=true;
  11.                                  
  12.                                  } else { serialPort1->Close();
  13.                                          this->button1->Text="Połącz";
  14.                                  port_open=false;}
  15.  
  16.  }
  17.                          
Od tej pory nasz klawisz wie, że ma otwierać port, ale też ma się zmieniać napis na klawiszu który będzie informował nas o możliwym działaniu
oraz czy port otwarty czy tez nie. No ładnie , ale nie wiemy jaki port ma być otwarty wiec zajmijmy się naszym drugim obiektem --- czyli naszą kontrolka serialPort  podobnie prawym klawiszem myszki - view Code i w jego sekcji dopisujemy:
  1. private: System::Void serialPort1_DataReceived(System::Object^  sender, System::IO::Ports::SerialDataReceivedEventArgs^  e) {
  2.  
  3. unsigned char data0, data1;
  4.  
  5. if (serialPort1->ReadByte()==0xAB) {
  6. data0=serialPort1->ReadByte();
  7. data1=serialPort1->ReadByte();
  8.  
  9. volty=Math::Round((float(data0*256+data1)/1024*5.00),2);
  10.  
  11.  
  12. data_count++;
  13.  
  14. }
  15. serialPort1->ReadByte();
  16.                          }
Jak widzicie opisałem zadania naszego portu , oraz umieściłem w zmiennej volty funkcję,
która odczytane dane z ADC przeliczy na napięcie i w tej postaci będzie wyświetlane w sekcji label1. Oczywiście szybkość portu określamy
w properities dla controlki na identyczną jak w programie dla płytki xxxduino.

Tak , ale mamy kilka portów serial i chcemy mieć możliwość wybierania no to do dzieła.
Pod przyciskiem połącz (button1) umieściliśmy combobox i w procedurce przycisku dodaliśmy zależność i wszystkie porty jakie
mamy pojawią się w naszym combo odpowiada za to ta linijka  kodu umieszczona w sekcji button1:
  1. this->serialPort1->PortName=this->comboBox1->Text;
Tak tu was postraszyłem ze będzie to trudne , ale kłopotem będzie wybieranie naszego kanału ADC i jego uruchomieniem. W tym celu w sekcji
naszego timera umieszczamy kod:
  1. his->label1->Text=Convert::ToString(volty)+ " V";
  2.  
  3. this->label3->Text=Convert::ToString(data_count*10)+ " reads/s";
  4. data_count=0;
a w sekcji listBox1:
  1. array<unsigned char>^start ={0xAC,(0x10+channel)};    
  2. array<unsigned char>^stop ={0xAC,0x00};
  3.                                  if (!adc_on) {
  4.                                          serialPort1->Write(start,0,2);
  5.                                           this->button2->Text="Stop";
  6.                                                  adc_on=true;
  7.                                  
  8.                                  } else {
  9.                                                          serialPort1->Write(stop,0,2);
  10.                                          this->button2->Text="Start";
  11.                                  adc_on=false;}

I to w zasadzie wszystko , ciężko cokolwiek opisać w przypadku VisualStudio i tworzenia aplikacji na windows gdyż wszechobecne kreatory skutecznie generują 98% kodu samym nam zostawiając niewiele roboty, czego nie opisałem znajdziecie w kompletnym źródle programu który po
wypakowaniu folderu otwieramy plikiem Voltage_Meter.sln

(Jak się znajdzie dodam)

A tak się prezentuje nasz programik po uruchomieniu :

Obrazek

Tymczasem czas na drugą połowę czyli program dla naszej płytki xxxDuino, który prezentuje się następująco:
  1. int voltage =0;
  2. int channel =0;
  3. int voltage1 =0;
  4. int chanel1 =1;
  5.  
  6. unsigned char incomingByte = 0;
  7. boolean measure=false;
  8.  
  9. void setup() {
  10.  
  11.   Serial.begin(9600);
  12. }
  13.  
  14. void loop() {
  15.  
  16.   if (measure) {
  17.  
  18.     voltage=analogRead(channel);
  19.     voltage=analogRead(chanel1);
  20.     Serial.print(0xAB,BYTE);
  21.     Serial.print(voltage>>8,BYTE);
  22.     Serial.print(voltage%256,BYTE);
  23.     Serial.print(voltage1>>8,BYTE);
  24.     Serial.print(voltage1%256,BYTE);
  25.     delay(50);
  26.  
  27.   }
  28.  
  29.   if (Serial.available() > 0) {
  30.     delay(10);
  31.  
  32.     if(Serial.read()==0xAC) {
  33.       incomingByte =Serial.read();
  34.  
  35.       switch (incomingByte) {
  36.  
  37.       case 0x10:
  38.         measure=true;
  39.         channel=0;
  40.         chanel1=0;
  41.         break;
  42.  
  43.       case 0x11:
  44.         measure=true;
  45.         channel=1;
  46.         chanel1=1;
  47.         break;
  48.  
  49.       case 0x12:
  50.         measure=true;
  51.         channel=2;
  52.         chanel1=2;
  53.         break;
  54.  
  55.       case 0x00:
  56.         measure=false;
  57.         break;
  58.  
  59.       }
  60.  
  61.     }
  62.  
  63.   }                
  64.  
  65. };
Jak widać nic specjalnego tu niema, ale w razie pytań zapraszam do komentarzy.
Tymczasem po wgraniu naszego programu do płytki możemy się cieszyć naszym woltomierzem na PC
 ! Wiadomość z: Lothar TEAM
UWAGA !!

Zakres pomiarowy to 0 - 5V dla wyższych napięć należy się postarać o właściwy dzielnik napięcia.  Podłączenie napięcia wyższego niż 5V do pinu mikrokontrolera spowoduje trwałe jego uszkodzenie.
Żeby nie było ostrzegałem :)
Powodzenia
ODPOWIEDZ

Wróć do „..:: Na szybciora --- z cyklu Sun Poleca C++ z pieca...”