sobota, 10 czerwca 2017

CAN - interfejs komunikacyjny prawie doskonały.

Ponieważ cały czas odkrywam mikrokontrolery PIC , w szczególności rodzinę 16-bitowców. Nie może zabraknąć rozpoznania interfejsu komunikacyjnego CAN, który jest na pokładzie wielu modeli z tej rodziny.
Czeka mnie zatem kolejna wspaniała przygoda . Jest to moje pierwsze zetknięcie się z tym interfejsem dlatego informację będę uzupełniał w miarę poznania.

Nie będę rozbudowywał w artykule nadmiernie aspektów teoretycznych bo o nich możemy doczytać w necie, skupię się raczej na aspektach praktycznych.
Poniżej w linkach przykłady źródeł wiedzy o CAN. Ponieważ wstęp jakiś musi być więc krótko trochę informacji encyklopedycznych :

Controller Area Network (CAN) – szeregowa asynchroniczna magistrala komunikacyjna powstała w latach 80. XX w. w firmie Robert Bosch GmbH z myślą o zastosowaniach w przemyśle samochodowym (np.ABS, sterowanie silnika). Obecnie znajduje zastosowanie również w przemysłowych magistralach takich jak CANopen, DeviceNET oraz w wojsku MilCAN.

Tu mała dygresja , CAN jest doskonałą alternatywą dla sieci opartych o RS485 Dla nas hobbystów jest doskonałym wyborem do budowy np sieci w inteligentnym budynku, czy też prostych interfejsów sieciowych do monitoringu temperatury, sterowania piecem etc. Wyobraźmy sobie jaką niezawodnością musi się CAN odznaczać skoro wybrał go m.in przemysł motoryzacyjny na swój flagowy interfejs komunikacyjny.

Magistrala CAN wykorzystuje dwuprzewodową skrętkę i pracuje z maksymalną prędkością transmisji 1 Mb/s na dystansie do 40 m, maksymalny zasięg to 10 km przy prędkości 5 kbit/s. Wraz ze zwiększaniem dystansu spada maksymalna prędkość transmisji (np. 250 kbit/s na 250 m).



W magistrali CAN nie ma wyodrębnionej jednostki nadrzędnej dlatego należy do grupy magistral typu multimaster. Komunikacja ma charakter rozgłoszeniowy ponieważ komunikaty nadawane na magistralę odbierane są przez wszystkie urządzenia.

Najważniejsze cechy CAN to:

-  do 8 bajtów danych w komunikacie (w przypadku CAN FD - 64 bajty),
-  komunikaty rozpoznawane przez identyfikatory (dodatkowo stosowane filtry i maski)
-  automatyczna obsługa dostępu do magistrali,
-  sprzętowa obsługa błędów.

Obecnie w praktyce funkcjonują dwie wersje protokołu: 2.0A (11-bitowy identyfikator) i 2.0B (29-bitowy identyfikator) określany przez Microchipa jako ECAN (Enhanced CAN) W wersja 2.0A możemy zaadresować ok 2032 komunikatów a w wersji 2.0B ok 536 mln. Ramka danych CAN składa się z następujących pól – początku arbitracji, sterującego, danych, sumy kontrolnej, potwierdzenia i końca.

W standardzie 2.0B pole arbitracji ma 32 bity (12 w 2.0A). Identyfikator komunikatu zajmujący niemal całe pole arbitracji, określa priorytet dostępu do magistrali – im mniejsza wartość liczbowa, tym priorytet większy. Charakterystyczne dla magistrali CAN jest to, że identyfikator nie jest przypisany do urządzenia, lecz do komunikatu. Dostęp do magistrali jest przyznawany metodą dominacji bitowej (bit dominance). Polega ona na tym, że wszystkie stacje badają stan magistrali czekając na możliwość wysłania własnego komunikatu. Konflikty wynikające z ewentualnego podjęcia równoczesnego nadawania przez kilka stacji są rozwiązywane w początkowej fazie transmisji w trakcie wysyłania pola arbitracji zawierającego identyfikator komunikatu. Jeżeli fizyczne medium transmisyjne posiada własność dominacji zera,wysłanie przez jedną stację wartości logicznej 0 (poziom dominujący), a przez drugą 1 (poziom recesywny) powoduje, że na magistrali ustala się 0. Dostęp do łącza otrzyma więc stacja o niższym numerze identyfikacyjnym. Stąd też wynika warunek poprawnej arbitracji wymagający, aby w sieci dwa urządzenia nie mogły nadawać komunikatów o tym samym identyfikatorze.


CAN charakteryzuje się dużą odpornością na zakłócenia i niezawodnością, 1 niewychwycony bit zdarza się raz na 100 lat pracy sieci.. Uzyskano to poprzez nadawanie danych w postaci napięciowego sygnału różnicowego oraz dzięki sprzętowej obsłudze protokołu i kontroli błędów. Specjalizowane kontrolery formują komunikaty, sterują bezkolizyjnym dostępem do magistrali, filtrują komunikaty. Obecnie większość czołowych firm elektronicznych produkuje kontrolery CAN jako układy peryferyjne lub wbudowane np. w mikrokontrolerach


Oprócz CAN w przemyśle samochodowym mają zastosowanie również LIN oraz FlexRay.

Koniec wiedzy encyklopedycznej teraz bierzemy lupę i przybliżamy perspektywę .

Zaczniemy od najważniejszego czyli ogólnego schematu połączenia na magistrali CAN.
Medium transmisyjnym jest skrętka dwuprzewodowa zakończona (zaterminowana) rezystorami typowo 120 Ohm.
Pojedyńczy węzeł składa się i tutaj mamy trzy warianty :

1. Mikrokontroler + CAN Controller + CAN Transceiver (czyli 3 oddzielne urządzenia)
lub
2. Mikrokontroler z CAN Controller + CAN Transceiver (czyli 2 oddzielne urządzenia)
lub
3. Mikrokontroler z CAN Controller i z  CAN Transceiver (czyli wszystko siedzi w jednym mikrokontrolerze)

Najbardziej popularnym wariantem jest wariant nr 2.

Na rysunku poniżej mamy przykład fizycznej struktury sieci CAN.



Informacje są przesyłane przez magistralę CAN metodą różnicową. Oznacza to, że stany sygnału na magistrali CAN określane są przy pomocy różnicy napięcia między liniami CAN High oraz CAN Low. Magistrala może przyjąć dwa stany: recesywny i dominujący. Stan recesywny wystąpi, gdy napięcie na obu liniach magistrali CAN będzie równe i wyniesie 2,5 V, a więc różnica napięć będzie równa 0 V. Stan dominujący wystąpi, gdy napięcie na linii CAN High wyniesie 3,5 V, natomiast na linii CAN Low 1,5 V, a więc różnica napięć będzie równa 2 V. Każde z wymienionych wartości napięcia ma określoną tolerancję błędu.

Rys. Relacje między logicznymi wartościami CAN a stanami magistrali CAN.

Na magistrali CAN zero logiczne reperezentowane jest przez stan dominujący a jedynka logiczna reprezentowana jest przez stan recesywny.

Aby zrealizować sprzętowo dwa węzły CAN wykorzystamy dwa mikrokontrolery PIC24HJ128GP502 (z wbudowanym kontrolerem CAN) i dwa transceivery MCP2562FD. Transceiver realizuje nam konwersję sygnału zero jedynkowego od strony mikrokontrolera na sygnał różnicowy po stronie magistrali CAN.
Transceiver jest dla nas w praktyce urządzeniem przezroczystym i jedyne co musimy wiedzieć to jak go podłączyć. Na rynku są dostępne transcivery od różnych producentów np SN65HVD233-HT lub SN65HVD230 firmy Texas Instruments dedykowane do napięć 3.3 V. Łatwo dostępne i relatywnie tanie w postaci gotowych modułów.

Słów kilka na temat wybranego transceivera MCP2562FD. Jest to obecnie najbardziej zaawansowany technologicznie transceiver firmy Microchip, jest w zasadzie następną generacją urządzeń opartych o zmodyfikowany protokół CAN - CAN FD. CAN FD jest ok 30 razy szybszy w przesyłaniu informacji od standardowej implementacji CAN i będzie stanowił podstawowe wyposażenie w przemyśle motoryzacyjnym w latach 2019-20. My w testach nie wykorzystamy tych możliwości jakie daje CAN FD, skupimy się na klasycznym CAN, który w praktyce amatorskiej starczy nam na długie lata :)

Przy użyciu transceiverów MCP2562FD , możemy obsłużyć 112 węzłów przy gwarantowanej prędkości 1 Mb/s. Fizycznie jest to prawdopodobnie znacznie większa ilość w przypadku MCP2562FD, ponieważ dane wziąłem z  transceivery MCP2562 (bez FD) gdzie były dostępne w specyfikacji.
W przypadku MCP2562FD nie znalazłem tej danej, ale wiemy , że w urządzeniu tym poprawiono szereg parametrów transmisyjnych aby mógł działać na dużo większych prędkościach transmisji np 8 Mb/s

Skoro mamy mniej więcej pogląd jak wygląda struktura sieci CAN (patrz rysunek wyżej), zobaczmy co lata po tej sieci i tutaj mamy dobry moment aby zapoznać się ze strukturą ramki CAN. Ramka to pakiet uporządkowanych bitów zaczynający się sygnałem początku ramki SOF (Start of Frame) i kończącym sekwencją 7-mio bitową EOF (End of Frame). Po każdej przesłanej ramce następuje minimum 3 bitowa przerwa (IFS)

Ramka w standardzie 2.0A


Ramka w standardzie 2.0B


W ramce 2.0A interesują nas w zasadzie tylko cztery jej fragmenty :

- 11-bitowy identyfikator ramki SID (identyfikator jest synonimem adresowania urządzeń ale w praktyce służy do adresowania wiadomości/ramek. Ramka z ustawionym adresem jest rozgłaszana w sieci i tutaj mamy dwie opcje w odbiorze, które możemy sobie ustawić . Pierwsza polega na tym, że wszystkie węzły odbierają ramkę a druga polega na tym, że wiadomość odbiera tylko jeden lub kilka wybranych węzłów , które mają ustawiony filtr lub maskę akceptujący adres odbieranej ramki. Ot i to jest cała filozofia quasi adresowania urządzeń. Dodatkową funkcję jako pełni identyfikator jest arbitraż ramek, czyli na podstawie jego wartości jest ustawiany priorytet dostępu ramki do magistrali CAN. Im mniejsza wartość identyfikatora tym większy priorytet. Tu jedna istotna uwaga, nie mogą w najprostszym układzie współistnieć ramki o tym samym adresie.

- RTR bit (Remote Transmission Request) - znacznik ramki zdalnej (bez danych). Jeśli chcemy odpytać jakiś węzeł o dane i np.nie chcemy mu przesyłać żadnych danych ustawiamy ten bit na 1. Węzeł docelowy   po zidentyfikowaniu (zastosowany filtr lub maska), że ramka/pytanie jest przeznaczone dla niego, odpowiada automatycznie przesyłając ramkę z danymi do węzła wywołującego.

- DLC (Data Lenght Code) - długość pola danych w bajtach. W jednej ramce możemy przesłać maksymalnie 64 bity danych czyli 8 bajtów. W DLC na 4-bitach musimy określić ile danych przesyłamy. W trybie ramki zdalnej (tryb RTR = 1) DLC=0.

- DATA FIELD - tu wbijamy nasze dane maksymalnie 64 bity (8 bajtów), wartości odczytane z czujników np temperatury etc.

Znajomość wymienionych powyżej fragmentów ramki CAN całkowicie nam wystarczy aby ogarnąć transmisję . O reszcie można sobie doczytać.

Musimy jeszcze rozjaśnić do czego służą filtry i maski stosowane w węzłach .
Jeśli przykładowo chcemy aby nasz węzeł odbierał wiadomości zaadresowane na wartość 0x123 (ustawione w 11-bitowym identyfikatorze ramki), musimy w odpowiednim rejestrze mikrokontrolera ustawić tę wartość.
Takich rejestrów spełniających rolę filtrów mamy do dyspozycji 16 szt czyli mamy innymi słowy 16 filtrów. Adres przychodzącej ramki jest porównywany z wartością ustawioną w rejestrze mikrokontrolera , jeśli adres ramki będzie miał wartość 0x123, dane z ramki zostaną zapisane w buforze odbiorczym w innym przypadku ramka przychodząca zostanie zignorowana.

Maska natomiast służy do maskowania fizycznego poszczególnych bitów lub grupy bitów , jest takim filtrem z grubsza umożliwiającym odbieranie ramek o adresach z jakiegoś zakresu/przedziału wartości np 100-199 etc. Masek mamy do dyspozycji 3 szt.

Jeszcze jedna rzecz o której musimy mieć blade pojęcie aby zrozumieć proces konfiguracji rejestrów kontrolera CAN. Mianowicie musimy wiedzieć jak wygląda bit w procesie wysyłania na magistralę CAN. Podstawową jednostką czasu w sieci CAN jest kwant (Time Quantum - TQ) i jest to interwał czasu pomiędzy kolejnymi cyklami zegara taktującego. Bit składa się z czterech  segmentów, z których każdy trwa przez czas będący wielokrotnością kwantu TQ . Na rysunku poniżej pokazana jest struktura bitu.




Odczyt wartości bitu w odbiorniku następuje w punkcie próbkowania (pomiędzy segmentami bufora fazy nr 1 i nr 2). Poszczególne segmenty spełniają następujące funkcje :

- Segment synchronizacji służy jak sama nazwa wskazuje do synchronizacji z magistralą.
- Segment propagacji ma na celu kompensację opóźnień propagacji sygnału na magistrali. Powinien mieć długość dwukrotności czasu propagacji sygnału na magistrali i w obwodach wejściowych węzła.
- bufor fazy nr 1 i nr 2 pozwalają na dostosowanie czasu trwania bitu do przesunięć fazy w czasie synchronizacji z magistralą.

Trochę to wygląda na skomplikowane aspekty ale daje wyobrażenie jak precyzyjnie możemy dopasować czasowo i fazowo węzły do magistrali dysponując odpowiednią aparaturą aby parametry te pomierzyć na linii. Hobbysta takiej aparatury nie ma ale musi mieć świadomość istnienia zależności w strukturze bitu bo czas trwania bitu jest w procesie konfiguracji kontrolera CAN uwzględniany.

Warto też nadmienić , że wszystkie węzły CAN muszą być ustawione na tę samą prędkość transmisji . Jednakowa prędkość transmisji pozwala wszystkim węzłom próbkować sygnał na magistrali w tym samym czasie co jest gwarancją poprawnego odczytania przesłanej wiadomości.

Od ogółu do szczegółu, rozwiniemy teraz temat podstawowego elementu konfiguracji CAN w mikrokontrolerze PIC24HJ128GP502. Zajmiemy się szczegółowiej tzw BIT TIMING czyli ustawieniem w rejestrach mikrokontrolera zależności czasowych w strukturze bitu to jak wspomniałem jest podstawowym elementem konfiguracji sieci CAN.

Na początek obrazek :


Powyższy wykres obrazuje m.in jakie bity i w jakich rejestrach należy ustawić aby skonfigurować zależności czasowe dla BIT Timing-u.

Najpierw musimy ustalić dwa parametry :
FTQ (częstotliwość dla TQ - Time Quantum) i czas trwania bitu (wszystkich segmentów) - CAN Nominal Bit Time (patrz obrazek powyżej), który musi się mieścić w przedziale 8-25 TQ .
O zasadach ustalania długości poszczególnych segmentów bitu doczytamy w doumentacji od Microchipa (Enhanced Controller Area Network - ECAN i ECAN operation with DMA).

FTQ = N*FBAUD

N- długość trwania bitu (wszystkich segmentów) wyrażony w TQ (Time Quantum). Ustawiamy w/g zaleceń producenta dla FCY = 40MHz (częstotliwość taktowania mikrokontrolera) na 20TQ.

FBAUD - prędkość magistrali CAN, przyjmujemy na 1 Mb/s

stąd FTQ po wyliczeniu w/g powyżej podanego wzoru wynosi  20 MHz

Kolejnym krokiem jest wyliczenie wartości BRP (Baud Rate Prescaler) w/g wzoru :


BRP = (FCY / (2 * FTQ))-1
BRP = (40MHz / (2 * 20MHz))-1 = 0
  
i na taką wartość ustawiamy bit BRP w rejestrze C1CFG1 mikrokontrolera

Ostatnią rzeczą , która została nam w procesie konfiguracji BIT TIMING-u jest ustalenie długości poszczególnych segmentów bitu. Pamiętamy z wykresów powyżej , że mamy cztery segmenty bitu :

- Segment synchronizacji jego długość jest stała i wynosi 1TQ
- Segment propagacji typowa długość zalecana przez Microchipa to 5TQ
- bufor fazy nr 2 wynosi w/g zaleceń 30 % długości całego bitu  czyli 20TQ*30% = 6TQ
- bufor fazy nr 1 wyliczamy znając długość pozostałych segmentów 20TQ - (1TQ + 5TQ + 6TQ) = 8TQ

Na powyższym wykresie widzimy jeszcze jakieś UFO w postaci bitu konfiguracyjnego oznaczonego SJW (Synchronization Jump Width), jest to element wykorzystywany w procesie początku synchronizacji bitu czyli wspomaga segment synchronizacji, i fizycznie niweluje przesunięcia fazy w taktowaniu zegara w poszczególnych węzłach.  SJW nie może być dłuższy niż segment bufora fazy nr 2, wartość wybieramy z przedziału 1-4 TQ.

Uff..... przebrneliśmy jakoś przez opis etapu konfiguracji BIT Timing-u czyli zależności czasowych w sieci CAN niezbędnych do jej prawidłowego działania.
Na początku wydaje się być to trochę zagmatwane ale przeczytanie kilka razy dokumentacji dostarczonej przez Microchipa (w linkach) i za którymś razem klapka zaskoczy.

Kiedy po raz pierwszy zerknąłem na rejestry mikrokontrolera dotyczące CAN to mi trochę włosy dęba staneły, mnogość opcji i podopcji oraz bogactwo implementacji za pomocą DMA , FIFO etc nie wspominając o obsłudze błędów i przerwań.

Jest tego trochę sporawo i może to przytłaczać przeciętnego zjadacza chleba z drugiej jednak strony bogactwo możliwości konfiguracyjnych w zakresie CAN zaimplementowanych w mikrokontrolerze PIC budzą podziw i szacunek dla producenta.  Pozatym im trudniej tym większe wyzwanie i tym większa radość potem z okiełznania tego.

Wiemy jak mniej więcej wygląda ramka CAN i konfiguracja czasowa bitu BIT Timing. Czas zatem na kolejne ważne zagadnienie jakim jest pakowanie ramek w bufory. Wysyłanie ramek na magistralę CAN odbywa się fizycznie z uporządkowanej przestrzeni pamięci DMA podzielonej na bufory.
Minimalna ilość buforów jaka jest potrzebna w procesie komunikacji to 2 szt, jeden bufor transmisyjny i jeden odbiorczy. Każdy bufor ma rozmiar 8 słów 16 bitowych.
Maksymalna ilość buforów jaką możemy utworzyć w pamięci DMA do współpracy z CAN to 32 szt, przy czym z tej puli możemy wydzielić maksymalną ilość buforów nadawczych 8 szt.  Na dowolnej grupie buforów możemy zastosować kolejkowanie FIFO (sygnalizuję tylko taką możliwość i nie rozwijam tematu, do doczytania w dokumentacji) .

Należy jeszcze wspomnieć o konfiguracji buforów ponieważ jest to trochę zagmatwane zagadnienie. Wiemy, że buforów mamy maksymalnie 32 szt rozpoznawanych jako Bufor0 ...... Bufor31.

Dostęp do buforów Bufor0....Bufor7 mamy za pomocą rejestru C1TRmnCON
(tylko ten zakres buforów możemy ustawić jako nadawcze)

Dostęp do buforów Bufor8....Bufor14 mamy za pomocą rejestrów do obsługi filtrów C1BUFPNT3 i C1BUFPNT4

Dostęp do rejstrów Bufor15....Bufor31 mamy za pomocą rejestrów obsługujących FIFO.

Najłatwiej ustawiać bufor nadawczy i odbiorczy w puli pierwszej czyli Bufor0....Bufor7.

Tak łopatologicznie rzecz ujmując w procesie nadawania pełną ramkę CAN (z uwzględnieniem rozszerzenia specyfikacji 2.0B)  reprezentuje 8 słów 16 bitowych i w takiej formie dane musimy wpisać do wybranego bufora nadawczego (do wyboru mamy 8 buforów nadawczych), który mieści się w przestrzeni pamięci DMA. Całą operację dobrze ilustruje poniższy obrazek :


Jeśli wybierzemy sobie np bufor nr 0 jako nasz bufor nadawczy to tam pakujemy całą ramkę w postaci 8 słów 16 bitowych.
W procesie nadawania  Word 7 jest nieistotne i nic tam nie wpisujemy. Na powyższym rysunku mamy pełną postać ramki CAN uwzględniającą wersje rozszerzoną specyfikacji 2.0B. Jeśli korzystamy tylko ze specyfikacji 2.0A to część elementów ramki pokazanych na rysunku nie interesuje nas np EID, SRR. W procesie wysyłania ramki musimy jednak te elementy uwzględnić zerując je i wysłać z ramką.

Proces odbioru ramki CAN również jest realizowany w pamięci DMA, nie musimy tutaj korzystać z żadnych rejestrów odbiorczych (choć takie istnieją dla upartych osiołków) z których dane trzeba by było wyłuskać i przesłać do zdefiniowanych w aplikacji buforów. Trzeba przyznać, że Microchip miał dobry pomysł aby całą obsługę nadawania i odbioru realizować w pamięci DMA, nie dość, że oszczędzamy w takim podejściu cykle zegarowe na obsługę CAN to jeszcze upraszczamy wiele operacji. Przy pierwszym  podejściu do tego zagadnienia wydaje się to zbyt skomplikowane na pierwszy rzut oka, ale jak klapka zaskoczy w mózgu to dostrzeżemy mega zalety tego podejścia i  prostotę .

W procesie odbioru nieodłącznym elementem są filtry i maski wiadomości. Umożliwia nam to selektywny odbiór wiadomości. Przy czym filtr ustawiany jest na jedną wartość SID (identyfikator ramki) np 100  a maska ustawia nam grupę/zakres wartości SID np 100-199.
Filtrów mamy do dyspozycji 16 szt a masek 3 szt. Poniższe wykresy obrazują schematycznie proces odbioru i filtrowania. Odebrana ramka znajdująca się w zdefiniowanym buforze odbiorczym DMA a konkretnie element Word 0 (słowo 16 bitowe w którym znajduje się SID (identyfikator ramki) trybu 2.0A) zostaje porównana z wartością filtra i/lub maski. Jeśli wartość SID w odebranej wiadomości pokrywa się z wartością filtra i/lub maski  ustawionymi w rejestrach,wiadomość jest odbierana, w innym przypadku odrzucana. Wartość filtra i maski definiujemy w odpowiednich rejestrach mikrokontrolera.

Warto tutaj nadmienić, że dostęp do rejestrów filtrów i masek tzn do modyfikowania ich wartości jest możliwy jeśli w rejestrze C1CTRL1 ustawimy bit WIN = 1 . Po ustawieniu filtrów i masek blokujemy dostęp czyli bit WIN = 0.




Pisać na temat CAN w szczególności o wszystkich opcjach i podopcjach dostępnych w mikrokontrolerach PIC można by było jeszcze długo, nie tknąłem tematu obsługi błędów czy przerwań, tutaj już odsyłam do dokumentacji jaką dostarcza Microchip lub do kodu jaki prezentuję na końcu artykułu.
Jest tego naprawdę sporo i można na tym jakiś doktorat zrobić. Ponieważ dziecko poznaje świat na początku przez dotknięcie zatem i my tak zrobimy, uchwycimy w ten sposób jakiś przyczółek ku dalszemu zrozumieniu CAN.


Czas zatem na zajęcia praktyczne z CAN.

Najpierw opis części sprzętowej.
Do zajęć praktycznych potrzebujemy zestawić w minimalnym wymiarze dwa węzły, które będą ze sobą się komunikować.


Węzeł nr 1  :

- mikrokontroler PIC24HJ128GP502 zegar węwnętrzny ok 40 MHz
- kontroler CAN : MCP2562FD lub MCP2562
- wyświetlacz znakowy DOGM204 firmy Electronics Assembly (mój obecnie ulubiony)
- zasilacz +5V i +3.3V
- skrętka UTP dwuprzewodowa

Węzeł nr 2  :
- mikrokontroler PIC24HJ128GP502 zegar węwnętrzny ok 40 MHz
- kontroler CAN : MCP2562FD lub MCP2562
- zasilacz +5V i +3.3V

+5V potrzebne jest do zasilania kontrolera CAN - MCP2562FD, który  bez problemu współpracuje z mikrokontrolerami +3.3V.

Poniżej schemat podłączeń Węzła nr 1 :






Wyświetlacz w Węźle nr 1 będziemy potrzebować aby wyświetlić odebrane z Węzła nr 2 dane.

Na schemacie zwracam uwagę na podłączenie napięcia zasilania +5V do pinu VDD w kontrolerze CAN - MCP2562FD

Poniżej schemat podłączeń Węzła nr 2 :




Schemat wymiany informacji między węzłami  będzie ogólnie przebiegać w/g poniższego opisu :

Węzeł nr 2 będzie wysyłał cyklicznie co 1 sekundę, ramkę w trybie 2.0B (SID 29 bity) z danymi.  Dane to inkrementowany licznik w zakresie 0-255  , czyli wykorzystamy jeden bajt danych w ramce z 8 dostępnych. Dane jakie będą przychodzić z Węzła nr 2 do Węzła nr 1 co 1 sekundę to cyfry 0...255.

W obu węzłach w obszarze pamięci DMA zarezerwujemy 2 bufory (nadawczy i odbiorczy). Przy czym w Węźle nr 2 korzystamy tylko z bufora nadawczego a Węźle nr 1 z odbiorczego.

Węzeł nr 2 - Bufor nr 0 wybierzemy jako nadawczy (TX) a odbiorczy (RX)(nie wykorzystujemy)  ustawimy na Buforze nr 1 SID ramki ustawimy na wartość 0x1FFEFFFF (SID na 29 bitach). Tu przy okazji ważna uwaga, wiemy , że buforów nadawczych może być maksymalnie 8 szt, ich numeracja w całej puli dostępnych 32 buforów DMA musi być w zakresie od 0 do 7 czyli Bufor nr 0 ...Bufor nr 7 możemy ustawić jako nadawcze lub odbiorcze a Bufor 8 ...Bufor31 tylko jako odbiorcze.

Węzeł nr 1 - odbieramy dane z Węzła nr 2 czyli nasłuchujemy tylko.  Dane prezentujemy  na wyświetlaczu DOGM204EA (4x20 ). Na identyfikator SID z odebranej ramki zastosujemy filtr nr 1 i maskę nr 1 .
Filtr nr 1 ustawiamy na SID ramki przychodzącej od Węzła nr 2 czyli na wartość  0x1FFEFFFF. Maskujemy taki zakres bitów aby łyknęło nam SID Węzła nr 2. Maskę zatem ustawimy sobie tak - 0x1FFFFFFF. Bufor nr 0 - nadawczy (niewykorzystujemy) i Bufor nr 1 odbiorczy.

Bufor nadawczy i odbiorczy będą fizycznie znajdować się w zarezerwowanej przestrzeni pamięci DMA.

Teraz zajmiemy się analizą  struktury danych jakie należy wpisywać do bufora nadawczego. Bufor ma rozmiar 8 słów 16 bitowych opisywanych jako Word0...Word7. Poniżej przyjrzymy się jak wygląda struktura poszczególnych słów. Jak już wspominałem aby wysłać ramkę standardową 2.0A musimy uwzględnić ustawienia dla części dotyczącej ramki 2.0B w większości przypadków będzie to tylko zerowanie wartości.

Przybliżmy zatem wartości wyjściowe dla Węzła nr 2, które będziemy ustawiać w buforze nadawczym:

- ramka nadawcza rozszerzona 2.0B w trybie normalnej ramki (RTR=0) ,
- SID =0x1FFEFFFF (identyfikator ramki, taki przyjmujemy)
- DLC = 0x1, (długość danych), korzystamy tylko z jednego bajtu, przesyłamy liczbę z zakresu 0-255 .

Word 0 (tę wartość po ustaleniu postaci poszczególnych bitów wpiszemy do pierwszego słowa bufora nadawczego Bufor[0][0] (przy założeniu że bufor nadawczy jest Buforem nr 0)


Na powyższym rysunku mamy dokładnie zobrazowane jak powinna wyglądać struktura pierwszego słowa (16bit) ramki. Tylko to nie jest rejestr mikrokontrolera, bo tak w pierwszym spojrzeniu można to odebrać.
SID0...SID10 - to identyfikator wiadomości 11 bitowy dla 2.0A (w trybie 2.0B to najstarsze bity 29 bitowego SID)
SRR i IDE są elementami ramki rozszerzonej 2.0B

Na mocy naszych założeń :
SID = 0x1FFEFFFF;
IDE =1; /*jeśli ramka ma być w standardzie rozszerzonym 2.0B to IDE=1*/
SRR = 0; /*element standardu 2.0B przy zdalnej ramce (RTR=1) musimy to ustawić na 1*/

Z powyższych danych składamy pierwsze słowo (16 bit) przesyłanej ramki.
Wartość word0 musimy wpisać do tablicy reperezentującej Bufor0  i robimy to tak :

Bufor[0][0] = word0 ;

------------------------------------------------------------------------------------------------------------

Word 1 (tę wartość po ustaleniu postaci poszczególnych bitów wpiszemy do drugiego słowa bufora nadawczego Bufor[0][1] (przy założeniu że bufor nadawczy jest Buforem nr 0)):



Na powyższym rysunku mamy zobrazowane jak powinna wyglądać struktura drugiego w kolejności słowa (16bit) ramki.
EID6...EID17 - to fragment 29-bitowego identyfikatora wiadomości ramki rozszerzonej 2.0B,

po ustaleniu postaci zapisujemy do bufora nadawczego :

Bufor[0][1] = word1 ;
------------------------------------------------------------------------------------------------------------

Word 2 (tę wartość po ustaleniu postaci poszczególnych bitów wpiszemy do trzeciego słowa bufora nadawczego Bufor[0][2] (przy założeniu że bufor nadawczy jest Buforem nr 0)):


Na powyższym rysunku mamy zobrazowane jak powinna wyglądać struktura trzeciego w kolejności słowa (16bit) ramki.

Komponenty poszczególne tego słowa to :
DLC0...DLC3 - na 4 bitach podajemy długość danych , jeśli wysyłamy 8 bajtów danych to tutaj podajemy wartość 8. W przypadku ramki zdalnej RTR=1, DLC=0.
RB0 i RB1 bity zarezerwowane nie interesują nas trzeba je tylko ustawić na 0.
RTR - włącznik zdalnej ramki
EID0...EID5 - to fragment 29 bitowego identyfikatora wiadomości ramki rozszerzonej 2.0B

Na mocy naszych założeń :
DLC = 1 (poniważ Węzeł nr 2 wysyła ramkę z jednym bajtem danych 0..255)
RTR = 0 (wysyłamy normalną ramkę)


zapisujemy do bufora nadawczego :

Bufor[0][2] = word2 ;

------------------------------------------------------------------------------------------------------------

Word3...Word6  tutaj umieszczamy dane, maksymalnie 8 bajtów rozłożonych po dwa na jedno słowo 16-bitowe i zapisujemy do bufora nadawczego
Bufor[0][3] = word3 ;
Bufor[0][4] = word4 ;
 Bufor[0][5] = word5 ; 
 Bufor[0][6] = word6 ; 
------------------------------------------------------------------------------------------------------------

Word7 - olewamy i nawet nie wpisujemy do bufora nadawczego.

I to by było na tyle jeśli chodzi o strukturę danych jakie powinniśmy wpisać do bufora nadawczego nr 0 dla Węzła 2. 
Do Węzła nr 1 nic nie trzeba wpisywać bo tutaj tylko odbieramy dane, wartości ustawione w programie nie są wykorzystywane i nie są wpisywane w bufor nadawczy.

Trwa testowanie kodu na żywym organizmie. Genialne jest to , że debugowanie protokołu transmisji CAN udało się dokonać bez użycia oscyloskopu czy też analizatora stanów ale za pomocą jednej diody LED i przerwań obsługujących szerokie spektrum błędów zaimplementowanych w module CAN mikrokontrolera PIC24HJ128GP502 :)

Struktura programu  :

Pliki nagłówkowe :

- ustaw_zegar.h

- dogm204.h (Węzeł nr 1 tylko)
- ECAN1Drv.h
- ECAN1Config.
h
- common.h (struktura odbiorcza i nadawcza)

Pliki wykonawcze :

- ustaw_zegar.c (konfiguracja zegara wewnętrznego na ok 40 MHz)
- dogm204.h (Węzeł nr 1 tylko - obsługa wyświetlacza DOGM204EA)
- ECAN1Drv.c (obsługa filtrów, masek, zapisu word0-word6 do bufora nadawczego,przepisywanie odebranych danych do struktury w pamięci RAM)
- ECAN1Config.c (konfiguracja kanałów DMA, BIT TIMING, aktywacja filtrów i masek, numeracja buforów)
- main.c (wysyłamy co 1 sekundę ramkę ze zmienną licznik, centrum dowodzenia przerwaniami CAN)


Najważniejsze elementy konfiguracji CAN w programie to :

- ustawienie dwóch kanałów DMA do współpracy z kontrolerem CAN, kanał DMA nr 0 przyporządkowano do modułu transmisyjnego CAN a kanał DMA nr 2 przydzielono do modułu odbiorczego CAN

- konfiguracja BIT TIMING-u czyli poszczególnych komponentów bitu ramki CAN.

- rezerwacja w pamięci DMA dwóch tablic 8-mio elementowych po 16 słów, na bufor nadawczy i odbiorczy

przyporządkowanie do poszczególnych tablic w pamięci DMA bufora nadawczego i odbiorczego.


Obsługa odebranej ramki jest realizowana za pomocą przerwania .
W przerwaniu następuje przepisanie z bufora odbiorczego z pamięci DMA danych i parametrów ramki do struktury w pamięci RAM. Dostęp do odebranych danych mamy ładnie za pomocą struktury odbiorczej.

Nie wszystkie funkcję są w programie wykorzystane, przydadzą się przy dalszej rozbudowie kodu.

Na zakończenie kilka słów podsumowania. Zdaję sobie sprawę, że ogarnięcie ze zrozumieniem transmisji CAN a w szczególności konfiguracja kontrolera CAN, DMA ewentualnie FIFO może przysporzyć bólu głowy. Mój artykuł nie wyczerpuje wszystkich zagadnień m.in nie tknąłem tematu obsługi błędów czy dostępnych eventów CAN oraz obsługi buforów FIFO , tu by trzeba było napisać jakąś książkę bo jest tego sporo.
Na początku myślałem , że sam tego nie ogarnę i minie parę wiosen zanim coś zrozumiem. W boju okazała się, że wilk nie taki straszny jak go malują. Dużą pomocą były dodatkowe materiały jakie oferuje Microchip na temat CAN bo sam datasheet mikrokontrolera PIC24HJ128GP502 w kwestii CAN jest tak ubogi jak pracownik MPO w Zimbabwe.

Warto poświęcić dla CAN czas i uwagę , jest to zajefajna sprawa. Możliwości jakie daje są naprawdę ogromne od prostego sterowania piecem czy rozbudowanej sieci czujników/elementów wykonawczych w inteligentnym domu. W szczególności w instalacjach gdzie na pierwszym miejscu stawia się niezawodność w dostarczeniu bezbłędnie danych i odporności na zakłócenia zewnętrzne. Nie bez przyczyny przemysł motoryzacyjny wybrał ten sposób komunikacji m.in w systemach bezpieczeństwa aktywnego i biernego gdzie nie możemy sobie pozwolić na utratę nawet jednego bitu.

Dużo się dzieje w kwestii podrasowania standardu CAN tak aby można było przesyłać więcej danych w jednej ramce i szybciej, przykładem tego jest CAN - FD o którym wspominałem w artykule. Mamy wysyp transciverów w tej technologii  np MCP2562FD firmy Microchip ale nie rozpoznałem sprawy jak z dostępnością kontrolerów bo te które są dostępne w mikrokontrolerach Microchip 16 bitowych jeszcze nie wspierają tego standardu.W 32 bitowcach wybranych firmy Microchip mignęło mi natomiast , że jest ten standard wspierany. Nie wiem jak sprawa wygląda w ARM-ach na przykład.

Kilka wiadmości z ostatniej chwili . Jest możliwe spięcie Linuxa lub Node.JS z CANBus , są do tego specjalne biblioteki. Co nam to daje ??? Możemy postawić sobie serwer Node.JS np na RasberyPI lub czymś podobnym. Do serwera podpinamy sprzętowy CAN w postaci kontrolera serii MCP251x plus Transciver dowolny i już mamy wymianę danych po CANbus Serwera z mikrokontrolerami.
Serwer pośredniczy nam w wymianie danych pomiędzy klientami www a siecią mikrokontrolerów (czujników, nastawników, właczników etc.). Możliwości wręcz nieograniczone. Wiem , że tego typu rozwiązania są implementowane w automatyce budynkowej i oferowane przez specjalistyczne firmy .

Kilka fotek z mojego poligonu CAN :








Pozdrawiam
picmajster.blog@gmail.com


Linki :
Interfejsy komunikacyjne w motoryzacji
MCP2562FD - transciver CAN
PIC24HJ128GP502 - specyfikacja
Enhanced Controller Area Network - ECAN
ECAN operation with DMA

Transcivery dedykowane do 3.3V firmy Texas Instruments
Artykuł w mikrokontroler.pl
Artykuł w EP
Artykuł w EP 1
Artykuł w EP 2
Artykuł w EP 3
Strona o CAN
CAN w motoryzacji, prezentacja
CAN FD - rozszerzona wersja CAN
CAN FD - artykuł
Wykład o CAN

Linux i CANbus
Node.JS i CANbus

Kod dla Węzła nr 2

/*****************************************************************************
  FileName:        ustaw_zegar.h
  Processor:       PIC24HJ128GP502
  Compiler:        XC16 ver 1.30
******************************************************************************/

#ifndef USTAW_ZEGAR_H
#define USTAW_ZEGAR_H

#define FCY 40000000 /* podajemy wartosc ustawionego zegara (40 MHz), wazne 
aby przed includowaniem <libpic30.h>, potrzebne to jest do wyliczania delay-i*/
/*deklaracja funkcji*/
void ustaw_zegar(void) ;

#endif  /* USTAW_ZEGAR_H */

/*****************************************************************************
  FileName:        ECAN1Drv.h
  Processor:       PIC24HJ128GP502
  Compiler:        XC16 ver 1.30
******************************************************************************/

#ifndef __ECAN1DRV_H__
#define __ECAN1DRV_H__ 

#include "common.h"

void ecan1WriteRxAcptFilter(int n, long identifier, unsigned int exide,unsigned int bufPnt,unsigned int maskSel);
void ecan1WriteRxAcptMask(int m, long identifierMask, unsigned int mide,unsigned int exide);
void ecan1WriteTxMsgBuf(CANMessageTX *tx_message);
void rxECAN1(CANMessageRX *message);
void clearIntrflags(void);
void ecan1DisableRXFilter(int n);
void ecan1SendMsg(void);

#endif


/*****************************************************************************
  FileName:        ECAN1Config.h
  Processor:       PIC24HJ128GP502
  Compiler:        XC16 ver 1.30
******************************************************************************/

#ifndef __ECAN1CONFIG_H__
#define __ECAN1CONFIG_H__ 

#include "ECAN1Drv.h"

/* CAN Baud Rate Configuration      */
#define FCAN    40000000 /*(FCAN = FCY)*/
#define BITRATE 1000000  /*predkosc CAN 1Mb/s*/
#define NTQ     20      /*20 Time Quanta in a Bit Time*/
#define BRP_VAL     ((FCAN/(2*NTQ*BITRATE))-1)

/*definiujemy ilosc buforow jeden nadawczy , drugi odbiorczy czyli 2 szt maksymalnie 32 szt*/
#define  ECAN1_MSG_BUF_LENGTH   2
/*rezerwujemy w przestrzeni pamieci DMA tablice na bufor nadawczy i odbiorczy*/
extern unsigned int ecan1msgBuf [ECAN1_MSG_BUF_LENGTH][8] __attribute__((space(dma)));

/*Function Prototype*/
void ecan1init(void);
void dma0init(void);
void dma2init(void);

#endif

/*****************************************************************************
  FileName:        common.h
  Processor:       PIC24HJ128GP502
  Compiler:        XC16 ver 1.30
******************************************************************************/

#ifndef __COMMON_H__
#define __COMMON_H__ 

#define CAN_MSG_DATA    0x01 // message type 
#define CAN_MSG_RTR     0x02 // data or RTR
#define CAN_FRAME_EXT   0x03 // Frame type
#define CAN_FRAME_STD   0x04 // extended or standard

/*received message structure in RAM */
/*z tej struktury odczytujemy odebrane dane*/
typedef struct{
    /* keep track of the buffer status */
    unsigned char buffer_status;
    /* RTR message or data message */
    unsigned char message_type;
    /* frame type extended or standard */
    unsigned char frame_type;
    /* numer bufora odbiorczego*/
    unsigned char buffer;
    /* 29 bit id max of 0x1FFF FFFF 
    *  11 bit id max of 0x7FF */
    unsigned long id; 
    /* message data */
    unsigned char data[8];  
    /* Received message data length */
    unsigned char data_length;
}CANMessageRX;


/*transmit message structure in RAM*/
typedef struct{
    /*buf   -> Transmit Buffer Number 0-7*/
    unsigned int buf;
    /* 29 bit id max of 0x1FFF FFFF - Extended Identifier
       11 bit id max of 0x7FF       - Standard Identifier*/
    unsigned long id;
    /*ide -> "0"  Message will transmit standard identifier
             "1"  Message will transmit extended identifier*/
    unsigned int ide;
    /*remoteTransmit -> "0" Message transmitted is a normal message
                        "1" Message transmitted is a remote message*/
    unsigned int remoteTransmit;
    /*dataLength -> Length of Data in Bytes to be transmitted*/
    unsigned int dataLength; /*1-8*/
    /*Transmit Data Bytes */
    unsigned char data[8];  

}CANMessageTX;


#endif

/* ECAN Transmit Message Buffer Configuration

Inputs:
buf -> Transmit Buffer Number

id ->   

Extended Identifier (29-bits) : 0b000f ffff ffff ffff ffff ffff ffff ffff
                                     |____________|_____________________|
                                            SID10:0           EID17:0



Standard Identifier (11-bits) : 0b0000 0000 0000 0000 0000 0fff ffff ffff
                                                            |___________|
                                                                  SID10:0

Standard Message Format: 
                                            Word0 : 0b000f ffff ffff ffff
                                                         |____________|||___
                                                            SID10:0   SRR   IDE     

                                            Word1 : 0b0000 0000 0000 0000
                                                           |____________|
                                                              EID17:6

                                            Word2 : 0b0000 00f0 0000 ffff
                                                      |_____||       |__|
                                                      EID5:0 RTR      DLC
                                        
                                                                  
                                                                    
Extended Message Format: 
                                            Word0 : 0b000f ffff ffff ffff
                                                         |____________|||___
                                                            SID10:0   SRR   IDE     

                                            Word1 : 0b0000 ffff ffff ffff
                                                           |____________|
                                                              EID17:6

                                            Word2 : 0bffff fff0 0000 ffff
                                                      |_____||       |__|
                                                      EID5:0 RTR      DLC

ide -> "0"  Message will transmit standard identifier
       "1"  Message will transmit extended identifier



remoteTransmit -> "0" Message transmitted is a normal message
                  "1" Message transmitted is a remote message

                Standard Message Format: 
                                            Word0 : 0b000f ffff ffff ff1f
                                                         |____________|||___
                                                            SID10:0   SRR   IDE     

                                            Word1 : 0b0000 0000 0000 0000
                                                           |____________|
                                                              EID17:6

                                            Word2 : 0b0000 0010 0000 ffff
                                                      |_____||       |__|
                                                      EID5:0 RTR      DLC
                                        
                                                                  
                                                                    
                Extended Message Format: 
                                            Word0 : 0b000f ffff ffff ff1f
                                                         |____________|||___
                                                            SID10:0   SRR   IDE     

                                            Word1 : 0b0000 ffff ffff ffff
                                                           |____________|
                                                              EID17:6

                                            Word2 : 0bffff ff10 0000 ffff
                                                      |_____||       |__|
                                                      EID5:0 RTR      DLC

dataLength -> Length of Data in Bytes to be transmitted

data[0]...data[7] ->  Transmit data

*/


/***************************************************************************
    FileName:        ustaw_zegar.c
    Processor:       PIC24HJ128GP502
    Compiler:        XC16 ver 1.30
 ***************************************************************************/
    /*Ustawiamy zegar wewnetrzny na ok 40 MHz (dokladnie na 39.61375 MHz*/
    #include "xc.h" /* wykrywa rodzaj procka i includuje odpowiedni plik 
    naglówkowy "p24HJ128GP502.h"*/
    #include <stdio.h>
    #include <stdlib.h>
    #include <stdint.h> /*dyrektywy do uint8_t itp*/
    #include "ustaw_zegar.h" /*z uwagi na FCY musi byc przed #include <libpic30.h>*/

    /*definicja funkcji*/
    void ustaw_zegar(void) {
    
    /* to co mozemy ustawic za pomoca '#pragma' jest dostepne w pliku 
     xc16/docs/config_index.html*/
    
    #pragma config JTAGEN = OFF
    // Watchdog timer enable/disable by user software
    #pragma config FWDTEN = OFF 

    //********************Start Ustawien Zegara************************
    /* 
    * Fcy - zegar instrukcji , Fosc - zegar rdzenia (jest zawsze dwa razy wiekszy 
    * od zegara instrukcji)) Ustawiamy zegar instrukcji na 40 Mhz z wewnetrznego 
    * oscylatora Fin=7.37 MHz w/g wzoru Fcy=Fosc/2 gdzie Fosc=Fin x (M/(N1+N2))
    * gdzie M=43, N2=2, N1=2 ustawiane w rejestrach PLLFBD/PLLPOST/PLLPRE
    */
    //Select Internal FRC (Fast RC Oscillator)
    #pragma config FNOSC = FRC // FOSCSEL-->FNOSC=0b000 (Fast RC Oscillator (FRC))
    //Enable Clock Switching and Configure
    #pragma config FCKSM = CSECMD //FOSC-->FCKSM=0b01 - wlacz zegar
    #pragma config OSCIOFNC = OFF //FOSC-->OSCIOFNC=1 - Fcy b?dzie na pinie OSCO

    /*Config PLL prescaler, PLL postscaler, PLL divisor*/
    PLLFBD = 41 ; //M=43 (0 bit to 2 st?d 41 = 43 patrz w rejestrze), tutaj 3.685 x 43 = 158.455MHz
    CLKDIVbits.PLLPRE=0 ;  //N1=2, tutaj 7.37 MHz / 2 = 3.685 MHz
    CLKDIVbits.PLLPOST=0 ; //N2=2, tutaj 158.455 MHz / 2 = 79.2275 MHz (Fosc)
    /*   
    * UWAGA przerwania musza byc wylaczone podczas wywolywania ponizszych 
    * dwóch funkcji __builtin_write_...brak definicji w pliku naglówkowym
    * to wewnetrzne funkcje kompilatora patrz help M-LAB IDE
    * i datasheet str 140(11.6.3.1 Control Register Lock)
    */
    /*Initiate Clock Switch to Internal FRC with PLL (OSCCON-->NOSC = 0b001)*/
      __builtin_write_OSCCONH(0x01); //tutaj argumentem jest wartosc z NOSC
    /*Start clock switching*/
      __builtin_write_OSCCONL(0x01);

    /*Wait for Clock switch to occur*/
      while(OSCCONbits.COSC !=0b001);

      /*Wait for PLL to lock*/
      while(OSCCONbits.LOCK !=1) {};

    }

/***************************************************************************
    FileName:        ECAN1Drv.c
    Processor:       PIC24HJ128GP502
    Compiler:        XC16 ver 1.30
  ***************************************************************************/


#include "xc.h"
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h> /*dyrektywy uint8_t itp*/
#include <string.h>
#include "ustaw_zegar.h" /*tutaj m.in ustawione FCY*/
#include <libpic30.h> /*dostep do delay-i,musi byc po zaincludowaniu ustaw_zegar.h*/

#include "ECAN1Config.h"
#include "ECAN1Drv.h"
#include "common.h"

/* 
This function configures Acceptance Filter "n" 

Inputs:
n-> Filter number [0-15]
identifier-> Bit ordering is given below
Filter Identifier (29-bits) : 0b000f ffff ffff ffff ffff ffff ffff ffff
                                   |____________|_____________________|
                                      SID10:0           EID17:0


Filter Identifier (11-bits) : 0b0000 0000 0000 0000 0000 0fff ffff ffff
                                                          |___________|
                                                              SID10:0
exide -> "0" for standard identifier 
         "1" for Extended identifier

bufPnt -> Message buffer to store filtered message [0-15]
maskSel -> Optinal Masking of identifier bits [0-3]
                        
*/

void ecan1WriteRxAcptFilter(int n, long identifier, unsigned int exide, unsigned int bufPnt,unsigned int maskSel) {

unsigned long sid10_0=0, eid15_0=0, eid17_16=0;
unsigned int *sidRegAddr,*bufPntRegAddr,*maskSelRegAddr, *fltEnRegAddr;


    C1CTRL1bits.WIN=1;

    // Obtain the Address of CiRXFnSID, CiBUFPNTn, CiFMSKSELn and CiFEN register for a given filter number "n"
    sidRegAddr = (unsigned int *)(&C1RXF0SID + (n << 1));
    bufPntRegAddr = (unsigned int *)(&C1BUFPNT1 + (n >> 2));
    maskSelRegAddr = (unsigned int *)(&C1FMSKSEL1 + (n >> 3));
    fltEnRegAddr = (unsigned int *)(&C1FEN1);


    // Bit-filed manupulation to write to Filter identifier register
    if(exide==1) {  // Filter Extended Identifier
        eid15_0 = (identifier & 0xFFFF);
        eid17_16= (identifier>>16) & 0x3;
        sid10_0 = (identifier>>18) & 0x7FF;

        *sidRegAddr=(((sid10_0)<<5) + 0x8) + eid17_16;  // Write to CiRXFnSID Register
        *(sidRegAddr+1)= eid15_0;                   // Write to CiRXFnEID Register

    }else{          // Filter Standard Identifier
        sid10_0 = (identifier & 0x7FF);         
        *sidRegAddr=(sid10_0)<<5;                   // Write to CiRXFnSID Register
        *(sidRegAddr+1)=0;                          // Write to CiRXFnEID Register
    }


   *bufPntRegAddr = (*bufPntRegAddr) & (0xFFFF - (0xF << (4 *(n & 3)))); // clear nibble
   *bufPntRegAddr = ((bufPnt << (4 *(n & 3))) | (*bufPntRegAddr));       // Write to C1BUFPNTn Register

   *maskSelRegAddr = (*maskSelRegAddr) & (0xFFFF - (0x3 << ((n & 7) * 2))); // clear 2 bits
   *maskSelRegAddr = ((maskSel << (2 * (n & 7))) | (*maskSelRegAddr));      // Write to C1FMSKSELn Register

   *fltEnRegAddr = ((0x1 << n) | (*fltEnRegAddr)); // Write to C1FEN1 Register

   C1CTRL1bits.WIN=0;


}

/* 
This function configures Acceptance Filter Mask "m" 

Inputs:
m-> Mask number [0-2]
identifier-> Bit ordering is given below
Filter Mask Identifier (29-bits) : 0b000f ffff ffff ffff ffff ffff ffff ffff
                                        |____________|_____________________|
                                            SID10:0           EID17:0


Filter Mask Identifier (11-bits) : 0b0000 0000 0000 0000 0000 0fff ffff ffff
                                                               |___________|
                                                                  SID10:0

mide ->  "0"  Match either standard or extended address message if filters match 
         "1"  Match only message types that correpond to 'exide' bit in filter
                    
*/

void ecan1WriteRxAcptMask(int m, long identifier, unsigned int mide, unsigned int exide){

unsigned long sid10_0=0, eid15_0=0, eid17_16=0;
unsigned int *maskRegAddr;


    C1CTRL1bits.WIN=1;

    // Obtain the Address of CiRXMmSID register for given Mask number "m"
    maskRegAddr = (unsigned int *)(&C1RXM0SID + (m << 1));

    // Bit-filed manupulation to write to Filter Mask register
    if(exide==1
    {   // Filter Extended Identifier
        eid15_0 = (identifier & 0xFFFF);
        eid17_16= (identifier>>16) & 0x3;
        sid10_0 = (identifier>>18) & 0x7FF;

        if(mide==1)
            *maskRegAddr=((sid10_0)<<5) + 0x0008 + eid17_16;    // Write to CiRXMnSID Register
        else
            *maskRegAddr=((sid10_0)<<5) + eid17_16; // Write to CiRXMnSID Register
        *(maskRegAddr+1)= eid15_0;                  // Write to CiRXMnEID Register

    }
    else
    {           // Filter Standard Identifier
        sid10_0 = (identifier & 0x7FF);         
        if(mide==1)
            *maskRegAddr=((sid10_0)<<5) + 0x0008;                   // Write to CiRXMnSID Register
        else
            *maskRegAddr=(sid10_0)<<5;                  // Write to CiRXMnSID Register  
        
        *(maskRegAddr+1)=0;                         // Write to CiRXMnEID Register
    }


    C1CTRL1bits.WIN=0;  

}

/*Funkcja nadawcza wykorzystujaca strukture nadawcza*/
/*wpisujemy dane ze struktury nadawczej do bufora nadawczego*/
void ecan1WriteTxMsgBuf(CANMessageTX *tx_message){
  unsigned long word0=0, word1=0, word2=0;
  unsigned long sid10_0=0, eid5_0=0, eid17_6=0;
  unsigned int data1=0, data2=0, data3=0, data4=0;

if(tx_message->ide)
    {
        eid5_0  = (tx_message->id & 0x3F);
        eid17_6 = (tx_message->id >>6) & 0xFFF;
        sid10_0 = (tx_message->id>>18) & 0x7FF;
        word1 = eid17_6;
    }
    else
    {
        sid10_0 = (tx_message->id & 0x7FF);
    }
    
    
    if(tx_message->remoteTransmit==1) {     // Transmit Remote Frame

        word0 = ((sid10_0 << 2) | tx_message->ide | 0x2);
        word2 = ((eid5_0 << 10)| 0x0200);}

    else {
        
        word0 = ((sid10_0 << 2) | tx_message->ide);
        word2 = (eid5_0 << 10);
         }
            
// Obtain the Address of Transmit Buffer in DMA RAM for a given Transmit Buffer number

if(tx_message->ide)
    ecan1msgBuf[tx_message->buf][0] = (word0 | 0x0002);
else
    ecan1msgBuf[tx_message->buf][0] = word0;

    ecan1msgBuf[tx_message->buf][1] = word1;
    ecan1msgBuf[tx_message->buf][2] = word2;

   /*dane do wyslania maksymalnie 8 bajtow w 4 slowach 16 bitowych*/
ecan1msgBuf[tx_message->buf][2] = ((ecan1msgBuf[tx_message->buf][2] & 0xFFF0) + tx_message->dataLength) ;
     
/*tu trzeba konwertowac 8 bajtow data[0]...data[7] na 4 slowa word1...word4*/
    
data1 = (unsigned int)tx_message->data[0];
data1 = (unsigned int)(tx_message->data[1])<<8 | data1 ;
        
data2 = (unsigned int)tx_message->data[2];
data2 = (unsigned int)(tx_message->data[3])<<8 | data2;
        
data3 = (unsigned int)tx_message->data[4];
data3 = (unsigned int)(tx_message->data[5])<<8 | data3;
        
data4 = (unsigned int)tx_message->data[6];
data4 = (unsigned int)(tx_message->data[7])<<8 | data4;


    ecan1msgBuf[tx_message->buf][3] = data1;
    ecan1msgBuf[tx_message->buf][4] = data2;
    ecan1msgBuf[tx_message->buf][5] = data3;
    ecan1msgBuf[tx_message->buf][6] = data4;

}  
/*przepisujemy dane z bufora odbiorczego (z pamieci DMA) do struktury odbiorczej (w pamieci RAM)*/    
void rxECAN1(CANMessageRX *message)
{
    unsigned int ide=0;
    unsigned int srr=0;
    unsigned long id=0;
            
    /*
    Standard Message Format: 
    Word0 : 0bUUUx xxxx xxxx xxxx
                 |____________|||
                    SID10:0   SRR IDE(bit 0)     
    Word1 : 0bUUUU xxxx xxxx xxxx
                   |____________|
                        EID17:6
    Word2 : 0bxxxx xxx0 UUU0 xxxx
              |_____||       |__|
              EID5:0 RTR      DLC
    word3-word6: data bytes
    word7: filter hit code bits
    
    Substitute Remote Request Bit
    SRR->   "0"  Normal Message 
            "1"  Message will request remote transmission
    
    Extended  Identifier Bit            
    IDE->   "0"  Message will transmit standard identifier
            "1"  Message will transmit extended identifier
    
    Remote Transmission Request Bit
    RTR->   "0"  Message transmitted is a normal message
            "1"  Message transmitted is a remote message
    */
    /* read word 0 to see the message type */
    ide=ecan1msgBuf[message->buffer][0] & 0x0001;   
    srr=ecan1msgBuf[message->buffer][0] & 0x0002;   
    
    /* check to see what type of message it is */
    /* message is standard identifier */
    if(ide==0)
    {
        message->id=(ecan1msgBuf[message->buffer][0] & 0x1FFC) >> 2;        
        message->frame_type=CAN_FRAME_STD;
    }
    /* mesage is extended identifier */
    else
    {
        id=ecan1msgBuf[message->buffer][0] & 0x1FFC;        
        message->id=id << 16;
        id=ecan1msgBuf[message->buffer][1] & 0x0FFF;
        message->id=message->id+(id << 6);
        id=(ecan1msgBuf[message->buffer][2] & 0xFC00) >> 10;
        message->id=message->id+id;     
        message->frame_type=CAN_FRAME_EXT;
    }
    /* check to see what type of message it is */
    /* RTR message */
    if(srr==1)
    {
        message->message_type=CAN_MSG_RTR;  
    }
    /* normal message */
    else
    {
        message->message_type=CAN_MSG_DATA;
        message->data[0]=(unsigned char)ecan1msgBuf[message->buffer][3];
        message->data[1]=(unsigned char)((ecan1msgBuf[message->buffer][3] & 0xFF00) >> 8);
        message->data[2]=(unsigned char)ecan1msgBuf[message->buffer][4];
        message->data[3]=(unsigned char)((ecan1msgBuf[message->buffer][4] & 0xFF00) >> 8);
        message->data[4]=(unsigned char)ecan1msgBuf[message->buffer][5];
        message->data[5]=(unsigned char)((ecan1msgBuf[message->buffer][5] & 0xFF00) >> 8);
        message->data[6]=(unsigned char)ecan1msgBuf[message->buffer][6];
        message->data[7]=(unsigned char)((ecan1msgBuf[message->buffer][6] & 0xFF00) >> 8);
        message->data_length=(unsigned char)(ecan1msgBuf[message->buffer][2] & 0x000F);
    }   
}

/*zerowanie flag od wszystkich przerwan*/
void clearIntrflags(void){
/* Clear Interrupt Flags */
    IFS0=0;
    IFS1=0;
    IFS2=0;
    IFS3=0;
    IFS4=0;
   }

/*------------------------------------------------------------------------------
 Disable RX Acceptance Filter
 void ecan1DisableRXFilter(int n)
--------------------------------------------------------------------------------
n -> Filter number [0-15]
*/

void ecan1DisableRXFilter(int n)
{
unsigned int *fltEnRegAddr;
   C1CTRL1bits.WIN=1;
   fltEnRegAddr = (unsigned int *)(&C1FEN1);
   *fltEnRegAddr = (*fltEnRegAddr) & (0xFFFF - (0x1 << n));
   C1CTRL1bits.WIN=0;

}

/*wyowlanie funkcji spowoduje wyslanie ramki CAN z bufora nadawczego*/
void ecan1SendMsg(void){
    C1TR01CONbits.TXREQ0 = 1 ; /*wyslij z bufora nadawczego nr 0 ramke CAN*/
}


/*****************************************************************************
  FileName:        ECAN1Config.c
  Processor:       PIC24HJ128GP502
  Compiler:        XC16 ver 1.30
******************************************************************************/


#include "xc.h"
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h> /*dyrektywy uint8_t itp*/
#include <string.h>
#include "ustaw_zegar.h" /*tutaj m.in ustawione FCY*/
#include <libpic30.h> /*dostep do delay-i,musi byc po zaincludowaniu ustaw_zegar.h*/

#include "ECAN1Config.h"
#include "ECAN1Drv.h"

/* DMA0 Initialization for ECAN1 Transmission */
void dma0init(void){

     DMACS0=0;/*clear the colission flags*/
     /*(DMA_MODULE_ON | DMA_SIZE_WORD | DMA_DIR_WRITE_PERIPHERAL | 
        DMA_INTERRUPT_FULL | DMA_NULLW_OFF | DMA_AMODE_PERIPHERAL_INDIRECT |
        DMA_MODE_CONTINUOUS) i z tego wychodzi 0x2020*/
     DMA0CON=0x2020;/*Set up Channel 0 for peripheral indirect addressing mode normal operation, word operation*/
     DMA0PAD=0x0442;/* Set up the address of the peripheral ECAN1 (C1TXD)*/
     DMA0CNT=0x0007;/*Set the data block transfer size of 8 (0-7) */
     DMA0REQ=0x0046;    /*ECAN 1 Transmit, Automatic DMA TX initiation by DMA request*/
     DMA0STA=  __builtin_dmaoffset(ecan1msgBuf);/*DPSRAM start address offset value*/   
     DMA0CONbits.CHEN=1;/*Enable the channel 0*/
     
}

/* DMA2 Initialization for ECAN1 Reception */
void dma2init(void){

     DMACS0=0;/*clear the colission flags*/
      /*(DMA_MODULE_ON | DMA_SIZE_WORD | DMA_DIR_READ_PERIPHERAL | 
        DMA_INTERRUPT_FULL | DMA_NULLW_OFF | DMA_AMODE_PERIPHERAL_INDIRECT |
        DMA_MODE_CONTINUOUS) i z tego wychodzi 0x0020*/
     DMA2CON=0x0020;
     DMA2PAD=0x0440;/*Set up the address of the peripheral ECAN1 (C1RXD)*/
     DMA2CNT=0x0007;/*Set the data block transfer size of 8 (0-7) */
     DMA2REQ=0x0022;    /*ECAN 1 Receive */
     DMA2STA= __builtin_dmaoffset(ecan1msgBuf); 
     DMA2CONbits.CHEN=1;/*Enable the channel 2*/
     
}

/*BIT TIME config*/
void ecan1ClkInit(void){

/* FCAN is selected to be FCY*/
/* FCAN = FCY = 40MHz */
/*
Bit Time = (Sync Segment + Propagation Delay + Phase Segment 1 + Phase Segment 2)=20*TQ
Phase Segment 1 = 8TQ
Phase Segment 2 = 6Tq
Propagation Delay = 5Tq
Sync Segment = 1TQ
CiCFG1<BRP> =(FCAN /(2 ×N×FBAUD))– 1
*/
    /* Synchronization Jump Width set to 4 TQ */
    C1CFG1bits.SJW = 0x3;
    /* Baud Rate Prescaler */
    C1CFG1bits.BRP = BRP_VAL;
    /* Phase Segment 1 time is 8 TQ */
    C1CFG2bits.SEG1PH=0x7;
    /* Phase Segment 2 time is set to be programmable */
    C1CFG2bits.SEG2PHTS = 0x1;
    /* Phase Segment 2 time is 6 TQ */
    C1CFG2bits.SEG2PH = 0x5;
    /* Propagation Segment time is 5 TQ */
    C1CFG2bits.PRSEG = 0x4;
    /* Bus line is sampled three times at the sample point */
    C1CFG2bits.SAM = 0x1;
}

/*odpalamy zaleznosci czasowe czyli BIT TIME, ustawiamy filtr i maske*/
void ecan1init(void){

/* Request Configuration Mode (wchodzimy w tryb konfiguracji, tylko wtedy mozemy
   ustawic filtr i maske*/
    C1CTRL1bits.REQOP=4;
    while(C1CTRL1bits.OPMODE!=4);

    ecan1ClkInit(); 
        
/*  Filter Configuration

    ecan1WriteRxAcptFilter(int n, long identifier, unsigned int exide,unsigned int bufPnt,unsigned int maskSel)

    n = 0 to 15 -> Filter number

    identifier -> SID <10:0> : EID <17:0> 

    exide = 0 -> Match messages with standard identifier addresses 
    exide = 1 -> Match messages with extended identifier addresses 

    bufPnt = 0 to 14  -> RX Buffer 0 to 14
    bufPnt = 15 -> RX FIFO Buffer

    maskSel = 0 ->  Acceptance Mask 0 register contains mask
    maskSel = 1 ->  Acceptance Mask 1 register contains mask
    maskSel = 2 ->  Acceptance Mask 2 register contains mask
    maskSel = 3 ->  No Mask Selection
    
*/
    ecan1WriteRxAcptFilter(1,0x1FFEFFFF,1,1,0);

/*  Mask Configuration

    ecan1WriteRxAcptMask(int m, long identifierMask, unsigned int mide, unsigned int exide)

    m = 0 to 2 -> Mask Number

    identifier -> SID <10:0> : EID <17:0> 

    mide = 0 -> Match either standard or extended address message if filters match 
    mide = 1 -> Match only message types that correpond to 'exide' bit in filter

    exide = 0 -> Match messages with standard identifier addresses 
    exide = 1 -> Match messages with extended identifier addresses
    
*/
    ecan1WriteRxAcptMask(1,0x1FFFFFFF,1,1);
    
/* Enter Normal Mode (wychodzimy z trybu konfiguracji i ustawiamy normalny tryb)*/
    C1CTRL1bits.REQOP=0;
    while(C1CTRL1bits.OPMODE!=0);
    
/*ECAN transmit/receive message control */
 /*ustawiamy numer bufora nadawczego i numer odbiorczego oraz nadajemy im priorytety*/

    C1RXFUL1=C1RXFUL2=C1RXOVF1=C1RXOVF2=0x0000;
    C1TR01CONbits.TXEN0=1;          /* ECAN1, Buffer 0 is a Transmit Buffer */
    C1TR01CONbits.TXEN1=0;          /* ECAN1, Buffer 1 is a Receive Buffer */
    C1TR01CONbits.TX0PRI=0b11;      /* Message Buffer 0 Priority Level */
    C1TR01CONbits.TX1PRI=0b11;      /* Message Buffer 1 Priority Level */
        
}


/*DMA interrupt handlers - do przyszlego wykorzystania*/

void __attribute__((interrupt, no_auto_psv)) _DMA0Interrupt(void)
{
   IFS0bits.DMA0IF = 0;          /*Clear the DMA0 Interrupt Flag*/
}

void __attribute__((interrupt, no_auto_psv)) _DMA1Interrupt(void)
{
   IFS0bits.DMA1IF = 0;          /*Clear the DMA1 Interrupt Flag*/
}

void __attribute__((interrupt, no_auto_psv)) _DMA2Interrupt(void)
{
   IFS1bits.DMA2IF = 0;          /*Clear the DMA2 Interrupt Flag*/
}

void __attribute__((interrupt, no_auto_psv)) _DMA3Interrupt(void)
{
   IFS2bits.DMA3IF = 0;          /*Clear the DMA3 Interrupt Flag*/
}

/*****************************************************************************
  FileName:        main.c
  Processor:       PIC24HJ128GP502
  Compiler:        XC16 ver 1.30
******************************************************************************/

#include "xc.h"
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h> /*dyrektywy uint8_t itp*/
#include <string.h>
#include "ustaw_zegar.h" /*tutaj m.in ustawione FCY*/
#include <libpic30.h> /*dostep do delay-i,musi byc po zaincludowaniu ustaw_zegar.h*/

#include "ECAN1Config.h"
#include "ECAN1Drv.h"
#include "common.h"

/*LED1_TOG - bedziemy zmieniac stan LED*/
#define LED1_TOG PORTA ^= (1<<_PORTA_RA1_POSITION) /*zmienia stan bitu na przeciwny*/

/*Define ECAN Message Buffers*/
unsigned int ecan1msgBuf [ECAN1_MSG_BUF_LENGTH][8] __attribute__((space(dma)));
/*CAN Messages Received in RAM - new structure for received data*/
CANMessageRX rx_ecan1message;
/*CAN Messages Transmission in RAM - new structure for transmit data*/
CANMessageTX tx_ecan1message;

/*Prototype Declaration*/
void clearIntrflags(void);
void InitTimer1(void);

/*Timery Programowe*/
volatile uint8_t Timer1_Programowy , Timer2_Programowy ;
uint8_t licznik = 0 ;/*to inkremetujemy i wysylamy w ramce CAN jako dane*/

int main(void)
{

/*Configure Oscillator Clock Source*/
ustaw_zegar(); /*odpalamy zegar na ok 40MHz*/
__delay_ms(50) ; /*stabilizacja napiec*/
/*remapping pinow pod CAN, przyporzodkuj piny do CANRX i CANTX*/
/*RX ustawiamy na pinie 21 (RP10)--> C1RX wartosc 10, wpisac na bitach od 0 do 4 do rejestru RPIN26*/
RPINR26bits.C1RXR = 10/*RP10 na CAN RX*/
/*TX ustawiamy na pinie 22 (RP11)--> C1TX wartosc z tabeli 10000, wpisac na bitach od 8 do 12 do rejestru RPOR5*/
RPOR5bits.RP11R = 0b10000/*RP11 na CAN TX*/

/*konfiguracja pinow*/
PMD1bits.AD1MD=1;       /*wylaczamy ADC*/
AD1PCFGL = 0x1E3F;      /*wylaczenie linii analogowych(wszystkie linie cyfrowe)*/
TRISAbits.TRISA1 = 0 ;  /*RA1 jako wyjscie - dioda LED*/

/*Clear Interrupt Flags*/
    clearIntrflags();

/* ECAN1 Initialisation         
   Configure DMA Channel 0 for ECAN1 Transmit
   Configure DMA Channel 2 for ECAN1 Receive */
    ecan1init();
    dma0init(); 
    dma2init();
/*Enable Automatice Response to Remote Frame using Message Buffer 0*/     
/*ten fragment przyda sie w przypadku zdalnej ramki RTR=1, zalatwia automatyczna odpowiedz, dla zwyklej ramki odznaczyć to, bo beda problemy z odbiorem*/
    //C1TR01CONbits.RTREN0=1;

    /* Enable ECAN1 Interrupt */                
    IEC2bits.C1IE = 1;
    C1INTEbits.TBIE = 1;    
    C1INTEbits.RBIE = 1;
    /*Enable DMA Interrupt*/
    IEC1bits.DMA2IE = 1 ; /*DMA2 Interrupt ON*/
    IEC0bits.DMA0IE = 1 ; /*DMA0 Interrupt ON*/   

 rx_ecan1message.buffer_status = 0/*zerowanie statusu bufora odbiorczego w RAM*/

 /*wpisujemy dane do struktury nadawczej*/
 tx_ecan1message.buf = 0;
 tx_ecan1message.id = 0x1FFEFFFF;
 tx_ecan1message.ide = 1 ;
 tx_ecan1message.remoteTransmit = 0;
 tx_ecan1message.data[0] = licznik;
 tx_ecan1message.dataLength = 8;
 InitTimer1(); /*Timer 1 init*/
 /*Petla glowna programu*/  
 while (1) {
            /*co 1 sekunde wyslij ramke z danymi*/
            if(!Timer1_Programowy) {
             Timer1_Programowy = 10 ; /*Timer1 sprzetowy x Timer1_Programowy = 100ms x 10 = 1 s*/
             /* Write a Message in ECAN1 Transmit Buffer*/  
             /* ECAN1 buffer loaded with Data */
             ecan1WriteTxMsgBuf(&tx_ecan1message); 
             /*Send message from Transmit Bufor*/
             ecan1SendMsg();
             licznik++;
             if(licznik>255) {licznik=0;}
             /*aktualizujemy dane*/
             tx_ecan1message.data[0] = licznik;
          }
    }
    
}
/*CAN interrupt handlers - centrum dowodzenia przerwaniami CAN*/
void __attribute__((interrupt, no_auto_psv))_C1Interrupt(void)  
{    
    IFS2bits.C1IF = 0;    /*clear interrupt flag*/
    if(C1INTFbits.TBIF)   /*jesli ramka CAN wyslana poprawnie obojetnie z jakiego bufora nadawczego 
                         * ustawiana jest flaga TBIF w rejestrze C1INTF*/ 
    { 
        LED1_TOG ;/*zmien stan LED jak ramka poprawnie wyslana*/
        C1INTFbits.TBIF = 0/*clear interrupt flag*/
    } 
    if(C1INTFbits.RBIF)/*jesli ramka CAN odebrana poprawnie ustawiana jest flaga RBIF w rejestrze C1INTF*/
    {      
        /*read the message*/ 
        if(C1RXFUL1bits.RXFUL1==1/*jesli bufor nr 1 odbiorczy zapelniony*/
        {
            /*UWAGA tu jest wazny motyw , bez tego ustawienia nie odczyta prawidlowo bufora odbiorczego*/
            rx_ecan1message.buffer = 1/*ustaw pomocniczo numer bufora odborczego, potrzebne w funkcji rxECAN1()*/
            rx_ecan1message.buffer_status = 1/*ustaw flage statusu odebranego bufora*/
            C1RXFUL1bits.RXFUL1=0;/*clear interrupt flag*/
        }       
        rxECAN1(&rx_ecan1message);/*przepisz dane z bufora odbiorczego z pamieci DMA do struktury w pamieci RAM*/               
        C1INTFbits.RBIF = 0;/*clear interrupt flag*/
    }
}

/*Inicjacja/konfiguracja Timera1*/
void InitTimer1(void){
    /*konfiguracja Timer1*/
    T1CONbits.TON = 0 ; /*Stop the timer1*/
    TMR1 = 0x0000 ;/*Clear timer register*/
    /*Zegar ok 40 MHz Prescaler 1:64; PR1 Preload = 61897; Actual Interrupt Time = 100 ms*/
    T1CONbits.TCKPS = 0b10 ; /*Prescaler 1:64*/
   
    /*konfiguracja przerwan*/
    IFS0bits.T1IF    = 0/*Clear Timer1 Interrupt Flag*/
    IEC0bits.T1IE    = 1/*Enable Timer1 Interrupt*/
    
    /*ustaw priorytet dla przerwania*/
    IPC0bits.T1IP = 0b100 ; /*Set Timer1 Interrupt Prioryty Level 4*/

/*TMR1 zlicza do wartosci ustawionej w PR1 jesli TMR1=PR1 zglaszane jest przerwanie*/
/*40MHz / Prescaler / PR1 = ok 10 Hz (100ms)*/    
    PR1      = 61897/*Load period value*/
    T1CONbits.TON = 1 ; /*Start the timer1*/    
}

/*Obsluga wektora przerwania dla Timer1*/
void __attribute__((interrupt, no_auto_psv))_T1Interrupt(void)
{
/*kod uzytkownika do obslugi przerwania*/
    uint8_t x;
    x = Timer1_Programowy ;
    if (x) Timer1_Programowy = --x ;
    x = Timer2_Programowy ;
    if (x) Timer2_Programowy = --x ;
    
IFS0bits.T1IF = 0 ; /*Clear Timer1 Interrupt Flag*/
}

Kod dla Węzła nr 1 (wszystkie pliki Węzła nr 2 oprócz main.c są plikami wspólnymi dla Węzła nr 1, poniżej pliki dedykowane dla Węzła nr 1)

/*****************************************************************************
  FileName:        dogm204.h
  Processor:       PIC24HJ128GP502
  Compiler:        XC16 ver 1.30
******************************************************************************/
#ifndef DOGM204_H
#define DOGM204_H

/*_TRISB3 --> TRISBbits.TRISB3*/
#define TRIS_RESET   _TRISA0
#define TRIS_RW      _TRISB3
#define TRIS_RS      _TRISA2
#define TRIS_E       _TRISB2
#define TRIS_DB4     _TRISB12
#define TRIS_DB5     _TRISB13
#define TRIS_DB6     _TRISB14
#define TRIS_DB7     _TRISB15
/*_RB3 --> PORTBbits.RB3*/
#define RESET   _RA0
#define RW      _RB3
#define RS      _RA2
#define E       _RB2
#define DB4     _RB12
#define DB5     _RB13
#define DB6     _RB14
#define DB7     _RB15

/* przyporzadkowanie adresow pamieci DD-RAM do pol wyswietlacza*/
#define LCD_Line1 0x00 /*adres 1 znaku 1 wiersza */
#define LCD_Line2 0x20 /*adres 1 znaku 2 wiersza */
#define LCD_Line3 0x40 /*adres 1 znaku 3 wiersza */
#define LCD_Line4 0x60 /*adres 1 znaku 4 wiersza */

 void Wyslij_do_LCD(unsigned char bajt);
 void WlaczLCD();
 void WyswietlLCD(char *napis);
 void lcd_int(uint16_t val);
 void UstawKursorLCD(uint8_t y, uint8_t x);
 void CzyscLCD();
 void WpiszSwojeZnaki(void);
#endif  /* DOGM204_H */


/*****************************************************************************
  FileName:        dogm204.c
  Processor:       PIC24HJ128GP502
  Compiler:        XC16 ver 1.30
******************************************************************************/
#include "xc.h" /* wykrywa rodzaj procka i includuje odpowiedni plik*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h> /*dyrektywy uint8_t itp*/
#define FCY 40000000UL /* podajemy wartosc ustawionego zegara (40 MHz), wazne 
aby przed includowaniem <libpic30.h>, potrzebne to jest to wyliczania delay-i*/
#include <libpic30.h> /*biblioteka dajaca dostepp do delay-i.*/
#include "dogm204.h"

/*definicje funkcji*/
void Wyslij_do_LCD(unsigned char bajt)
{
    /*ustaw linie EN, przed wysylka danych*/

    E = 1;
    /*wyslanie 4 najstarszych bitow danych*/
    if(bajt & 0x80) DB7 = 1else DB7 = 0;
    if(bajt & 0x40) DB6 = 1else DB6 = 0;
    if(bajt & 0x20) DB5 = 1else DB5 = 0;
    if(bajt & 0x10) DB4 = 1else DB4 = 0;
    __delay_us(1);
    /*potwierdzenie wyslania danych (opadajacym zboczem EN)*/
    E = 0;
        
    /*ustawienie EN*/
    __delay_us(1);
    E = 1;
    /*wyslanie 4 najmlodszych bitow danych*/    
    if(bajt & 0x08) DB7 = 1else DB7 = 0;
    if(bajt & 0x04) DB6 = 1else DB6 = 0;
    if(bajt & 0x02) DB5 = 1else DB5 = 0;
    if(bajt & 0x01) DB4 = 1else DB4 = 0;
    __delay_us(1);
    /*potwierdz wysylke danych opadajacym zboczem EN*/
    E = 0;

    __delay_us(16);
    
}   


void WlaczLCD()
{
    /*ustawienie kierunku wyjsciowego linii podlaczonych do LCD*/
    TRIS_RESET = 0 ;
    TRIS_RW = 0 ;
    TRIS_RS = 0;
    TRIS_E = 0;
    TRIS_DB7 = 0;
    TRIS_DB6 = 0;
    TRIS_DB5 = 0;
    TRIS_DB4 = 0;

    /*zerowanie linii*/
    RESET = 1 ; /* 0 - Stan aktywny*/
    RW = 0 ;
    RS = 0/* 0 - wskazuje na rejestr rozkazow / 1 - wskazuje na rejestr danych*/
    E = 0;
    DB7 = 0;
    DB6 = 0;
    DB5 = 0;
    DB4 = 0;

    /*Start Inicjalizacji DOGM204 tryb 4-bity*/
    /*zaczekaj co najmniej 5 ms na ustabilizowanie sie napiecia*/
    __delay_ms(5);
    /*Hardware Reset 10ms*/
    RESET = 0 ;
    __delay_ms(10);
    RESET = 1 ;
    __delay_ms(1);
    
  /*Sekwencja startowa dla trybu 4-bit, patrz mini-datasheet str 5*/
  Wyslij_do_LCD(0x33);//wysylamy instrukcje do rejestru rozkazow
  Wyslij_do_LCD(0x32);
  Wyslij_do_LCD(0x2A);
  Wyslij_do_LCD(0x09);
  Wyslij_do_LCD(0x06);
  Wyslij_do_LCD(0x1E);
  Wyslij_do_LCD(0x29);
  Wyslij_do_LCD(0x1B);
  Wyslij_do_LCD(0x6E);
  Wyslij_do_LCD(0x57);
  Wyslij_do_LCD(0x72);
  Wyslij_do_LCD(0x28);
  Wyslij_do_LCD(0x0F); /*Display on, cursor on, blink on*/
  CzyscLCD();
RS = 1 ; /*przelacz na rejestr danych*/  
        
 /*Koniec inicjalizacji i ustawien wyswietlacza DOGM204*/      
}

void WyswietlLCD(char *napis)
{
    while(*napis){
    Wyslij_do_LCD(*napis++);
    }
         
}
// wyslanie liczby dziesietnej
    void lcd_int(uint16_t val)
    {
    char bufor[17];
    sprintf(bufor,"%i",val);
    WyswietlLCD(bufor);
    }

void UstawKursorLCD(uint8_t y, uint8_t x)
{
    uint8_t n ;
    /*y (wiersze) = 1 do 4*/
    /*x (kolumna) = 1 do 20*/
    /*ustal adres poczatku znaku w wierszu*/
    switch(y)
    {
        case 1: y = LCD_Line1 ;break;
        case 2: y = LCD_Line2 ;break;
        case 3: y = LCD_Line3 ;break;
        case 4: y = LCD_Line4 ;break;
    
    }
    /*ustal nowy adres pami?ci DD RAM*/
    /*ustaw bajt do Set DDRAM adres*/
    /* x odejmujemy jeden aby przekonwertowa? z 0-19 na 1-20 */
    n = 0b10000000 + y + (x-1) ;
    
    /*wy?lij rozkaz ustawienia nowego adresu DD RAM*/
    RS = 0/*stan niski na lini? RS, wybieramy rejestr instrukcji*/
    Wyslij_do_LCD(n);
    RS = 1;  /*prze??cz na rejestr danych */ 
}

void CzyscLCD()
{
    RS = 0/*przelacz na rejestr rozkazow*/
    Wyslij_do_LCD(1);
    RS = 1/*przelacz na rejestr danych*/
    /*czekaj ??*/
    __delay_us(1);

void WpiszSwojeZnaki(void) {
    /*definicja wlasnych znaków maks 8 szt*/
    char znak1[]= {0,0,14,17,31,16,14,2}; /* definicja literki e z ogonkiem */
    int i; 
    /* adresy poczatku definicji znaku to wielokrotnosc osmiu DEC(0,8,16,24,32,40,48,56)
     * ale uwaga wazne ! adresy kodowane sa na 6 mlodszych bitach dwa najstarsze bity
     * to zawsze  01 (01AAAAAA-gdzie A adres).Uwzgledniajac wartosc calego bajtu
     * adresy poczatku beda wygladal tak HEX(0x40,0x48,0x50,0x58,0x60,0x68,0x70,0x78)
     * Aby wpisac do pamieci wyswietlacza zdefiniowany znak nalezy najpierw wyslac 
     * do rejestru rozkazów (RS na 0) adres poczatku definicji znaku 
     * a w drugim kroku przesylamy dane (RS=1) 8 x bajt (tablica) definjujace obraz znaku*/
    
    RS = 0 ;/*stan niski na linii RS, wybieramy rejestr instrukcji*/
     /*wysylamy instrukcje do rejestru rozkazow (ustaw adres poczatkowy w CGRAM 
      na nasz znak w tym przypadku znak na pozycji drugiej) */
    Wyslij_do_LCD(0x48);/*wysylamy instrukcje do rejestru rozkazow 
     (ustaw adres poczatkowy w CGRAM na nasz znak w tym przypadku znak na pozycji drugiej) */
    
    RS = 1 ;/*stan wysoki na linii RS, wybieramy rejestr danych*/
    /*wysylamy 8 x bajt zdefiniowanego w tablicy znak1[] znaku*/
    for(i=0;i<=7;i++)
    {
       Wyslij_do_LCD(znak1[i]);
    }
   
    RS = 0 ;/*stan niski na lini RS, wybieramy rejestr instrukcji*/
    /*ustawiamy adres DDRAM na pierwszy znak w pierwszej linii, nie zapomnijmy
     o tym poniewaz inaczej zostaniemy w pamieci CGRAM*/
    Wyslij_do_LCD(0x80);
    RS = 1 ; /*stan wysoki na linii RS, wybieramy rejestr danych*/
}


/*****************************************************************************
  FileName:        main.c
  Processor:       PIC24HJ128GP502
  Compiler:        XC16 ver 1.30
******************************************************************************/

#include "xc.h"
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h> /*dyrektywy uint8_t itp*/
#include <string.h>
#include "ustaw_zegar.h" /*tutaj m.in ustawione FCY*/
#include <libpic30.h> /*dostep do delay-i,musi byc po zaincludowaniu ustaw_zegar.h*/

#include "dogm204.h"
#include "ECAN1Config.h"
#include "ECAN1Drv.h"
#include "common.h"


/*LED1_TOG - bedziemy zmieniac stan LED*/
#define LED1_TOG PORTA ^= (1<<_PORTA_RA1_POSITION) /*zmienia stan bitu na przeciwny*/

/*Define ECAN Message Buffers*/
unsigned int ecan1msgBuf [ECAN1_MSG_BUF_LENGTH][8] __attribute__((space(dma)));
/*CAN Messages Received in RAM - new structure for received data*/
CANMessageRX rx_ecan1message;
/*CAN Messages Transmission in RAM*/
CANMessageTX tx_ecan1message;

// Prototype Declaration
void clearIntrflags(void);
void ecan1WriteMessage(void);

int main(void)
{
/* Configure Oscillator Clock Source    */
ustaw_zegar();         /*odpalamy zegar na ok 40MHz*/
__delay_ms(50) ; /*stabilizacja napiec*/
/*remapping pinow pod CAN, przyporzodkuj piny do CANRX i CANTX*/
/*RX ustawiamy na pinie 21 (RP10)--> C1RX wartosc 10, wpisac na bitach od 0 do 4 do rejestru RPIN26*/
RPINR26bits.C1RXR = 10/*RP10 na CAN RX*/
/*TX ustawiamy na pinie 22 (RP11)--> C1TX wartosc z tabeli 10000, wpisac na bitach od 8 do 12 do rejestru RPOR5*/
RPOR5bits.RP11R = 0b10000/*RP11 na CAN TX*/

/*konfiguracja pinow*/
PMD1bits.AD1MD=1;       /*wylaczamy ADC*/
AD1PCFGL = 0x1E3F;      /*wylaczenie linii analogowych(wszystkie linie cyfrowe)*/
TRISAbits.TRISA1 = 0 ;  /*RA1 jako wyjscie - dioda LED*/

/* Clear Interrupt Flags                */
    clearIntrflags();
/* ECAN1 Initialisation         
   Configure DMA Channel 0 for ECAN1 Transmit
   Configure DMA Channel 2 for ECAN1 Receive */
    ecan1init();
    dma0init(); 
    dma2init();
/*Enable Automatice Response to Remote Frame using Message Buffer 0*/        
/*ten fragment sie przyda w przypadku zdalnej ramki RTR=1, zalatwia automatyczna odpowiedz, dla zwyklej ramki odznaczy? to bo beda problemy z odbiorem*/
    //C1TR01CONbits.RTREN0=1;

/* Enable ECAN1 Interrupt */                
        
    IEC2bits.C1IE = 1;
    C1INTEbits.TBIE = 1;    
    C1INTEbits.RBIE = 1;
/* Enable DMA Interrupt*/
    IEC1bits.DMA2IE = 1 ; /*DMA2 Interrupt ON*/
    IEC0bits.DMA0IE = 1 ; /*DMA0 Interrupt ON*/  
    rx_ecan1message.buffer_status=0/*zerowanie statusu bufora odbiorczego w RAM*/
     
    WlaczLCD();          /*inicjalizacja wyswietlacza LCD*/

    /*Petla glowna programu*/
    while (1) {
        
           /*sprawdz status bufora RAM , czy przyszly nowe dane powinny przychodzic co 1 sekunde*/
             if(rx_ecan1message.buffer_status == 1){
               CzyscLCD();
               UstawKursorLCD(1,4);
               lcd_int(rx_ecan1message.data[0]) ;
               /*tutaj zmien stan diody LED*/
                LED1_TOG ; /*zmieniaj stan LED na przeciwny*/
                rx_ecan1message.buffer_status = 0/*zeruj stan bufora po przetworzeniu danych*/
               }
               
    }
}   

/*CAN interrupt handlers - centrum dowodzenia przerwaniami CAN*/
void __attribute__((interrupt, no_auto_psv))_C1Interrupt(void)  
{    
    IFS2bits.C1IF = 0;    /*clear interrupt flag*/
    if(C1INTFbits.TBIF)   /*jesli ramka CAN wyslana poprawnie obojetnie z jakiego bufora nadawczego 
                         * ustawiana jest flaga TBIF w rejestrze C1INTF*/ 
    { 
        LED1_TOG ;/*zmien stan LED jak ramka poprawnie wyslana*/
        C1INTFbits.TBIF = 0/*clear interrupt flag*/
    } 
    if(C1INTFbits.RBIF)/*jesli ramka CAN odebrana poprawnie ustawiana jest flaga RBIF w rejestrze C1INTF*/
    {      
        /*read the message*/ 
        if(C1RXFUL1bits.RXFUL1==1/*jesli bufor nr 1 odbiorczy zapelniony*/
        {
            /*UWAGA tu jest wazny motyw , bez tego ustawienia nie odczyta prawidlowo bufora odbiorczego*/
            rx_ecan1message.buffer = 1/*ustaw pomocniczo numer bufora odborczego, potrzebne w funkcji rxECAN1()*/
            rx_ecan1message.buffer_status = 1/*ustaw flage statusu odebranego bufora*/
            C1RXFUL1bits.RXFUL1=0;/*clear interrupt flag*/
        }       
        rxECAN1(&rx_ecan1message);/*przepisz dane z bufora odbiorczego z pamieci DMA do struktury w pamieci RAM*/               
        C1INTFbits.RBIF = 0;/*clear interrupt flag*/
    }
}


2 komentarze:

  1. Super artykuł o CAN. Czegoś takiego było mi potrzeba.

    OdpowiedzUsuń
  2. Cieszę się, że mogłem pomóc moją amatorską pisaniną :)

    OdpowiedzUsuń