Kanał - ATNEL tech-forum
Wszystkie działy
Najnowsze wątki



Teraz jest 29 mar 2024, o 11:00


Strefa czasowa: UTC + 1





Utwórz nowy wątek Odpowiedz w wątku  [ Posty: 1 ] 
Autor Wiadomość
 Tytuł: Modbus RTU
PostNapisane: 26 sty 2015, o 11:19 
Offline
Użytkownik

Dołączył(a): 09 cze 2013
Posty: 34
Pomógł: 0

Modbus RTU – implementacja Atmega

Jakiś czas temu w pracy miałem potrzebę skomunikowania mikrokontrolera z panelem operatorskim firmy Proface. Wybór padł na protokół modbus RTU. Jako, że czytając na forum, nie znalazłem dobrze opisanej implementacji tego protokołu na Atmedze, a jest to czasami potrzebne to postanowiłem podzielić się moimi doświadczeniami.
Całość postanowiłem opisać na przykładzie, bez zbędnej teorii, wszystko można doczytać (polecam plik PDF: http://www.modbus.org/docs/Modbus_Appli ... _V1_1b.pdf). Mój procesorek to Atmega64, natomiast panel operatorski to Proface GP4106G1D.
Na początek, zapraszam do obejrzenia załączonego filmiku, żeby było jasne o czym jest mowa ;)
http://youtu.be/4LVJcYCdRjU
Przede wszystkim, dosyć szybko udało mi się zrozumieć o co chodzi, dzięki temu, że korzystałem z manuala od chińskiego serva, w którym zawarte są tylko konkretne informacje (fragment owego manuala, w którym opisany jest modus zamieszczam w załączniku).
Modbus opiera się na modelu master-slave. W moim przypadku Proface to master, Atmega – slave.
Na samym początku należy skonfigurować moduł UART, u mnie jest ustawiony na 8 bitów danych, bez parzystości, jeden bit stopu i prędkość 115200 bps. To samo ustawiamy w panelu, dodatkowo w masterze jest jeszcze parametr o nazwie Timeout, oznacza on czas jaki master będzie oczekiwał na odpowiedź po zadaniu pytania. U mnie jest to ustawione na 3 sekundy (tak było ustawione domyślnie). Dodatkowo jeżeli komunikacja będzie opierała się na standardzie RS-485 trzeba przewidzieć pin przełączający na nadawanie i odbiór, oczywiście będzie potrzebny też scalak MAX-485 lub inny, u mnie jest to 75176B texas inst. Oto przykładowy schemat:
Obrazek
Przejdźmy do samej obsługi ramki. Przykładowo na panelu wstawiamy dwie zmienne 16-bitowe, które mają być wysyłane do slavea. W tym momencie na nóżce RXD mikrokontrolera będzie dochodziła ramka danych, która w kodzie HEX wygląda tak:

01 03 02 00 00 02 C5 B3

Tak powinna wyglądać odpowiedź:

01 0304 00 B1 1F 40 A3 D4

Przejdźmy do opisu pierwszej ramki:

01 - adress slave
03 - kod funkcji
02 00 - adress początkowy pod który trafią dane od slavea
00 02 - ilość 16-bitowych słów, które master chce otrzymać
C5 B3 - suma kontrolna

Opis Odpowiedzi

01 - adress slave
03 - kod funkcji
04 - ilość bajtów jakie slave wysyła do mastera
00 B1 - zawartość pierwszego słowa
1F 40 - zawartość drugiego słowa
A3 D4 - suma kontrolna

Drugi bajt zawsze oznacza kod funkcji. W powyższym przykładzie jest to 0x03 Access multiple words. Pozostałe funkcję są opisane w PDF dotyczącym modbusa, do którego link podałem wcześniej. W moim kodzie obsłużyłem 3 funkcję (0x03, 0x01, 0x0F) ponieważ więcej nie miałem potrzeby.
Zabierzmy się do pisania kodu:

Składnia: [ Pobierz ] [ Ukryj ]
język c
Musisz się zalogować, aby zobaczyć kod źródłowy. Tylko zalogowani użytkownicy mogą widzieć kod.

Inicjalizacji UART nie będę opisywał (jest tak samo napisane jak w bluebooku inicjalizacja RS-485). Natomiast wyjaśnię obsługę otrzymywania ramki. Już nie pamiętam dlaczego ale za pomocą bufora okrężnego nie udało mi się tego obsłużyć, więc rozwiązałem to w inny sposób. Po prostu zdefiniowałem tablicę odbiorczą na rozmiar 11 bajtów ( 11 ponieważ rozmiar ramki w moim przypadku nie przekroczy 11 bajtów) i za każdym razem gdy zostaną odebrane wszystkie bajty z ramki, co jest sprawdzane w warunku if(UART_RxIndex==data_length) to zerowany jest indeks tablicy UART_RxIndeks i ustawiana flaga recieve_done, która powoduje uruchomienie fragmentu kodu gdzie jest wysyłana odpowiedź. Odpowiedź jest wykonywana w funkcji modbus();.
Zanim przejdziemy do opisu odpowiedzi na zapytanie mastera, wyjaśnię jeszcze co się dzieje w przerwaniu Compare Match, na wszelki wypadek napisałem jeszcze watchdoga. Jeśli flaga recieve_done nie jest ustawiona przez dłużej niż 250 ms to inicjalizuje parametry odbioru danych od nowa. Na filmiku jest widoczne jak przerywam komunikację poprzez odłączenie wtyczki i transmisja wstaje za każdym razem. Przy tych ustawieniach nie zdarzyło mi się aby komunikacja się zgubiła.
Teraz należało by odpowiedzieć na pytanie mastera. Wykonuję to w funkcji modbus():

Składnia: [ Pobierz ] [ Ukryj ]
język c
Musisz się zalogować, aby zobaczyć kod źródłowy. Tylko zalogowani użytkownicy mogą widzieć kod.

Myśle, że komentarze w kodzie wyjaśniają większość wątpliwości. Wyjaśnienia wymaga na pewno obliczanie sumy kontrolnej czyli
CRC = crc_chk(Modbus_TxBuf, data_to_send-2); //oblicz sumę kontrolną
CRC_L = CRC; //CRC Check Low (send first)
CRC_H = (CRC>>8);
Funkcja wygląda następująco:
Składnia: [ Pobierz ] [ Ukryj ]
język c
Musisz się zalogować, aby zobaczyć kod źródłowy. Tylko zalogowani użytkownicy mogą widzieć kod.

Funkcja ta jest gotowcem ściągniętym z manuala od serva, który jest załączony poniżej 
W funkcji wysyłającej ramkę do proface też nie ma żadnych czarów:
Składnia: [ Pobierz ] [ Ukryj ]
język c
Musisz się zalogować, aby zobaczyć kod źródłowy. Tylko zalogowani użytkownicy mogą widzieć kod.

Jeszcze króciutko wyjaśnię co się dzieje w obsłudze poszczególnych funkcji:

0x03
W Proface mam zadeklarowane 3 zmienne 16-bitowe, więc zapytanie jest o 3 słowa, przekazuje je do bajtów, które są obszarem danych.
U mnie są to pulse – impulsy z enkodera, QB0 i QB1 są to bajty statusowe, zapalam i gaszę poszczególne bity (dzieje się to w funkcji 0x0F) i takty - to będzie coś związanego z pomiarem czasu.

0x01
Z funkcji tej aktualnie nie korzystam w moim programie, ale może ona służyć do obsługi takiej funkcji jak invert bit w panelu operatorskim, po naciśnięciu klawisza na panelu zdeklarowanego jako invert bit, proface zanim dokona operacji zmiany stanu bitu pyta się slave jaki jest aktulanie stan bitu 0 czy 1 i po otrzymaniu odpowiedzi wykonuje operację invert.

0x0F
//ustawianie bitów statusowych
if(Modbus_RxBuf[7]==1) QB0 |= (1<<Modbus_RxBuf[3]); //ustawienie bitów na bajcie QB0
if(Modbus_RxBuf[7]==0) QB0 &= ~(1<<Modbus_RxBuf[3]); //zerowanie bitów na bajcie QB0
Po zdeklarowaniu w panelu przycisku set bit, reset bit lub momentary bit, w momencie gdy bit zostanie ustawiony to na bajcie 7 pojawia się 1, jeżeli wyzerowany to 0, bajt 3 oznacza, który bit jest ustawiany, jeżeli mamy 8 przycisków to przypisujemy im numery od 0 do 7.

Mam nadzieję, że udało mi się w miarę jasno opisać obsługę modbusa. Zdaję sobie sprawę, że program jest daleki od doskonałości. Najlepiej dokonywać przełączanie na odbiór za pomocą przerwania USART_TXC_vect. Nie wykorzystałem tego, ponieważ nie chciało to dobrze działać, i z powodu braku czasu nie dopracowałem tego i jest tak jak teraz. Efekt był taki, że transmisja ruszała z kopyta, a po jakimś czasem coś się gubiło. Nie do końca też rozumiem zależności czasowym występujących w tym protokole, np. w manualu jest zdanie, że po każdej wymianie danych cisza na linii powinna trwać 10 ms. Jak sprawdzam na oscyloskopie u mnie jest to 5 ms. Jednakże u mnie to wszystko dobrze działa.
Mam niewielki staż w programowaniu uC (ledwie rok) więc proszę o wyrozumiałość. Wszystkie uwagi są dla mnie cenne, mam nadzieję, że wspólnie dopracujemy temat tak by każdy mógł bez problemu implementować ten protokół w razie potrzeby ;)


Załączniki:

Aby zobaczyć załączniki musisz się zalogować. Tylko zalogowani użytkownicy mogą oglądać i pobierać załączniki.



Góra
 Zobacz profil  
cytowanie selektywne  Cytuj  
Wyświetl posty nie starsze niż:  Sortuj wg  
Utwórz nowy wątek Odpowiedz w wątku  [ Posty: 1 ] 

Strefa czasowa: UTC + 1


Kto przegląda forum

Użytkownicy przeglądający ten dział: Brak zidentyfikowanych użytkowników i 0 gości


Nie możesz rozpoczynać nowych wątków
Nie możesz odpowiadać w wątkach
Nie możesz edytować swoich postów
Nie możesz usuwać swoich postów
Nie możesz dodawać załączników

Szukaj:
Skocz do:  
cron
Sitemap
Technologię dostarcza phpBB® Forum Software © phpBB Group phpBB3.PL
phpBB SEO