Ponieważ asynchroniczna komunikacja szeregowa jest obecnie szeroko rozpowszechniona wśród urządzeń elektronicznych, uważam, że wielu z nas od czasu do czasu napotyka takie pytanie. Rozważ urządzenie elektroniczne D
i komputer PC
podłączony do linii szeregowej (RS-232 lub podobny) i wymagane do ciągłej wymiany informacji . Tj. Wysyła PC
każdą ramkę poleceń X ms
i D
odpowiada każdą z raportem statusu / ramką telemetryczną Y ms
(raport może być wysłany jako odpowiedź na żądanie lub niezależnie - tutaj tak naprawdę nie ma znaczenia). Ramki komunikacyjne mogą zawierać dowolne dane binarne . Zakładając, że ramki komunikacyjne są pakietami o stałej długości.
Problem:
Ponieważ protokół jest ciągły, strona odbierająca może utracić synchronizację lub po prostu „dołączyć” w środku wysyłanej ramki, więc po prostu nie będzie wiedział, gdzie jest początek ramki (SOF). Dane mają inne znaczenie w zależności od ich pozycji względem SOF, otrzymane dane zostaną uszkodzone, potencjalnie na zawsze.
Wymagane rozwiązanie
Niezawodny schemat ograniczania / synchronizacji do wykrywania SOF z krótkim czasem odzyskiwania (tj. Nie powinno to zająć więcej niż, powiedzmy, 1 ramka, aby ponownie zsynchronizować).
Istniejące techniki, które znam (i używam):
1) Nagłówek / suma kontrolna - SOF jako predefiniowana wartość bajtu. Suma kontrolna na końcu ramki.
- Plusy: proste.
- Minusy: niewiarygodne. Nieznany czas odzyskiwania.
2) Wypychanie bajtów:
- Plusy: niezawodne, szybkie odzyskiwanie, można go używać z dowolnym sprzętem
- Wady: Nie nadaje się do komunikacji opartej na ramkach o stałym rozmiarze
3) Oznaczenie 9-go bitu - dodaj każdy bajt dodatkowym bitem, podczas gdy SOF jest oznaczony, 1
a bajty danych są oznaczone 0
:
- Plusy: niezawodne, szybkie odzyskiwanie
- Minusy: Wymaga wsparcia sprzętowego. Nieobsługiwany bezpośrednio przez większość
PC
sprzętu i oprogramowania.
4) Oznakowanie 8-go bitu - rodzaj emulacji powyższego, przy użyciu 8-go bitu zamiast 9-tego, co pozostawia tylko 7 bitów na każde słowo danych.
- Plusy: niezawodne, szybkie odzyskiwanie, można go używać z dowolnym sprzętem.
- Wady: Wymaga schematu kodowania / dekodowania z / do konwencjonalnej reprezentacji 8-bitowej do / z reprezentacji 7-bitowej. Nieco marnotrawstwo.
5) Na podstawie limitu czasu - załóż SOF jako pierwszy bajt następujący po określonym czasie bezczynności.
- Plusy: brak narzutu danych, proste.
- Minusy: Nie tak niezawodny. Nie działa dobrze w przypadku słabych systemów pomiaru czasu, takich jak np. Komputer z systemem Windows. Potencjalny narzut przepustowości.
Pytanie: Jakie są inne możliwe techniki / rozwiązania mające na celu rozwiązanie problemu? Czy możesz wskazać wady na powyższej liście, które można łatwo obejść, usuwając je w ten sposób? Jak (lub chciałbyś) zaprojektować protokół systemowy?
źródło
Odpowiedzi:
Z mojego doświadczenia wynika, że wszyscy spędzają dużo więcej czasu na debugowaniu systemów komunikacyjnych, niż się kiedykolwiek spodziewali. Dlatego zdecydowanie sugeruję, aby za każdym razem, gdy trzeba dokonać wyboru protokołu komunikacyjnego, wybieramy dowolną opcję, która ułatwia debugowanie systemu, jeśli to w ogóle możliwe.
Zachęcam do zabawy przy projektowaniu kilku niestandardowych protokołów - jest to zabawne i bardzo edukacyjne. Zachęcam również do zapoznania się z wcześniej istniejącymi protokołami. Gdybym musiał przesyłać dane z jednego miejsca do drugiego, bardzo starałbym się użyć jakiegoś wcześniej istniejącego protokołu, który ktoś inny spędził już dużo czasu na debugowaniu.
Bardzo prawdopodobne jest, że napisanie od podstaw własnego protokołu komunikacyjnego rozwiąże wiele podobnych problemów, które napotykają wszyscy, pisząc nowy protokół.
Istnieje kilkanaście protokołów systemów wbudowanych wymienionych w protokołach Good RS232 opartych na komunikacji wbudowanej w komputer - który z nich jest najbliższy twoim wymaganiom?
Nawet jeśli pewne okoliczności uniemożliwiały dokładne użycie wcześniej istniejącego protokołu, bardziej prawdopodobne jest, że zacznę działać szybciej, zaczynając od protokołu, który prawie odpowiada wymaganiom, a następnie dostosowując go.
złe wieści
Jak już powiedziałem wcześniej :
Niestety żaden protokół komunikacyjny nie ma wszystkich tych przyjemnych funkcji:
Byłbym zaskoczony i zachwycony, gdyby istniał sposób, aby protokół komunikacyjny posiadał wszystkie te funkcje.
dobre wieści
Często sprawia to, że debugowanie jest o wiele łatwiejsze, jeśli człowiek w terminalu tekstowym może zastąpić dowolne z komunikujących się urządzeń. Wymaga to zaprojektowania protokołu tak, aby był względnie niezależny od czasu (nie ma limitu czasu podczas stosunkowo długich przerw między naciśnięciami klawiszy wpisywanymi przez człowieka). Ponadto takie protokoły są ograniczone do rodzajów bajtów, które są łatwe do wpisania przez człowieka, a następnie do odczytania na ekranie.
Niektóre protokoły pozwalają na wysyłanie wiadomości w trybie „tekstowym” lub „binarnym” (i wymagają, aby wszystkie możliwe wiadomości binarne miały jakąś „równoważną” wiadomość tekstową, co oznacza to samo). Może to znacznie ułatwić debugowanie.
Wydaje się, że niektórzy uważają, że ograniczenie protokołu do używania tylko znaków drukowalnych jest „marnotrawstwem”, ale oszczędność czasu debugowania często sprawia, że warto.
Jak już wspomniano, jeśli zezwolisz, aby pole danych zawierało dowolny dowolny bajt, w tym bajty początku i końca nagłówka, kiedy odbiornik jest włączany po raz pierwszy, prawdopodobne jest, że odbiornik źle synchronizuje coś, co wygląda jak bajt początku nagłówka (SOH) w polu danych w środku jednego pakietu. Zwykle odbiorca otrzyma niedopasowaną sumę kontrolną na końcu tego pseudopakietu (który zwykle znajduje się w połowie drugiego rzeczywistego pakietu). Bardzo kuszące jest po prostu odrzucić cały pseudo-komunikat (w tym pierwszą połowę drugiego pakietu) przed szukaniem następnej SOH - w rezultacie odbiornik może pozostać niezsynchronizowany dla wielu wiadomości.
Jak zauważył alex.forencich, znacznie lepszym rozwiązaniem jest odrzucanie bajtów przez odbiornik na początku bufora aż do następnego SOH. Umożliwia to odbiornikowi (po ewentualnej pracy przez kilka bajtów SOH w tym pakiecie danych) natychmiastową synchronizację na drugim pakiecie.
Jak zauważył Nicholas Clark, nadziewanie bajtami spójnego narzutu (COBS) ma stały narzut, który działa dobrze z ramkami o stałym rozmiarze.
Jedną z często pomijanych technik jest dedykowany bajt znacznika końca ramki. Gdy odbiornik jest włączony w środku transmisji, dedykowany bajt znacznika końca ramki pomaga odbiornikowi szybciej synchronizować.
Gdy odbiornik jest włączony w środku pakietu, a pole danych pakietu zawiera bajty, które wydają się być początkiem pakietu (początek pseudo-pakietu), nadajnik może wstawić serię bajtów znacznika końca ramki po tym pakiecie, aby takie pseudo-bajty początku pakietu w polu danych nie zakłócały natychmiastowej synchronizacji i prawidłowego dekodowania następnego pakietu - nawet jeśli masz pecha i sumę kontrolną tego pseudopakietu wydaje się poprawny.
Powodzenia.
źródło
Programy wypychania bajtów sprawdziły się dla mnie przez lata. Są ładne, ponieważ można je łatwo zaimplementować w oprogramowaniu lub w sprzęcie, do wysyłania pakietów danych można użyć standardowego kabla USB-U-UART i gwarantuje się uzyskanie dobrej jakości ramkowania bez martwienia się o przekroczenia limitu czasu, zamiana na gorąco lub coś podobnego.
Opowiadałbym się za metodą wypychania bajtów w połączeniu z bajtem długości (moduł długości modulo 256) i CRC na poziomie pakietu, a następnie używam UART z bitem parzystości. Bajt długości zapewnia wykrywanie po bajcie, co dobrze działa z bitem parzystości (ponieważ większość UART upuści wszystkie bajty, które nie są parzyste). Następnie CRC na poziomie pakietu zapewnia dodatkowe bezpieczeństwo.
Jeśli chodzi o nakładanie się bajtów, czy spojrzałeś na protokół COBS? To genialny sposób na wypychanie bajtów ze stałym narzutem 1 bajtu na każde 254 przesyłane (plus twoja ramka, CRC, LEN itp.).
https://en.wikipedia.org/wiki/Consistent_Overhead_Byte_Stuffing
źródło
Twoja opcja nr 1, SOH plus suma kontrolna, JEST niezawodna i odzyskuje na następnej nieuszkodzonej ramce.
Zakładam, że albo znasz już długość wiadomości, albo długość jest zakodowana w bajtach bezpośrednio po SOH. Bajty kontrolne pojawiają się na końcu wiadomości. Potrzebujesz również bufora po stronie odbiorczej dla danych, który jest co najmniej tak długi, jak Twoja najdłuższa wiadomość.
Za każdym razem, gdy widzisz bajt SOH na początku bufora, jest to potencjalnie początek komunikatu. Przeszukujesz bufor, aby obliczyć wartość kontrolną tego komunikatu i sprawdzić, czy pasuje on do bajtów kontrolnych w buforze. Jeśli tak, to koniec; w przeciwnym razie odrzucisz dane z bufora, aż dojdziesz do następnego bajtu SOH.
Zauważ, że jeśli komunikat faktycznie zawiera błędy danych, algorytm go odrzuci - ale prawdopodobnie i tak zamierzałeś to zrobić. Jeśli Twój algorytm sprawdzania zawiera korekcję błędów przesyłania dalej, możesz sprawdzić każde potencjalne wyrównanie komunikatu pod kątem możliwych do naprawienia błędów.
Jeśli komunikaty mają ustaloną długość, możesz zrezygnować z bajtu SOH - po prostu przetestuj KAŻDĄ możliwą pozycję początkową pod kątem prawidłowej wartości kontrolnej.
Możesz także zrezygnować z algorytmu sprawdzania i zachować tylko bajt SOH, ale to sprawia, że algorytm jest mniej deterministyczny. Chodzi o to, że w przypadku prawidłowego wyrównywania wiadomości SOH zawsze będzie pojawiać się na początku wiadomości. Jeśli masz niepoprawne wyrównanie, prawdopodobnie następny bajt w strumieniu danych nie będzie innym SOH (zależy od tego, jak często SOH pojawia się w danych komunikatu). Na tej podstawie możesz wybrać prawidłowe bajty SOH. (Zasadniczo tak działa kadrowanie w synchronicznych usługach telekomunikacyjnych, takich jak T1 i E1.)
źródło
Jedną z niewymienionych opcji, ale szeroko stosowaną (szczególnie w Internecie) jest kodowanie ASCII / tekst (w rzeczywistości większość współczesnych implementacji zakłada UTF-8). Z mojego doświadczenia wynika, że faceci od sprzętu nie lubią tego robić, ale ludzie oprogramowania wolą to od prawie wszystkiego innego (dotyczy to głównie tradycji uniksowej polegającej na tworzeniu wszystkich tekstów).
Zaletą kodowania tekstu jest to, że do kadrowania można używać znaków niedrukowalnych. Na przykład najprostszym byłoby użycie czegoś w rodzaju
0x00
wskazania początku i0xff
końca ramki.Widziałem dwa główne sposoby kodowania danych jako tekstu:
Gdy poprosimy o to faceta od montażu / montażu, najprawdopodobniej zostanie to zaimplementowane jako kodowanie szesnastkowe. Jest to po prostu konwersja bajtów na ich wartości szesnastkowe w ASCII. Koszty ogólne są duże. Zasadniczo prześlesz dwa bajty na każdy rzeczywisty bajt danych.
Gdy poprosi o to programistę, prawdopodobnie zostanie on zaimplementowany jako kodowanie base64. Jest to de facto kodowanie Internetu. Używany do wszystkiego, od załączników MIME do e-maila po kodowanie danych URL. Koszty ogólne wynoszą dokładnie 33%. Znacznie lepsze niż proste kodowanie szesnastkowe.
Alternatywnie możesz całkowicie zrezygnować z danych binarnych i przesłać tekst. W tym przypadku najczęściej stosowaną techniką jest rozdzielanie danych znakiem nowej linii (po prostu
"\n"
lub"\r\n"
). NMEA (GPS), polecenia Modem AT i czujniki Adventech ADAM to tylko niektóre z najczęstszych tego przykładów.Wszystkie te tekstowe protokoły / ramki mają następujące zalety i wady:
Zawodowiec:
Kon:
"0"
(0x30) zamiast czterech bajtów 0x00000000)sprintf()
funkcję)Osobiście dla mnie zalety przewyższają wady. Łatwość samego debugowania liczy się jako 5 punktów (tak, że sam pojedynczy punkt już przewyższa obie wady).
Są też nie tak dokładnie przemyślane rozwiązania, często pochodzące od osób zajmujących się oprogramowaniem: wysyłanie zakodowanych danych bez myślenia o kadrowaniu.
W przeszłości musiałem współpracować ze sprzętem, który wysyłał nieprzetworzony XML. XML był całym kadrem. Na szczęście
<xml></xml>
znaczniki dość łatwo określają granice ramek . Największą wadą dla mnie jest to, że używa więcej niż jednego bajtu do kadrowania. Również samo kadrowanie może nie zostać naprawione, ponieważ znacznik może zawierać atrybuty:<tag foo="bar"></tag>
więc musisz znaleźć bufor w najgorszym przypadku, aby znaleźć początek ramki.Ostatnio widziałem, jak ludzie zaczynają wysyłać JSON z portów szeregowych. W przypadku JSON kadrowanie jest w najlepszym razie zgadywaniem. Masz tylko
"{"
(lub"["
) znak do wykrycia ramki, ale są one również zawarte w danych. Tak więc w końcu potrzebujesz parsera zejścia rekurencyjnego (lub przynajmniej licznika nawiasów klamrowych), aby ustalić ramkę. Przynajmniej trywialne jest sprawdzenie, czy bieżąca ramka kończy się przedwcześnie:"}{"
czy"]["
w JSON są nielegalne, a tym samym wskazują, że stara ramka się zakończyła i zaczęła się nowa ramka.źródło
<
i>
jako ograniczników i uważam, że poczta e-mail używa znaków nowej linii. Uwaga: tak, poczta e-mail ma poprawnie sformatowaną ramkę które mogą być przesyłane przez RS232.To, co opisujesz jako „X-bitowe oznaczenie”, można uogólnić na inne kody, które mają właściwość rozszerzania danych o stały ułamek, pozostawiając niektóre słowa kodowe do wykorzystania jako ograniczniki. Często kody te zapewniają również inne korzyści; Płyty CD używają modulacji od ośmiu do czternastu , co gwarantuje maksymalną długość przebiegu równą 0 bitów między nimi 1. Inne przykłady obejmują kody blokowe korekcji błędów przesyłania dalej , które wykorzystują dodatkowe bity również do kodowania informacji o wykrywaniu błędów i korekcji.
Kolejnym systemem, o którym nie wspomniałeś, jest wykorzystywanie informacji poza pasmem, takich jak linia wyboru żetonów, do rozgraniczenia transakcji lub pakietów.
źródło
Inną opcją jest tak zwane kodowanie linii . Kodowanie liniowe nadaje sygnałowi pewne właściwości elektryczne, które ułatwiają transmisję (zrównoważone napięcie DC i gwarancja maksymalnej długości przebiegu) i obsługują znaki sterujące dla ramkowania i synchronizacji zegara. Kody linii są stosowane we wszystkich nowoczesnych szybkich protokołach szeregowych - 10M, 100M, 1G i 10G Ethernet, serial ATA, FireWire, USB 3, PCIe itp. Typowe kody linii to 8b / 10b , 64b / 66b i 128b / 130b. Istnieją również prostsze kody linii, które nie dostarczają informacji o kadrowaniu, tylko równowaga DC i synchronizacja zegara. Przykładami są Machester i NRZ. Prawdopodobnie chcesz użyć 8b / 10b, jeśli chcesz szybko zsynchronizować; inne kody linii nie są zaprojektowane tak, aby się zsynchronizować w pośpiechu. Korzystanie z kodu linii, takiego jak powyższy, będzie wymagało użycia niestandardowego sprzętu do transmisji i odbioru.
Jeśli chodzi o opcję 5, standardowy szeregowy RS232 powinien obsługiwać wysyłanie i odbieranie przerw, gdy linia jest bezczynna przez kilka bajtów. Może to jednak nie być obsługiwane we wszystkich systemach.
Zasadniczo najprostszą i najbardziej niezawodną metodą ramkowania jest opcja 1 w połączeniu z polem długości i prostą procedurą CRC lub sumą kontrolną. Procedura dekodowania jest prosta: odrzuć bajty, aż pojawi się bajt początkowy, przeczytaj pole długości, poczekaj na całą ramkę, sprawdź sumę kontrolną, zachowaj, jeśli jest dobra. Jeśli suma kontrolna jest zła, zacznij usuwać bajty z bufora, aż pojawi się bajt początkowy i powtórz. Głównym problemem związanym z tą techniką jest znalezienie początku bajtu ramki, który tak naprawdę nie jest początkiem bajtu ramki. Aby złagodzić ten problem, jedną z technik jest ucieczka bajtów o tej samej wartości co początek bajtu ramki innym znakiem kontrolnym, a następnie zmiana bajtu zmiany znaczenia, aby miał inną wartość. W takim przypadku będziesz musiał zrobić to samo z nowym bajtem kontrolnym.
źródło