Skocz do zawartości

[Programowanie] Port szeregowy i interfejs USART czyli komunikacja mikrokontrolera z komputerem


Pomocna odpowiedź

Z tego co wiem Windows zabrania bezpośredniego odwoływania się i trzeba to robić przez WinAPI. Pod linuksem działa jak odczyt/zapis pliku, co ma wady i zalety, ale ogólnie programuje się prościej. Korzystać z IRQ można chyba tylko w DOSie czy czymś podobnym.

Jeżeli ktoś chce ominąć WinAPI to może chyba odwołać się bezpośrednio do funkcji BIOSu poprzez napisanie odpowiednich funkcji w Asemblerze i dołączenie ich do projektu w języku wyższego poziomu. Serial port jest obsługiwany przez przerwanie 14 w wektorze przerwań. Tutaj link do jego poszczególnych opcji:

http://www.ctyme.com/intr/int-14.htm

Panowie wydaje mi się że zbyt odbiegamy od (wydaje mi się) zamierzenia tego artykułu...

Od stwierdzenie dla początkujących - podstawy programowania, przeszliśmy do zagadnienia programowania w asemblerze, zejście do najniższej warstwy programowej jakim jest bios... Jeszcze trochę to zaczniemy rozważać minimum rekompliację jądra linuksa, lub co gorsze napisanie własnego systemu operacyjnego do obsługi portu.

Powinniśmy wrócić do zagadnienia zasadniczego, czyli oprogramowania portu tym co zostało wyznaczone czyli C# i na tym się skupmy...

Inaczej nie będzie to już ani dla początkujących, ani nie będzie zasługiwać tu na portal "robotyczny" A na portal informatyczny i to nie niskim poziomie doświadczenia.

  • 1 rok później...

Mam pytanie czy nie wiecie gdzie można znaleźć informacje które rozwijały by temat przedstawiony w artykule.

W szczególności poszukuje informacji które pozwolą mi zaprojektować w c# klasę służąca do odbierania i wysyłania danych w raz z niezbędnymi zabezpieczeniami jak również inne klasy związane z przechowywaniem informacji pobranych z urządzenia.

Mi bardziej chodzi o zaawansowaną klasę która implementuje już klasę SerialPort.

Przed chwilą znalazłem

http://www.dreamincode.net/forums/topic/35775-serial-port-communication-in-c%23/

ale wolałbym coś bardziej zaawansowanego co ma wbudowana funkcje która odpowiada za wysłanie i jednocześnie czeka na odpowiedz.

  • 2 miesiące później...

Witaj, do Twojej aplikacji dodałem dwa przyciski Start i Stop w pierwszej zakładce. Ich wciskanie powoduje odpowiednio:

private void bStart_Click(object sender, EventArgs e)
       {
           if (port.IsOpen)
           {
               Byte[] tosend = { (Byte)128 };
               port.Write(tosend, 0, 1);
           }
           else System.Windows.Forms.MessageBox.Show("Aby rozpocząć odbieranie danych należy ustanowić połączenie");    
       }
       private void bStop_Click(object sender, EventArgs e)
       {
           if (port.IsOpen)
           {
               Byte[] tosend = { (Byte)129 };
               port.Write(tosend, 0, 1);
           }
           else System.Windows.Forms.MessageBox.Show("Nie ustanowiono połączenia");
       }

Zauważyłem że przy kilkukrotnym wciskaniu Start i Stop układ komunikacji pracuje jak należy jednakże po np. 3-cim Stopie, uC pokazuje że nie wysyła(informacja w sprzęcie na diodach potwierdzona testem w Htem-ie) do RichBoxa dalej dopisywany jest ostatni odebrany bajt(chyba ponieważ odbieram 0x00 albo 0xFF), w sposób ciągły. Czy wiesz jaka może byc tego przyczyna?

  • 1 rok później...

Odświeżam (mam nadzieję, że tylko na chwilę) temat. Otóż mam pewien problem. Razem z Twoją aplikacją zastosowałem moduł bluetooth firmy Atmel do komunikacji bezprzewodowej z atmegą8. Przy łączeniu się przez kabel wszystko zawsze jest ok. Przy korzystaniu z bluetoota na innych programach (putty, Docklight) też wszystko wyświetla się ok. Natomiast gdy połączę się przez bluetooth występuje dziwne zjawisko. Wysyłanie z komputera odbywa się normalnym tempem ale odbiór danych z mikrokontrolera bardzo zwalnia, tak jakby w bluetoothie zapełniał się jakiś bufor, który aplikacja nie nadążą odbierać i wyświetlać. Pierwszy bajt pokazuje się odpowiednim czasie, drugi pojawia się po około pół sekundy (bez żadnego opóźnienia w kodzie przy wysyłaniu), i to opóźnienie zwiększa się cały czas, tj. dane wysłane z procka po sekundzie od połączenia, wyświetlane są dopiero po kilku sekundach. Efekt zwiększa się bardzo szybko z czasem. Co ciekawe jeśli wyślemy kilka bajtów z komputera w czasie odbioru, nagle wyświetli się też kilka bajtów przychodzących... Jaka może być tego przyczyna?

  • 1 rok później...
Kiedyś próbowałem się skomunikować i natrafiłem na coś takiego:

http://www.winapi.org/index.php?option=com_content&task=view&id=49&Itemid=30

oraz poszukałem na MSDN i np.:

http://msdn.microsoft.com/en-us/library/system.io.ports.serialport.aspx

W każdym razie: jest to dość paskudne, ale jak już będziecie mieć działajacy kod, to nie będzie problemu. WinAPI działa w każdym środowisku pod Windowsem, ja pisałem w Borland CBuilder.

Inna sprawa, że taka aplikacja potrafi zarżnąć komputer - Core2Duo pozwala na odbieranie góra kilku kB/s i to z bólami. Java będzie pewnie jeszcze wolniejsza. Pewnie pisząc w czymś lepiej optymalizujacym (np. VS) program będzie działać szybciej, ale dużej różnicy nie przewiduję, bo problemem jest pośrednictwo systemu.

Jeżeli zachowasz podstawowe zasady OOP i nie oprzesz wszystkiego na antipatternach, to spokojnie bujniesz 1MB/s - przetestowane z STM32 USB CDC.

Obciążenie CPU? 0%.

Jeżeli chce się pisać aplikacje okienkowe to trzeba zaczerpnąć podstawowej wiedzy o nich (dispatcher - wątek UI). Jeżeli ktoś takowej wiedzy nie posiada i jej nie chce, to polecam w prosty sposób zrobić aplikację konsolową, w której takich problemów się nie napotka, a funkcjonalność pozostaje taka sama - można fajnie zapisywać dane gdzie się chce i jak się chce, do pamięci w celu przetworzenia, na dysk, do sieci, co dusza zapragnie.

Przenośność aplikacji C#? Na każdy popularny system operacyjny - Mac OS, Linux, natywny Windows (niestety bez wsparcia dla WPF ;( ).

http://www.mono-project.com/

#edit

WOW właśnie zobaczyłem datę 😋

jemdream, ja nie wątpię, że USB wyciągnie 1MBps. Tutaj jest kwestia pośrednictwa systemu - przy Windows port szeregowy działa wolno - bo i nie ma potrzeby w 99% przypadków aby działał szybciej, toż on służy przede wszystkim jako konsola i do wymiany małych pakietów danych. Do tego dochodzi kwestia rozmiaru buforów oraz ich opróżniania.

Wierzę jednak, że przyspieszenie jest możliwe, ale wymaga napisania programu wymagającego uprawnień administratora przy każdym uruchomieniu, aby móc pominąć chociaż warstwę API systemu. Lub też właśnie odpowiedni sterownik (np. DXX od FTDI ma lepsze właściwości niż zwykły Generic Serial Port) - nie wiem jak tu wygląda kwestia z STM32.

Pod linuksem można osiągnąć znacznie szybsze transfery.

  • 5 lat(a) później...

Cześć!

Odświeżam temat, może ktoś bardziej doświadczony będzie w stanie mi pomóc lub coś zasugerować. Swoją aplikacje w C# napisałem w oparciu o przedstawiony artykuł. Aplikacja odbiera ramki danych wysyłanych na port COM co 200 ms .

Problem z którym się zmagam polega na zawieszaniu się aplikacji podczas próby zamknięcia portu. W kodzie z artykułu dane odbierane są podczas obsługi zdarzenia w oparciu o delegata i funkcję na którą wskazuje. Zamknięcie portu odbywa się za pomocą pictureBoxa i przerwania od niego, w którym port jest zamykany. Wszystko tak samo jak w głównym programie Form1.cs. Nie mam zbyt dużego doświadczenia w programowaniu w C#, próbowałem już kilku rozwiązań z internetu. Generalnie z tego co czytałem problem dot. lub jest związany z obsługą zdarzeń od zamykania portu i formularza, trochę się w tym gubię wiec proszę o wyrozumiałość. 

W jaki sposób ustrzec aplikacje przed zawieszaniem się ??? Pozdrawiam i dziękuje za wszelkie wskazówki !!!

PS : może ktoś tu zajrzy 🙂

 

//wygenerowane
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;



//moje
using System.IO.Ports;

namespace TutorialCOM
{
    public delegate void Delegat();

    public partial class tutorialCOM : Form
    {
        //zmienne użytkownika
        SerialPort port;
        
        Delegat moj_del;

        public tutorialCOM()
        {
            InitializeComponent();
            //inicjalizacja zmiennej port z domyślnymi wartościami
            port = new SerialPort();
            


            //ustawienie timeoutów aby program się nie wieszał
            port.ReadTimeout = 500;
            port.WriteTimeout = 500;

            Opcje.Enter += new EventHandler(Opcje_Enter);
            port.DataReceived += new SerialDataReceivedEventHandler(DataRecievedHandler);
            
            moj_del = new Delegat(WpiszOdebrane);
        }

        private void DataRecievedHandler(object sender, SerialDataReceivedEventArgs e)
        {
            rtbTerminal.Invoke(moj_del); 
        }

        private void WpiszOdebrane()
        {
            DodajKolorowy(rtbTerminal, port.ReadByte().ToString("X") + " ", System.Drawing.Color.Blue);
        }

        private void DodajKolorowy(System.Windows.Forms.RichTextBox RichTextBox, string Text, System.Drawing.Color Color)
        {
            var StartIndex = RichTextBox.TextLength;
            RichTextBox.AppendText(Text);
            var EndIndex = RichTextBox.TextLength;
            RichTextBox.Select(StartIndex, EndIndex - StartIndex);
            RichTextBox.SelectionColor = Color;

            RichTextBox.ScrollToCaret();
        }

        void Opcje_Enter(object sender, EventArgs e)
        {
            //aktualizacja list
            this.cbName.Items.Clear();
            this.cbParity.Items.Clear();
            this.cbStop.Items.Clear();
            foreach (String s in SerialPort.GetPortNames()) this.cbName.Items.Add(s);
            foreach (String s in Enum.GetNames(typeof(Parity))) this.cbParity.Items.Add(s);
            foreach (String s in Enum.GetNames(typeof(StopBits))) this.cbStop.Items.Add(s);

            //aktualizacja nazw
            cbName.Text = port.PortName.ToString();
            cbBaud.Text = port.BaudRate.ToString();
            cbData.Text = port.DataBits.ToString();
            cbParity.Text = port.Parity.ToString();
            cbStop.Text = port.StopBits.ToString();
        }

        private void butSend_Click(object sender, EventArgs e)
        {
            if (port.IsOpen)
            {
                DodajKolorowy(rtbTerminal, ((Int32)numericSend.Value).ToString("X") + " ", System.Drawing.Color.Black);
                Byte[] tosend = { (Byte) numericSend.Value};
                port.Write(tosend, 0, 1);
            }
            else MessageBox.Show("Aby wysłać bajt musisz ustanowić połączenie");
        }

        private void butDomyslne_Click(object sender, EventArgs e)
        {
            this.cbName.Text = "COM8";
            this.cbBaud.Text = "115200";
            this.cbData.Text = "8";
            this.cbParity.Text = "None";
            this.cbStop.Text = "One";
        }

        private void butCancel_Click(object sender, EventArgs e)
        {
            cbName.Text = port.PortName.ToString();
            cbBaud.Text = port.BaudRate.ToString();
            cbData.Text = port.DataBits.ToString();
            cbParity.Text = port.Parity.ToString();
            cbStop.Text = port.StopBits.ToString();
        }

     

        private void pbStatus_Click(object sender, EventArgs e) // bylo  FormClosingEventArgs
        {
            
            //jeżeli połączenie jest aktywne to je kończymy, zmieniamy kolor na red i zmieniamy napis
            if (port.IsOpen)
            {
             

                pbStatus.BackColor = Color.Red;
                port.Close();
                labStatus.Text = "Brak połączenia (teraz można zmieniać opcje połączenia)";
                DodajKolorowy(rtbTerminal, "\nZakończono połączenie z " + port.PortName + "\n", System.Drawing.Color.Orange);
            }
           //w przeciwnym wypadku włączamy połączenie, zmieniamy kolor na zielony i zmieniamy napis
            else
            {
                //połączenie może nie być możliwe dlatego należy się zabezpieczyć na wypadek błędu
                try
                {
                    //najpierw przepisujemy do portu parametry z opcji
                    port.PortName = this.cbName.Text;
                    port.BaudRate = Int32.Parse(this.cbBaud.Text);
                    port.DataBits = Int32.Parse(this.cbData.Text);
                    port.Parity = (Parity)Enum.Parse(typeof(Parity), this.cbParity.Text);
                    port.StopBits = (StopBits)Enum.Parse(typeof(StopBits), this.cbStop.Text);
                    //a następnie uruchamiamy port
                    port.Open();
                    //po uruchomieniu zmieniamy elementy graficzne interfejsu
                    pbStatus.BackColor = System.Drawing.Color.Green;
                    labStatus.Text = "Aktywne połączenie (port:" + port.PortName.ToString() + ", prędkość: " + port.BaudRate.ToString() + ", bity danych: " +
                    port.DataBits.ToString() + "\n bity stopu: " + port.StopBits.ToString() + ", parzystość: " + port.Parity.ToString() + ")";
                    DodajKolorowy(rtbTerminal, "Rozpoczęto połączenie z " + port.PortName + "\n", System.Drawing.Color.Orange);
                }
                //jeżeli nastąpi błąd to go przechwycimy i wyświetlimy stosowny komunikat
                catch(Exception exc)
                {
                    MessageBox.Show("Błąd połączenia:\n" + exc.Message);
                }
            }
        }

        private void butClear_Click(object sender, EventArgs e)
        {

        }

        private void rtbTerminal_TextChanged(object sender, EventArgs e)
        {

        }
    }
}

 

Bądź aktywny - zaloguj się lub utwórz konto!

Tylko zarejestrowani użytkownicy mogą komentować zawartość tej strony

Utwórz konto w ~20 sekund!

Zarejestruj nowe konto, to proste!

Zarejestruj się »

Zaloguj się

Posiadasz własne konto? Użyj go!

Zaloguj się »
×
×
  • Utwórz nowe...