• Email
  • Forum

Aplikacja na Windows

Piszemy aplikację w darmowym Visual Studio 2010

Zgłębienie tego tematu do tego poziomu zajeło mi dwa dni.
Jak widać nie trzeba studiować informatyki by stworzyć taką aplikację.

Program będzie korzystał z portu COM i umożliwiał dwustronną komunikację ze sterownikiem.

[Rozmiar: 36897 bajtów]

Pisać będę na raty, a potem ten komentarz usunę ;)

Program - środowisko

Zacząć trzeba od ściągnięcia instalatora. Poniżej link do takiego jakiego użyłem ja. Jest to instalator Online, który sam sprawdza co potrzebne i instaluje tylko potrzebne elementy.

[Rozmiar: 7784 bajtów]

Program informuje, że po czasie będzie wymagał darmowej rejestracji w Microsofcie. Nie miałem z tym problemu bo konto na Microsofcie miałem już od dawna. To takie samo konto jak w Google. Tez daje dostęp do przestrzeni dyskowej OneDrive i tym podobnych usług.

Pierwsze spojrzenie

Kiedy uda się pomyślnie zainstalować oczom naszym ukaże się coś takiego. Obrazki będą mniejsze by strona się wczytywała, ale dostępne są po kliknięciu w powiększeniu. Z tego co pamiętam to chyba program zabiera na szybką wycieczkę albo tworzenie przykładowego programu do przeglądania zdjęć. Można się bawić.

[Rozmiar: 37989 bajtów]

Jak już opanujemy tworzenie nowego projektu to wyświetli się okienko nowo tworzonego programu Form1. Klikając raz na ten element wyświetlą nam się w okienku po prawej jego "Properties" - właściwości. Tam na pozycji Text możesz już nazwać swój projekt. Rozciągnij go i umieść w nim GroupBoxy bo potem ładnie wyglądają i pozwalają podpisać a nawet zarządzać naraz całą grupą. Do projektu przyda się też przeciągnąć port Com i Timery. Może się zdarzyć że na jakiś element klikniesz dwukrotnie Wtedy otworzy się karta w kodem programu. Możesz przełączyć się na kartę "Design" z powrotem jak w Chrome na razie. Tak potem znajduje się w kodzie część odpowiedzialną za obsługę np. przycisku lub dodaje taki kod.

Na koniec możesz nacisnąć zieloną strzałkę "Start Debuging" i cieszyć się widokiem przyszłego projektu.

[Rozmiar: 37989 bajtów]

Uruchomienie aplikacji w trybie debugowania ma same plusy. Nie kompiluje się długo i możemy co moment zmieniając jakiś parametr znów ja włączyć. Jeśli z jakiegoś powodu aplikacja spowoduje błąd i się wysypie to w normalnym trybie Windows dostaniemy suchy komunikat o błędzie. W trybie debugowania zostaniemy przeniesieni do linii kodu która powoduje problem. Dostaniemy tez podpowiedź co poszło nie tak :D. Przykład poniżej

[Rozmiar: 13661 bajtów]

To kawałek kodu odpowiedzialny za automatyczne połączenie ze sterownikiem jeśli zahaczony jest CheckBox1. Przed otwarciem portu musimy określić numer portu i prędkość. W tym przypadku ustawiony był numer portu który akurat nie istnieje, bo to wirtualny port USB który wyjąłem z gniazda. Nie mógł więc być przyjęty jako wartość i spowodował błąd. Takie błędy da się omijać, ale o tym później. Kiedy zaznajomisz się z treścią komunikatu wyłącz debugowanie by dokonać poprawek.

Wsparcie i przewodniki

Przy pisaniu bardzo pomocne mogą być poniżej podlinkowane poradniki video, oraz ..google :)
Szukając czegokolwiek do swojej aplikacji dopisz w wyszukiwarce po odstępie magiczne "vb.net"
Odpowiedzi w postaci dokumentacji są wtedy na msdn.microsoft.com, a przykłady rozwiązań problemów zadanych przez użytkowników na stackoverflow.com.

[Rozmiar: 4331 bajtów] [Rozmiar: 4401 bajtów]

IntelliSense

Zanim zaczniemy trzeba też wspomnieć o tym wynalazku, bo ktoś nieuprzedzony mógłby się zdenerwować. Kiedy zacznie się coś wpisywać to narzędzie IntelliSense będzie chciało nam pomóc i podsuwa szereg podpowiedzi pod wpisywanym przez nas tekstem. Takie lepsze T9 ;) Trzeba się tylko przyzwyczaić do zatwierdzania wyboru przyciskiem TAB, a nie Enter`em. Trzeba też patrzeć co się wybiera. Jeśli mamy CheckBoxy to będą tam ponumerowane, ale będzie też taki bez numeru i jak na złość jako pierwsza podpowiedź. Trzeba wybrać ten o który nam chodzi.

[Rozmiar: 13631 bajtów]

Jak po komendzie postawisz kropkę, to będzie chciało podpowiadać dalej... i tak do szczęśliwego końca :D

Importy i zmienne globalne

Żeby móc skorzystać z obsługi portów albo na przykład robiąc aplikację graficzną obsługiwać grafiki musimy do naszej aplikacji zaimportować zbiory instrukcji. Importujemy je na początku by potem móc używać zawartych w nich komend.

Zmienne możemy definiować bezpośrednio w Suba`ach, ale możemy wtedy na nich działać tylko w ich obrębie. Żeby móc wynik jakiegoś procesu odczytać w innym Sub`ie można zdefiniować na początku programu zmienną globalną. Widzianą dla wszystkich i wszędzie. Wbrew pozorom, często przydaje się właśnie to by wartość pozostała tylko w Sub`ie.

Żeby dodać potrzebne nam Importy i zadeklarować zmienne klikamy w Designerze na naszą aplikację. Najlepiej na jej pasek z nazwą. Otworzy się karta z kodem programu i przy okazji kodem który wykonuje się zaraz przy uruchamianiu naszego programu.

[Rozmiar: 8796 bajtów]

Importy wpisujemy nad wszystkim. Na samej górze. Zmienne globalne wpisujemy do Public Class nad wszystkimi prywatnymi.

Imports System
Imports System.Threading
Imports System.IO.Ports
Imports System.ComponentModel

Public Class Form1

    Dim myPort As Array              'posłuży do zebrania informacji o wszystkich dostępnych portach
    Dim TestArray As String()        'zmienna Test podzielona/rozdzielona na małe stringi z indeksami
    Dim Test As String = ""          'wiadomość odebrana ze sterownika, po testach nazwa została :D
    Dim tryb As Integer              'tryb w jakim pracuje sterownik ręczny/automatyczny
    Dim zaczekaj As Boolean          'pomocniczy Bit by pominąć jeden odczyt
	

My Settings czyli pamiętane zmnienne

Muszę w tym miejscu wrócić na chwilę do ustawień projektu. Przy starcie programu potrzebne bowiem będą zmienne których ustawienia są pamiętane i odczytywane. Trzeba je zadeklarować na osobnej karcie w ustawieniach.

[Rozmiar: 23382 bajtów]

Tu, wspomnę o czymś bo może ktoś nie rozumie. Ze szkoły wiemy że 2 x 2 = 4. W języku programowania znak "równa się" jest jednak inaczej interpretowany. Jest to znak przyrównania. Wartość lub wynik operacji działań po prawej stronie znaku jest przypisywany jako wartość dla zmiennej stojącej po lewej stronie znaku. Żeby więc jakiemuś parametrowi w naszym programie nadać wartość na starcie, pamiętaną w ustawieniach, to trzeba ten parametr przyrównać do pamiętanej zmiennej. I tak dla przykładu przy starcie programu:

Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
    myPort = IO.Ports.SerialPort.GetPortNames()    'myPort przyjmuje informacje o dostępnych portach COM
    portComboBox.Items.AddRange(myPort)            'rozwijana lista portów przyjmuje wartości z myPort
    baudComboBox.Text = My.Settings.Baud_rate      'tekst przyjmuje wartość ostatnio zapamiętanego wyboru
    portComboBox.Text = My.Settings.Port_numer     'tekst nazwy portu przyjmuje zapamiętaną wartość
    CheckBox2.Text = My.Settings.Aux1              'nazwa dla dodatkowego wyjścia przyrównana do zapisanej
    CheckBox3.Text = My.Settings.Aux2
    CheckBox1.Checked = My.Settings.Autostart      'wczytuje z pamięci czy połączyć automatycznie
		 

Natomiast w drugą stronę. Żeby ustawienia "się pamiętały" trzeba w Desgnerze kliknąć dwukrotnie w CheckBoxa którego stan chcemy pamiętać i otworzyć obsługujący to zdarzenie kod.

Private Sub CheckBox1_CheckedChanged(sender As System.Object, e As System.EventArgs) Handles CheckBox1.CheckedChanged	
    My.Settings.Autostart = CheckBox1.Checked  'zmienna Autostart w My.Settings przyjmuje wartość CheckBoxa
End Sub
	  

Rozwijane listy wyboru - ComboBox`y

Wróćmy na moment do Designera i, jeśli ktoś jeszcze tego nie zrobił, wybierzmy styl dla ComboBoxów i wpiszmy prędkości transmisji (Baudrate)

[Rozmiar: 23382 bajtów]

Gotowy do połączenia? Otwieramy port

Przy okazji omówię instrukcję Try, Catch, End Try, wyskakujące okienko oraz uaktywnianie przycisków.

Jeśli mamy przycisk "Połącz" to klikamy go w Designerze by otworzyć kod obsługujący jego przyciśnięcie i dodajemy instrukcje. Port będzie się nazywał tak jak wybraliśmy w portComboBoxie, prędkość transmisji taka jak w baudComboBoxie. Otwieramy port. To jest funkcja więc na końcu nawiasy. Uruchamiamy Timer o którym za chwilę. Timer będzie odliczał czas do odświeżania wyświetlaczy. Uaktywniamy lub dezaktywujemy wybrane przyciski lub nawet grupy. Można w ten sposób zapobiec wpisaniu czegoś gdy jeszcze nie ma to sensu. Na przykład dopiero od momentu połaczenia aktywny jest przycisk "Rozłącz."

 Private Sub Connect_button_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Connect_button.Click
       Try
        SerialPort1.PortName = portComboBox.Text
        SerialPort1.BaudRate = baudComboBox.Text
        SerialPort1.Open()                           'otwieramy port
        Timer1.Start()                               'uruchamiamy Timer1
        zaczekaj = True                              'pomijamy jeden cykl odczytu (niekonieczne)
        baudComboBox.Enabled = False                 'wyłączamy możliwość zmiany na otwartym porcie
        portComboBox.Enabled = False                 'możliwość wyboru portu nieaktywna
        Connect_button.Enabled = False               'połączony więc przycisk "Połącz" nieaktywny
        Temp_button.Enabled = True                   'aktywuj wysyłanie ustawionej temperatury
        TrackBar1.Enabled = True                     ' suwak do ustawiania PWM
        TrackBar2.Enabled = True                     ' suwak do ustawiania pilnowanej temperatury
        TrackBar3.Enabled = True                     ' suwak do ustawiania histerezy
        Disconnect_Button.Enabled = True             ' przycisk "Rozłącz"
        Button1.Enabled = True                       ' wybór trybu pracy
        Label6.Enabled = True                        ' automatycznym/ręcznym (tylko człon się zmienia)
        GroupBox6.Enabled = True                     ' dodatkowe wyjścia
      Catch	
                MessageBox.Show("Wybierz numer portu COM", "Ostrzeżenie")
      End Try
    End Sub
 

Instrukcje Try, Catch, End Try informują komputer ,że ma podjąć próbę. Jeśli się nie powiedzie z jakiegoś powodu to ma zrobić to, co po instrukcji Catch. Możemy tam nic nie wpisać, ale wtedy komputer przemilczy wszystko i nie będziemy wiedzieć dlaczego nic się nie wyświetla. Używając Try, Catch, End Try musimy użyć wszystkich trzech. Nie można pominąć Catch.

O tych poleceniach dowiedziałem się dużo później więc musiałem najpierw zadbać by program zachowywał się poprawnie bez nich. Nie używam ich też w trybie Debugowania. Dopisuję je do programu na końcu. Przydają się na nieprzewidziane zachowania. Np. przy otwieraniu plików użytkownik się rozmyśla.. W tym akurat przypadku "wyskoczy" okienko z podpowiedzią by wybrać jakiś port.

[Rozmiar: 13208 bajtów]

Podobnie postępujemy z przyciskiem "Rozłącz"

Private Sub Disconnect_Button_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Disconnect_Button.Click

        Timer1.Stop()                           'zatrzymaj Timer1
        Connect_button.Enabled = True           'aktywuj i dezaktywuj przyciski
        Temp_button.Enabled = False
        PWM_button.Enabled = False
        Disconnect_Button.Enabled = False
        TrackBar1.Enabled = False
        TrackBar2.Enabled = False
        TrackBar3.Enabled = False
        Button1.Enabled = False
        baudComboBox.Enabled = True
        portComboBox.Enabled = True
        Label6.Enabled = False
        GroupBox6.Enabled = False
        SerialPort1.Close()                     'zamknij port
End Sub

Autostart

Nie jest tak ważne żeby program sam się łączył ze sterownikiem jeśli tak zaznaczymy. Ważne jest żeby przy starcie program do portComboBoxa wpisał dostępne w danej chwili porty COM. Mając jednak CheckBox1 sprawdzam go i program może się łączyć automatycznie.

Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        myPort = IO.Ports.SerialPort.GetPortNames() 'najważniejszy tutaj zapis
        portComboBox.Items.AddRange(myPort)
        baudComboBox.Text = My.Settings.Baud_rate
        portComboBox.Text = My.Settings.Port_numer
        CheckBox2.Text = My.Settings.Aux1
        CheckBox3.Text = My.Settings.Aux2
        CheckBox1.Checked = My.Settings.Autostart    'odczytaj czy ma połączyć automatycznie

        If CheckBox1.Checked = False Then            'jeśli NIE zaznaczono "Połącz automatycznie"
            PWM_button.Enabled = False
            Temp_button.Enabled = False
            Disconnect_Button.Enabled = False
            TrackBar1.Enabled = False
            Button1.Enabled = False
            baudComboBox.Enabled = True
            portComboBox.Enabled = True
            Label6.Enabled = False
            GroupBox6.Enabled = False
        ElseIf CheckBox1.Checked = True Then         'jeśli zaznaczono TAK
            Try                                      'to spróbuj
                SerialPort1.PortName = portComboBox.Text
                SerialPort1.BaudRate = baudComboBox.Text
                SerialPort1.Open()
                Timer1.Start()
                zaczekaj = True
                baudComboBox.Enabled = False
                portComboBox.Enabled = False
                Connect_button.Enabled = False
                Temp_button.Enabled = True
                TrackBar1.Enabled = True
                Disconnect_Button.Enabled = True
                Button1.Enabled = True
                Label6.Enabled = True
                GroupBox6.Enabled = True
            Catch                                      'jeśli cos pójdzie źle
                MessageBox.Show("Wybierz numer portu COM", "Ostrzeżenie") 'pokaż ostrzeżenie
            End Try
        End If
    End Sub

W kodzie brakuje jeszcze obsługi portComboBox i baudComboBox. Klikamy je więc po kolei w Designerze i dodajemy instrukcję obsługi zdarzenia gdy ktoś będzie chciał nimi coś wybrać w programie.

Private Sub portComboBox_SelectedIndexChanged(sender As System.Object, e As System.EventArgs) Handles portComboBox.SelectedIndexChanged
        My.Settings.Port_numer = portComboBox.Text
    End Sub
Private Sub baudComboBox_SelectedIndexChanged(sender As System.Object, e As System.EventArgs) Handles baudComboBox.SelectedIndexChanged
        My.Settings.Baud_rate = baudComboBox.Text
    End Sub

Obsługa portu

Timery i Port trzymają się razem w lewym rogu Designera bo to komponenty. Kilkamy więc raz na Port by pokazała się jego karta Properties. W niej u góry znajdujemy żółty obrazek przedstawiający piorun. Będzie podpisany jako Events. Klikamy go i wybieramy "DataReceived" dwukrotnie klikając tak by otworzyć kod programu który obsługuje to zdarzenie

[Rozmiar: 10748 bajtów]

Deklarujemy Incomming jako String do którego trafiaja odebrane dane z Portu. Na końcu string przepisywany jest do zmiennej Test

Private Sub SerialPort1_DataReceived(sender As System.Object, e As System.IO.Ports.SerialDataReceivedEventArgs) Handles SerialPort1.DataReceived
        Dim Incoming As String = SerialPort1.ReadLine() 'są różne metody czytania portu, ta czyta linię
        Test &= Incoming
    End Sub

Podział wiadomości i rozdzielenie do okien i suwaków

Ten program współpracuje ze sterownikiem który wysyła co sekundę wiadomość z kilkoma parametrami systemu.
Był to mój pierwszy taki projekt i tak to rozwiązałem, że wysyła je rozdzielone spacją a program je po tych spacjach dekoduje.Parametry można też zmieniać przy samym sterowniku więc potrzebne jest odświeżanie wszystkich danych. Tak więc kawałek kodu z Atmegi wygląda tak:

 Tekst_pwm = Str(x)
 Tekst_t = Str(odczyt)
 Tekst_tryb = Str(tryb)
 Tekst_pilnuj = Str(pilnuj)
 Tekst_hist = Str(hist)
Message = Tekst_tryb + " " + Tekst_t + " " + Tekst_pwm + " " + Tekst_pilnuj + " " + Tekst_hist + " "
Print Message

Program sprawdza port co 500ms i dekoduje wiadomość. Żeby robił to z taką częstotliwością potrzebny jest Timer1. Klikamy raz by pokazała się karta jego właściwości i ustawiamy Interwał na 500

[Rozmiar: 13701 bajtów]

Po czym klikamy Timer1 znowu, tylko dwukrotnie żeby otworzyć kod który będzie się wykonywał co zadany interwał.

Private Sub Timer1_Tick(sender As System.Object, e As System.EventArgs) Handles Timer1.Tick

        Dim TestArray() As String = Split(Test)  'podziel wiadomość na małe ponumerowane
        Try                                      'spróbuj
            If TestArray.Length > 4 Then         'jeśli wiadomość prawidłowa bo ma więcej niż 4 człony 
                TextBox3.Clear()                 'wyczyść okno PWM
                TextBox3.Text = TestArray(2)     'wpisz wartość trzeciej jako tekst (zaczyna się od zera)
                TextBox1.Clear()                 'wyczyść okno Temperatura
                TextBox1.Text = TestArray(1)     'wpisz wartość drugiej jako tekst
                TrackBar1.Value = TestArray(2)   'rusz suwak do wartości PWM
                TrackBar2.Value = 800 + TestArray(3) 'wartość dla suwaka temp. pilnowanej
                TextBox2.Clear()                 'wyczyść okno pilnowanej temp. 
                TextBox2.Text = TrackBar2.Value - 800   'wpisz wartość do okna
                TextBox4.Text = TestArray(4)     'histereza 
                TrackBar3.Value = TextBox4.Text
                If zaczekaj = False Then         'to było zabezpieczenie gdy sterownik odpowiadał echem 
                    tryb = CInt(TestArray(0))    'przekonwertuj na wartość
                Else
                    zaczekaj = False             'skasuj pominiecie odczytu
                End If

            End If
        Catch
        End Try
        If tryb = 0 Then                          'jeśli wykryto tryb 0 to zmień text w Label6
            Label6.Text = "automatycznym"
            PWM_button.Enabled = False
            TrackBar1.Enabled = False
            GroupBox4.Enabled = True
        ElseIf tryb = 1 Then
            Label6.Text = "ręcznym"
            PWM_button.Enabled = True
            TrackBar1.Enabled = True
            GroupBox4.Enabled = False
        End If

        Test = 0 'zeruj zmienna Test (powinno być Nothing ale działało :)

    End Sub

Teraz jeśli wyślesz taką wiadomość na port to program ją zdekoduje i umieści w okienkach.

Timer2 i wysyłanie komend do portu

Suwaki reprezentują też aktualne wartości i z powodu co półsekundowego odświeżania niemożliwe było by ustawienie nimi czegokolwiek. Zrobiłem więc tak, że ruszenie suwakiem wyłącza Timer1 odpowiedzialny za odświeżanie i uruchamia drugi Timer2 który to po zadanym czasie, 5 sekund, się wyłącza włączając najpierw z powrotem Timer1. Masz więc 5 sekund na wysłanie nowej zadanej sterownikowi wartości. Ustawiamy więc interwał na 5000 i klikamy podwójnie Timer2 by wyświetlić kod jego obsługi

Private Sub Timer2_Tick(sender As System.Object, e As System.EventArgs) Handles Timer2.Tick
        If Timer1.Enabled = False Then
            Timer1.Start()
            Timer2.Stop()
        End If
    End Sub

Tu zmieniamy jedną wartość suwakiem. Kliknij w suwak dwukrotnie by otworzyć kod obsługi zdarzenia kiedy ktoś go przesunie.

Private Sub TrackBar1_ValueChanged(sender As System.Object, e As System.EventArgs) Handles TrackBar1.ValueChanged
        Timer1.Stop()
        Timer2.Start()
        TextBox3.Text = TrackBar1.Value 'wartość wyświetla się w okienku

    End Sub

Po czym masz 5 sekund by nacisnąć "Ustaw"

Private Sub PWM_button_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles PWM_button.Click
        SerialPort1.Write(TrackBar1.Value & vbCr) 'wpisz do portu wartość z suwaka i zatwierdź "CarriageReturn"(enter)
        zaczekaj = True                           'gdy sterownik miał włączone Echo to pomijało jeden odczyt
    End Sub

Zostawiłem tu takie rozwiązania które już poprawiłem specjalnie żeby pokazać ile udało mi się dowiedzieć w dwa dni. Od tego czasu trochę się nauczyłem do czego i Ciebie zachęcam :) Nie zawsze wiąże się wszystko z czytanie opasłych tomów, albo zaczynaniem od zera. Masz Internet możesz więcej. Jeśli uważasz, że w opisie czegoś jeszcze brakuje to napisz na forum a ja postaram się temat opracować i dopisać dla Wszystkich. Np. nie opisałem jeszcze sposobu tworzenia ikonki do programu i pewnych ustawień: Twórz samodzielną aplikację, oraz Zapisuj ustawienia przy zamykaniu programu - dopiszę w wolnym czasie.

Email

Jeśli mogę w czymś pomóc, napisz.