logo elektroda
logo elektroda
X
logo elektroda
REKLAMA
REKLAMA
Adblock/uBlockOrigin/AdGuard mogą powodować znikanie niektórych postów z powodu nowej reguły.

[Visual C#] Jak poprawić odbieranie danych z SerialPort w Visual C#?

dawid.barracuda 09 Cze 2017 22:41 2406 29
REKLAMA
  • #1 16521522
    dawid.barracuda
    Poziom 13  
    Posty: 597
    Pomógł: 1
    Ocena: 32
    Cześć wszystkim :)
    Od jakiegoś czasu intensywnie pracuję nad komunikacją pomiędzy kilkoma AVR'ami, a PCtem. Różne rzeczy już tu na Forum omawiałem i wiele głupich błędów dzięki Wam poprawiłem :) Jednakże dalej mam pewne problemy, nie zawsze transmisja działa prawidłowo i szukam przyczyny dlaczego. Nieprawidłowość polega na tym, że po prostu staje wszystko dęba i pozornie wygląda to tak, że AVR nie odpowiada na komendę wysłaną z aplikacji na PC. Jednak gdy taka sytuacja zachodzi i zamknę aplikację i przeniosę się do realterma by stamtąd wysłać rozkaz to procek zawsze odpowiada prawidłowo. Pomyślałem więc, że może przyczyna leży w moim programie na PC? Oto jak odbieram dane z portu COM:
    Kod: C#
    Zaloguj się, aby zobaczyć kod


    Próbowałem użyć komendy readExisting, ale to przypisuje dane do stringa, a ja nie operuję w ASCII.
    W ustawieniach kontrolki serialPort mam ReceivedBytesThreshold ustawiony na 1, a gdy zliczałem liczbę wywołań tego zdarzenia (poprzez zwiększanie zmiennej o 1) to czasem była mniejsza niż wielkość paczki danych. Skąd ta rozbieżność?

    No i pytanie zasadnicze - czy prawidłowo odbieram dane w zdarzeniu czy może powinienem zrobić to inaczej? Czy jest to w ogóle możliwe, że obsługa portu COM przez visual C# może być w pewien sposób niestabilna i to rujnuje mi transmisję? Nie ukrywam jednak, że zdaje mi się, że ja robię gdzieś błąd, tylko nie wiem w którym miejscu :)

    Proszę uprzejmie o wskazówki i pozdrawiam.
  • REKLAMA
  • Pomocny post
    #2 16522000
    Konto nie istnieje
    Konto nie istnieje  
  • #3 16522026
    dawid.barracuda
    Poziom 13  
    Posty: 597
    Pomógł: 1
    Ocena: 32
    Piotrus_999 napisał:
    Jaka predkość?

    9600bps. Niby standardowo, ale nie wiem czy to dla Ciebie "żółwia prędkość" :)

    Piotrus_999 napisał:
    Sorki do eventu "received" - to już mogę koledze powiedzić że to nie będzie działac (no chyba że na jakiś żółwich predkościach). Jest to schrzanione w C#. Najlepiej czytać w BW całość bufora, a w swoich funkcjach sobie dalej obrabiać.


    Mówisz, że event DataReceived jest schrzaniony w C#? I nie rozumiem tego - czytać w BW? Mógłbyś przybliżyć? :)
  • REKLAMA
  • Pomocny post
    #4 16522055
    Konto nie istnieje
    Konto nie istnieje  
  • #5 16522161
    dawid.barracuda
    Poziom 13  
    Posty: 597
    Pomógł: 1
    Ocena: 32
    Używam C# od bardzo niedawna więc będę popełniał więcej błędów niż w C dla AVR, to nie ulega wątpliwości. Dlatego pytam na Forum :)

    Pomyślałem o czymś takim:
    Kod: C#
    Zaloguj się, aby zobaczyć kod


    Co o tym sądzisz?

    Czytam o background worker i z tego, co rozumiem to dzięki niemu mogę sobie uruchomić jakiś "cięższy" proces w osobnym wątku dzięki czemu nie zablokuję sobie głównego okna programu, czy tak? No i utworzony wątek zajmowałby się np. jedynie obsługą nadchodzących danych. Dobrze rozumiem?

    Rzeczywiście, zmienne globalne trochę mnie tu irytują. W sofcie na AVR wszystko przepisałem tak, że zmiennych globalnych nie używam za wyjątkiem flagi od timeout'u zdaje się. I rzeczywiście, lepiej mi się takim kodem posługiwać.
    Skoro nie zmienne globalne (wracam do C#) to może każde z urządzeń zapisać jako obiekt? W sumie pasuje - jedno urządzenie to jeden obiekt, każde z urządzeń ma określone parametry. Lepsze podejście?

    Jak @Piotrus_999 uważasz? Najlepszym wyjściem byłoby użycie BW i streama asynchronicznego? Oczywiście nie ukrywam, że to dla mnie nowość, ale nie mam oporów przed poszerzeniem umiejętności.

    Dodano po 29 [minuty]:

    Jeszcze jedno - do tej pory nadchodzące dane obsługiwałem w timerze. Jak została postawiona flaga, że dane są odebrane to wtedy uruchamiana była instrukcja warunkowa. Takie podejście jest ok czy to też powinienem zmienić?
  • Pomocny post
    #6 16522238
    Konto nie istnieje
    Konto nie istnieje  
  • #7 16522453
    dawid.barracuda
    Poziom 13  
    Posty: 597
    Pomógł: 1
    Ocena: 32
    Dalej gdzieś się gubią te bajty :( Zacząłem więc próbować z BW:
    Kod: C#
    Zaloguj się, aby zobaczyć kod

    Przy czym readResponse() to funkcja pollingu z pierwszego postu, przypominam:
    Kod: C#
    Zaloguj się, aby zobaczyć kod


    W momencie otwarcia portu COM uruchamiam BW: serialReceiveBackgroundWorker.RunWorkerAsync(); No i robię to za każdym razem w timerze, gdy przetworzę odebraną ramkę. Na chwilę obecną rozumiem to tak, że DoWork jest jednorazowe - po wykonaniu instrukcji z DoWork BW zgłasza wykonanie zadania i jest wyłączany, czy tak?
    Zauważyłem też takie coś: w panelu Diagnostic Tools wykres zużycia procesora do tej pory był na poziomie dosłownie szczątkowym - co jakiś czas był malutki pik na wykresie. Po dodaniu i uruchomieniu BW zużycie CPU skoczyło do ok. 20% i utrzymuje się na mniej więcej stałym poziomie. Specyfika komponentu czy moje nie do końca jeszcze poprawne podejście do tematu?
  • #8 16522462
    Konto nie istnieje
    Konto nie istnieje  
  • #9 16522478
    dawid.barracuda
    Poziom 13  
    Posty: 597
    Pomógł: 1
    Ocena: 32
    No i mnie to się wydaje podejrzane, że gubi te bajty. Bufory i timeout'y mam ustawione na dość duże. Oto ustawienia mojego serialportu:
    [Visual C#] Jak poprawić odbieranie danych z SerialPort w Visual C#?

    Jak puściłem sobie odpytywanie mojego AVR'a korzystając z BW to bez problemu odebrałem 3.5k ramek, a bez BW siadało po kilkunastu, kilkudziesięciu, kilkuset, ale siadało. Puszczę jeszcze na dłużej i zobaczę czy się wykrzaczy.

    Ustawienia portu ok czy coś zmienić?
  • REKLAMA
  • #10 16522496
    Konto nie istnieje
    Konto nie istnieje  
  • #11 16522572
    dawid.barracuda
    Poziom 13  
    Posty: 597
    Pomógł: 1
    Ocena: 32
    Jeśli dobrze rozumiem, co czytam o custom events - będę mógł dzięki temu odpalić w dogodnym momencie funkcję bez użycia timera i tam np. obrobić odebrane dane? Coś jak dataReceived w serial port, który reaguje na pojawienie się danych w buforze?
  • #12 16522777
    Konto nie istnieje
    Konto nie istnieje  
  • REKLAMA
  • #13 16522868
    dawid.barracuda
    Poziom 13  
    Posty: 597
    Pomógł: 1
    Ocena: 32
    Ok, przyjrzę się tematowi, bo rzeczywiście byłoby to estetyczniejsze i wygodniejsze.

    Zostawiłem program na dłużej i wyszedłem z domu. Wracam i dane śmigały dalej, bo mrugała mi dioda sygnalizująca odebranie danych przez uC.
    Statystyka z PC:
    46583 odebrane ramki bez żadnej straty. Czas działania programu: 178:20 min. Ekran miałem wyłączony, ruszyłem myszką żeby się włączył i dostałem w tym momencie taki wyjątek:
    Cytat:
    An unhandled exception of type 'System.InvalidOperationException' occurred in System.dll

    Additional information: Ten proces BackgroundWorker jest aktualnie zajęty, a nie można wykonywać wielu zadań jednocześnie.

    Jak mam to rozumieć?
  • #14 16522929
    Konto nie istnieje
    Konto nie istnieje  
  • #15 16523163
    dawid.barracuda
    Poziom 13  
    Posty: 597
    Pomógł: 1
    Ocena: 32
    Jasne, uruchomiłem na razie bez obsługi wyjątków żeby tylko sprawdzić, czy BW poprawi funkcjonowanie.

    Za Twoją radą od kilku godzin kopię nt. custom events. Udało mi się jedynie uruchomić taki kod w swoim programie:
    Kod: C#
    Zaloguj się, aby zobaczyć kod

    Przy czym class Test to moja klasa Form1, a static void Main() nazwałem inaczej. Dodatkowo zamiast Console.WriteLine wyświetliłem message boxa.

    Jednak wydaje mi się, że to przerost formy nad treścią w moim przypadku. Ja bym chciał zrobić sobie zdarzenie i funkcję obsługującą zdarzenie w obrębie klasy Form1. Nie mogę znaleźć odpowiedniego przykładu. Jak powinienem to zrobić?

    Dodano po 28 [minuty]:

    Lub chociaż tak zmienić klasę Observer żebym mógł za jej pomocą obrobić nadchodzącą ramkę... Czy tu wystarczy dopisać odpowiednią funkcję w klasie i wywołać ją w funkcji HandleEvent?
  • #16 16523224
    Konto nie istnieje
    Konto nie istnieje  
  • #17 16523277
    dawid.barracuda
    Poziom 13  
    Posty: 597
    Pomógł: 1
    Ocena: 32
    Docelowo chcę to zrobić porządnie oczywiście.
    Może źle się wyraziłem - samo użycie custom events bardzo mi się podoba, bo te timery są średnio wygodne. Przerostem formy nad treścią nazwałem utworzenie dwóch dodatkowych klas do obsługi custom events. Krótko mówiąc - jak utworzyć custom event w obrębie klasy Form1?
  • #18 16523518
    Konto nie istnieje
    Konto nie istnieje  
  • #19 16523527
    dawid.barracuda
    Poziom 13  
    Posty: 597
    Pomógł: 1
    Ocena: 32
    No ok, rozumiem. Tak się przy tym uparłem, bo w klasie Form1 mam zmienne globalne z parametrami odbieranymi od urządzeń podpiętych do mojego "huba" i chciałem mieć do nich dostęp w funkcji obsługującej zdarzenie w klasie obserwującej. Ustaliliśmy jednak, że zmienne globalne to zły pomysł. Lepiej więc utworzyć osobną klasę na każde z obsługiwanych urządzeń?

    Dodano po 6 [minuty]:

    Może jeszcze doprecyzuję co chcę zrobić.

    Jak tutaj:
    Kod: C#
    Zaloguj się, aby zobaczyć kod


    uzyskać dostęp do aktualnych zmiennych klasy Form1? I nie chodzi mi tutaj o utworzenie drugiej instancji tej klasy.
  • #20 16523557
    Konto nie istnieje
    Konto nie istnieje  
  • #21 16523572
    dawid.barracuda
    Poziom 13  
    Posty: 597
    Pomógł: 1
    Ocena: 32
    Cały czas kopię i poszerzam swoje OOP :) Jasne, odnośnie kilku instancji tej samej klasy nie mam wątpliwości.

    Odnośnie:
    Cytat:
    No ale inna klasa może mieć do nich dostęp poprzez odpowiednie mechanizmy.

    Doczytałem, że mając zmienną zadeklarowaną jako "public static" mogę ją zmieniać w dowolnym miejscu programu i dodatkowo jest ona powiązana z klasą, a nie jej konkretną instancją. Wobec tego, jeśli w klasie Form1 zadeklaruję jako public static mój bufor odbiorczy z seriala (czyli tablicę byte[]), to w klasie obsługującej zdarzenie mogę sobie z tym zrobić co chcę, czy tak?
    Ja to właśnie sprawdziłem próbując zmienić wartość jednej takiej zmiennej z klasy Form1 w klasie Observer, w funkcji obsługującej zdarzenie i to działa. Bardziej pytam teraz o to czy takie podejście jest ok?
    Kod: C#
    Zaloguj się, aby zobaczyć kod
  • #22 16523613
    Konto nie istnieje
    Konto nie istnieje  
  • #23 16523743
    dawid.barracuda
    Poziom 13  
    Posty: 597
    Pomógł: 1
    Ocena: 32
    Ok, doczytam o get i set :) Z tego, co czytam do tej pory to o tych metodach to to, że mogę sobie tak ustawić zmienną, że np. odczyt będzie dostępny dla każdego, ale zmiana już nie.

    Chciałbym wrócić jeszcze do wyjątku
    Cytat:
    An unhandled exception of type 'System.InvalidOperationException' occurred in System.dll

    Additional information: Ten proces BackgroundWorker jest aktualnie zajęty, a nie można wykonywać wielu zadań jednocześnie.


    Napisałeś, że to "uśnięcie kompa". Szukam po sieci czegoś z podobnym wyrażeniem, ale nie mogę nic znaleźć. Chodzi Ci tylko o obsługę wyjątku od RunAsync() ?
  • #24 16523787
    Konto nie istnieje
    Konto nie istnieje  
  • #25 16524185
    dawid.barracuda
    Poziom 13  
    Posty: 597
    Pomógł: 1
    Ocena: 32
    Ok, rozumiem :)

    Tak sobie testuję tego backgroundworkera i zauważyłem, że ma on zdarzenie 'RunWorkerCompleted'. Pomyślałem, że warto sprawdzić i wrzuciłem tam kod analizujący odebraną ramkę z seriala. No i rzeczywiście - działa. Wielkości obliczone, etykiety zaktualizowane. Dzięki temu nie muszę bawić się z timerem sprawdzającym co jakiś czas czy coś odebrałem i po analizie ramki w tym zdarzeniu mogę od razu włączyć BW z powrotem. Odeszła też (w tym przypadku) konieczność zastosowania custom eventu.
    Co sądzisz o takim podejściu?
  • #26 16524234
    Konto nie istnieje
    Konto nie istnieje  
  • #27 16533051
    dawid.barracuda
    Poziom 13  
    Posty: 597
    Pomógł: 1
    Ocena: 32
    Tematu nie porzuciłem, spokojnie :)
    Poczytałem trochę o klasach, bo przyznam się szczerze, jakoś do tej pory traktowałem to po macoszemu. Poczytałem o modyfikatorach dostępu, klasach i zmiennych statycznych, getterach i setterach. Wobec poszerzonej wiedzy - stary program wrzuciłem radośnie do śmietnika i napisałem nowy wykorzystując kilka funkcji ze starego.
    Pisząc program od początku napisałem osobne klasy dla każdego z urządzeń (wszystkie w osobnych plikach oczywiście) oraz klasy statyczne dotyczące COBS'a i MODBUS'a (odnośnie COBS'a to akurat skorzystałem z dostępnej w sieci napisanej od razu pod C#). Przyznam, że im więcej tej obiektowości tym bardziej mi się to podoba.
    Dorzucając do tego wszystkiego BackgroundWorker i obsługę w nim portu COM transmisja hula jak złoto. Jak na razie chodziło 1.5h i nie straciłem żadnej ramki, czyli soft pod procka jest napisany w miarę ok, a lipa była po stronie PC. Minusem jest to, że robię w BW klasyczny polling - pętla kręci tak długo aż dostanie bajt zerowy. BW obciąża przez to procesor, ale moim celem jest zrobienie jak najbardziej niezawodnej transmisji, obciążenie procka aż tak mocno mnie nie boli. Plusem jest to, że nie mam na razie żadnego timera w projekcie, ale pewnie dodam jeden tylko do obsługi timeout'u. Będzie sprawdzał czy BW nie zakończył pracy przez n sekund.
    Za radą @Piotrus_999 wyzbyłem się też zmiennych globalnych. Chcąc mieć jednak jakiś dostęp do odebranych danych w różnych miejscach programu zadeklarowałem w klasach statycznych (obsługujących COBS i MODBUS) bufory, więc odwołuję się po nazwie klasy w dowolnym miejscu programu. Mam nadzieję, że takie rozwiązanie jest dozwolone "z punktu widzenia sztuki". Proszę jednak o komentarz w tej sprawie. Nie miałem po prostu innego pomysłu na szeroko dostępne dane poza umieszczeniem ich w klasie.

    Muszę się przyznać do dwóch rzeczy. Pierwsza jest taka, że nie mogę pojąć custom event'ów. No za diabła tego nie rozumiem. Pochodną tego jest pewnie to, że odpytywanie kolejnych urządzeń rozwiązałem maszyną stanów - ja wiem, że w C# to nie powinno się znaleźć. Chcąc się jednak usprawiedliwić napisałem sobie do tego klasę statyczną:
    Kod: C#
    Zaloguj się, aby zobaczyć kod


    Dzięki temu w klasie Form1 wystarczy mi to:
    Kod: C#
    Zaloguj się, aby zobaczyć kod


    Jeśli powyższe jest jednak nie do przyjęcia - to ja się nie obrażę, proszę o wskazówki :) Wykorzystałem to co wiem. Nie mam innego pomysłu na zbudowanie kolejki zapytań bez maszyny stanów.

    @Piotrus_999 - pomogłeś mi najwięcej, więc w szczególności czekam na Twoją opinię :)

    Dodano po 19 [godziny] 54 [minuty]:

    Tak mnie naszła myśl - a gdyby obsługę wydawanych rozkazów zorganizować w postaci jakiejś struktury danych, np. FIFO albo kolejki priorytetowej? Rozwiązanie na pewno lepsze od maszyny stanów, ale czy w dobrym miejscu chcę tego użyć?
    @Piotrus_999 - jak myślisz?

    Dodano po 2 [godziny] 57 [minuty]:

    Chodzi mi o to, że w momencie otwierania portu COM inicjowałbym kolejkę odpytującą urządzenia pomiarowe. BW pobierałby z początku kolejki rozkaz i wysyłał go na port COM. W momencie, gdy kolejka zrobiłaby się pusta inicjowałbym kolejkę od początku. W momencie, gdy użytkownik chciałby wysłać jakieś polecenie do sieci urządzeń - np. "otwórz zawór" to wpycham je na szczyt kolejki i BW ściąga je w pierwszej kolejności jak skończy bieżące zadanie. Chyba lepsze rozwiązanie niż rzeźbienie na flagach, ale czekam na Wasze opinie :)

    Dodano po 3 [minuty]:

    Chociaż jak tak sobie myślę to i LIFO by wystarczyło - kolejność odbierania danych pomiarowych nie ma większego znaczenia w moim przypadku, najważniejsze jest by komendy użytkownika były wykonywane jak najszybciej - czyli dodane na końcu zdjęte będą pierwsze. Widzę nawet, że w C# mam dostępne takie narzędzia.
  • #28 16536348
    Konto nie istnieje
    Konto nie istnieje  
  • #29 16545653
    dawid.barracuda
    Poziom 13  
    Posty: 597
    Pomógł: 1
    Ocena: 32
    Wysyłkę rozkazów zorganizowałem przy pomocy klasy Stack dostępnej w przestrzeni System.Collections. I sprawdza się znakomicie. Dzięki temu pozbyłem się skomplikowanej konstrukcji maszyny stanów - teraz tylko wrzucam i zrzucam ze stosu interesujący mnie rozkaz.

    Przyjrzałem się też trochę mocniej eventom. Gapiłem się tak długo aż udało mi się osiągnąć zamierzony efekt :)

    Przedstawię co napisałem trochę schematycznie, żeby było jak najmniej kodu.

    1. Mam klasę z moim urządzeniem:
    Kod: C#
    Zaloguj się, aby zobaczyć kod


    2. W klasie z główną formatką (Form1) dodaję zdarzenie:
    Kod: C#
    Zaloguj się, aby zobaczyć kod


    3. Oraz definiuję funkcję z obsługą zdarzenia:
    Kod: C#
    Zaloguj się, aby zobaczyć kod



    No i teraz w momencie, kiedy odbiorę dane z portu COM i wywołam funkcję PrzygotujDane() - w której obrabiam sobie to co odebrałem - uruchomię event, który informuje mnie, że dane zostały przygotowane. I w tym evencie mogę sobie np. zaktualizować etykiety na głównej formatce programu.
    Czy to co napisałem jest poprawnym podejściem do zagadnienia?

    Mam jeszcze dwa pytania:
    1. Zauważyłem, że jak napiszę:
    Kod: C#
    Zaloguj się, aby zobaczyć kod

    zamiast:
    Kod: C#
    Zaloguj się, aby zobaczyć kod

    to zdarzenie również jest wykonywane. Zapisem dłuższym sugerowałem sie na podstawie pliku *.designer.cs. Czy te dwa zapisy czymś się różnią?

    2. Czy EventHandler jest dosłownie "uchwytem" który tylko przechowuje aktualny obiekt wywołujący zdarzenie i ew. dane ze zdarzeniem związane? Jak powinienem poprawnie rozumieć ten delegat?
  • Pomocny post
    #30 16549067
    kornik280
    Poziom 18  
    Posty: 466
    Pomógł: 36
    Ocena: 14
    1.Tak to jest to samo.
    2. Event to jest tak jak wskaźnik na funkcje w c++, gdzie podajesz referencje do obiektu dla którego zostało to wywołane, oraz obiekt dziedziczący po EventArgs (dzięki możesz posłać swoje custom argumenty)

Podsumowanie tematu

✨ Dyskusja dotyczy problemów z odbieraniem danych z portu szeregowego (SerialPort) w aplikacji napisanej w Visual C#. Użytkownik zmaga się z utratą danych podczas transmisji między AVR a PC, co nie występuje przy użyciu terminala. Uczestnicy forum sugerują, aby zamiast polegać na zdarzeniu DataReceived, używać BackgroundWorker do obsługi danych w osobnym wątku, co poprawia responsywność aplikacji. Wskazują również na konieczność czytania całego bufora danych oraz unikania zmiennych globalnych. Użytkownik eksperymentuje z różnymi podejściami, w tym z custom events oraz statycznymi zmiennymi, co prowadzi do poprawy działania programu. Ostatecznie, po wprowadzeniu zmian, transmisja działa stabilnie, a użytkownik nie doświadcza już utraty ramek.
Podsumowanie AI na podstawie dyskusji. Może zawierać błędy.
REKLAMA