sobota, 11 sierpnia 2018

MCP2517FD/MCP2518FD - genialny kontroler CAN FD i współpraca z PIC32MM

O tym cudnym i zjawiskowym kontrolerze CAN FD zrobiłem wpis przy okazji zabaw z PIC24HJ - artykuł Teraz przyszła wiekopomna chwila aby ponownie się nim zająć tym razem z perspektywy 32-bitowego MCU PIC32MM firmy Microchip. Kontroler CAN FD MCP2517FD(obecnie zastąpiony przez MCP2518FD) oferowany przez Microchipa jest wybitnym w swojej klasie urządzeniem. Był to pierwszy na rynku taki kontroler w wydaniu zewnętrznym i nawet obecnie trudno znaleźć konkurenta dla niego. Warto wspomnieć, że kontroler jest sprzętowo przygotowany do pracy w sieci z protokołem DeviceNet. Jest to amerykański standard sieciowy wykorzystywany w automatyce przemysłowej. Dodatkowo znajdziemy tu unikalną funkcjonalność w postaci znaczników czasu dodawanych do ramek CAN. Układ ten od początku zetknięcia się z nim bardzo intryguje. Nie dość , że reprezentuje nową technologię w CAN i zwiększa możliwości tej sieci to jeszcze bardzo ciekawie wygląda od strony całej filozofii/koncepcji przepływu i konfiguracji danych opartych o jeden ciągły obszar pamięci RAM . Takie rozwiązanie odbiega od tradycyjnego podejścia w którym poszczególne komponenty ramki CAN umieszcza się w dedykowanych rejestrach. Przy pierwszym zetknięciu z MCP2517FD, onieśmiela ilość rejestrów konfiguracyjnych a jest ich całkiem sporo ale świadczy to o wyjątkowości tego kontrolera i drzemiących w nim potężnych możliwościach. Powiem szczerze jeśli poobcujemy z tym układem i zaprzyjaźnimy się z nim to nie będziemy chcieli spojrzeć na jakikolwiek inny kontroler CAN.
Względna złożoność kontrolera ma też swoją cenę np. w postaci bardzo małej ilości softu na GitHubie czy też w ogóle prób uruchomienia i jakichkolwiek testów w necie. Nikt się jeszcze nie odważył napisać własnego API czy biblioteki dla tego kontrolera z wyjątkiem Microchipa. Choć jest jeden czytelnik mojego bloga , który uruchomił kontroler bez zewnętrznego API i był bardzo zadowolony z rezultatów swojej pracy a podszedł do zagadnienia bardzo sprytnie robiąc zrzut on-line wszystkich rejestrów kontrolera na ekran monitora. Ciekawe podejście ciekawy człowiek. No ale ja nie jestem tak dobry jak moi Szanowni Czytelnicy dlatego do współpracy kontrolera CAN z 32-bitowym MCU PIC32MM wykorzystam API dostarczane przez Microchipa. Ale żeby nie było , że tak poszedłem całkowicie na łatwiznę to uprzejmie informuję, że API Microchipa dedykowane jest dla serii PIC32MX i SAM a dla serii PIC32MM nie , więc musiałem dokonać pewnych zmian w API czyli wykonać pracę organiczną na kodzie :).

Z rezultatów dostosowania API kontrolera CAN do współpracy z PIC32MM jestem bardzo zadowolony a nawet dumny ponieważ udało się zaimplementować prawie całe potężne API wykorzystujące wszystkie możliwości kontrolera MCP2517FD.

Do uruchomienia kontrolera CAN FD wykorzystam niezmiennie moją płytkę developerską dla PIC32MM i dodatkowe specjalnie stworzone moduły PICbus na których osadzony jest m.in kontroler CAN FD. Całość wygląda jak poniżej :



Warto przypomnieć sobie jeszcze kilka podstawowych spraw m.in co to jest te CAN FD a przede wszystkim czym się różni od CAN 2.0. Uprzejmię donoszę, że w CAN FD autor czyli firma Bosch jak głosi legenda zaimplementowała znacznie szybszy sposób przesyłania ramki CAN i jednocześnie zwiększyła ilość przesyłania danych w jednej ramce z 8 bajtów do 64 bajtów. Podczas przesyłu fragmentu ramki z polem danych i CRC, prędkość zwiększana jest z 1 Mbps do np. 8 Mbps. Sam arbitraż pozostał bez zmian czyli wynosi maks 1Mbps. Sumaryczna efektywność przesyłu danych jak gdzieś wyczytałem jest ok 30 razy większa w stosunku do wersji CAN 2.0

Jeśli chodzi o dokumentację MCP2517FD z którą warto się zaznajomić to : Datasheet i Manual oraz dwa narzędzia : RAM usage Calculations i Bit time Calculations. Wszytko to dostępne jest w zakładce Documents w linku poniżej artykułu.

Spróbujmy tak łopatologicznie wyjaśnić jak ogólnie wygląda struktura i upakowanie danych w kontrolerze czyli Message Memory Organization. Wyobraźmy sobie , że mamy kawałek pamięci RAM konkretnie o wymiarze 2048 Bajtów. Pamięć ta jest podzielona logicznie na 33 bloki funkcjonalne a są to TEF, TXQ, FIFO1....31 (na razie abstrahując od tego co te skróty oznaczają). Każdemu z tych 33 bloków możemy przyporządkować (umieścić wewnątrz bloku) od 1 do 32 elementów=wiadomości. Wiadomość Message Object jest formowana w kodzie aplikacji i przepisywana do RAM kontrolera . Typ wiadomości czyli ramka nadawana lub odbierana jest elementem konfigurowanym w każdym z 33 bloków oddzielnie a zasadniczo w każdym z 31 bloków ponieważ TEF i TXQ są funkcjonalnie tylko blokami nadawczymi. Jakimś obrazem tego co próbuję wyjaśnić  jest poniższa tabelka :


Jeśli chcemy skonfigurować dowolny blok (FIFO1...FIFO32) w pamięci RAM aby odbierać w nim wiadomości / ramki to musimy to skonfigurować odpowiednim wpisem w rejestr C1FIFOCONm. Na przykład chcemy aby blok funkcjonalny FIFO2 zajmował się odebranymi wiadomościami, musimy zatem poinformować kontroler o tej decyzji wpisem w rejestr C1FIFOCON2 i jednocześnie zadeklarować ile różnych wiadomości/Message Object będziemy obsługiwać w FIFO2 czyli od 1 do 32 szt. W ten sam sposób postępujemy jeśli chcemy dowolny blok (FIFO1...FIFO32) ustawić  na blok nadawczy i przyporządkować mu od 1 do 32 różnych wiadomości//Message Object do nadawania. Wielkość pamięci RAM limituje ile możemy maksymalnie obsłużyć ramek CAN "jednocześnie", posiłkując się kalkulatorem RAM usage Calculations wyszło mi, że dla przesyłania lub odbierania ramek z 8 bajtowym polem danych będziemy mogli obsłużyć 120 wiadomości "jednocześnie".

Cała obsługa opisanej powyższej organizacji pamięci RAM celem wysłania lub odebrania wiadomości sprowadza się do kilku prostych czynności. Pierwszym krokiem jest konfiguracja odpowiedniego bloku pamięci RAM jako TX lub RX , drugim krokiem jest zadeklarowanie "głębokości" bloku czyli ile sobie życzymy obsługiwać wiadomości (od 1 do 32) . Kolejny krok to w przypadku np. ramki TX uformowanie jej za pomocą  struktury w języku C, strukturę tę przepisujemy do pamięci RAM pod adres odpowiedniego bloku . Klikamy w odpowiednim rejestrze "enter" (bit TXREQ w rejestrze C1FIFOCONm ) i nasza ramka magicznie zostanie wysłana ze swojego miejsca w pamięci RAM w sieć CAN a my mamy pełną kontrolę za pomocą rejestrów statusowych nad stanem i losem naszej wiadomości. Wszystko jest tak "zaeventowane" , że nawet jak mucha przeleci obok naszej wiadomości to dostaniemy o tym informację :)

Teraz słów kilka na temat bloków funkcjonalnych a mamy ich trzy : TEF, TXQ i FIFO. Najprostszym do zrozumienia jest blok FIFO bo jak sama nazwa wskazuje jest to po prostu FIFO, którego elementami są obiekty w postaci wiadomości (ramki CAN) możemy tutaj nadawać i odbierać :).

TXQ (Transmit  Queue) to blok , który potrafi tylko nadawać, można go wykorzystać np do automatycznej odpowiedzi w trybie RTR ale tu trzeba mieć na uwadze, że RTR nie zadziała z CAN FD tylko dla CAN 2.0 i jest to w sumie jedyna wada protokołu CAN FD.


TEF (Transmit Event FIFO) - to jest taki blok cudak , który potrafi tylko nadawać i tylko swój identyfikator ID , brakuje w tym bloku możliwości wysyłania pola danych. Nie do końca rozumiem cel dla takiego cudaka być może służy on np. do rozsyłania znaczników czasu bo jako jedyny ma taką możliwość.

Ponieważ nie chcę w artykule za mocno skupiać się na teorii ale bardziej na praktyce dlatego przechodzimy  teraz do schematu mojego modułu w którym umieściłem kontroler MCP2517FD :


Poniżej wirtualny wygląd modułu :
warstwa Top :

warstwa Bottom :

Na pokładzie modułu znajdziemy :
MCP2517FD - kontroler CAN FD
MCP2558FD - transciver CAN FD (Microchip ma kilka transciverów CAN FD, kóre można zamiennie tutaj dać, na naszym rynku dostępny jest np..MCP2542FD)
MCP1703 - regulator napięcia 5V (zasilanie +5V potrzebujemy dla transcivera CAN)
MCP9808 - czujnik temperatury

Zauważmy , że moduł udało się obsadzić wyłącznie elementami firmy Microchip.



Na płytce dałem dwa podłączenia do jednej magistrali CAN aby móc rozgałęziać linie od urządzenia do urządzenia. Zawsze mnie nurtowało jak fizycznie spiętych jest wiele modułów do jednej magistrali przewodowej CAN. Trudno sobie wyobrazić aby każdy moduł był wcinany w sieć za pomocą np trójników kablowych ??, trochę byłoby to upierdliwe . Powiem szczerze nie wiem jak to się robi fizycznie.

W linkach poniżej artykułu znajduje się plik Gerbera z projektem płytki z MCP2517FD. Plik przesyłamy do płytkarni , która wypala nam z niego gotową płytkę.

Poniżej schemat mojej płytki developerskiej dla PIC32MM, z którego połapiemy się jakie piny są podpięte w złączu MB2 do  którego obsadziłem moduł CAN. Warto też wspomnieć , że moduł CAN wymaga dwóch napięć +3.3V i np.+12V (Vin) z którego MCP1703 robi +5V do zasilania Transcivera MCP2558FD



Poniżej rozkład pinów jaki wykorzystuję w PIC32MM0256GPM048 do sterowania MCP2517FD :


W opisie pinów INT0, INT1 ,INT2 nie oznacza przerwań od strony MCU tylko to są przerwania od strony kontrolera CAN. Od strony MCU będziemy wykrywać zmianę zbocza na pinie z 1 na 0 wykorzystując Input Change Notification (ICN) rozwinę ten temat w dalszej części artykułu.
Do wymiany danych z MCP2517FD wykorzystam interfejs komunikacyjny SPI3, który mamy dostępny na złączu MB2 mojej płytki developerskiej.
Dla poprawnej współpracy PIC32MM z MCP2517FD należy w rejestrze konfiguracyjnym dla SPI3 , SPI3CON, bit CKE ustawić na 1.

Funkcja inicjalizacji SPI3 dla PIC32MM0256GPM048 wygląda następująco :




Konfiguracja oraz Bit Timing w MCP2517FD :  

Konfiguracja jest najbardziej skomplikowanym aspektem w sieci CAN, jest tu dużo mało przyjaznych pojęć i wzorów a całość określana jest jako Bit Timing . Na szczęście nie piszemy doktoratu i możemy iść tam gdzie tylko się da na skróty. Jednym ze skrótów jest przygotowany przez Microchipa arkusz :



Za pomocą tego arkusza wygenerujemy wartości , które należy wpisać w podane rejestry kontrolera CAN aby skonfigurować poprawnie Bit Timing. Z naszej strony podajemy w wymiarze minimalnym tylko dwa parametry : SYSCLK = 20MHz (tyle ma nasz kwarc na płytce)  i Bus lenght = 10m (np.mała odległość dla testów, normalnie trzeba tutaj coś konkretnego podać 40 m/100m/200m etc.). W miarę poznania głębiej Bit Timingu możemy zmieniać świadomie inne parametry . Na razie jednak rozpatrujemy wszystko w najprostszym możliwie aspekcie co by nam czaszka nie zaparowała.
W API Microchipa ustawienie BIT Timingu sprowadza się do wywołania jednej funkcji i nie musimy w tym przypadku korzystać z kalkulatora.
Bit Timing jest aspektem niezbędnym do poprawnej pracy w sieci CAN, natomiast  jest jeszcze inny odrębny aspekt związany z konfiguracją sprzętową samego kontrolera CAN. I tym się teraz zajmiemy.

Na początek ważna informacja , niektóre parametry w rejestrach i rejestry  kontrolera CAN możemy zmieniać tylko i wyłącznie w trybie Configuration Mode. W tryb ten wchodzimy z automatu po włączeniu zasilania i przy pierwszej konfiguracji nie musimy go włączać tylko po zakończeniu działań konfiguracyjnych wyłączamy manualnie ten tryb. Jeśli podczas działania programu chcemy ponownie grzebać w rejestrach konfiguracyjnych musimy tryb Configuration Mode włączyć. Trybem tym sterujemy  wpisem w rejestr CiCON, włączenie trybu to ustawienie pola REQOP = 0b100 w tym rejestrze. W rejestrze tym ustawimy  kontroler w tryb CAN FD lub CAN 2.0
Tryb w jakim aktualnie jest kontroler odczytamy w polu OPMOD , dla trybu
Configuration Mode otrzymamy wartość OPMOD == 0b100.
Rejestry i ich elementy jakie wymagają włączenia trybu Configuration Mode aby można było je zmieniać to :

  • CiCON: TXQEN, STEF, SERR2LOM, ESIGM, RTXAT, WAKFIL, PXEDIS, ISOCRCEN
  • CiNBTCFG, CiDBTCFG, CiTDC
  • CiTXQCON: PLSIZE, FSIZE
  • CiFIFOCONm: PLSIZE, FSIZE, TXEN, RXTSEN
  • CiTEFCON: FSIZE, TEFTSEN


Pierwszym elementem konfiguracji sprzętowej kontrolera jest konfiguracja zegara/oscylatora. I tutaj jest fajna informacja taka, że nic nie musimy nigdzie ustawiać bo domyślne ustawienia rejestrów są dla naszych potrzeb na  starcie optymalne.
Zatem pierwszy element konfiguracyjny mamy za sobą uff jak było ciężko :)

Drugim elementem sprzętowej konfiguracji kontrolera jest ustawienie funkcji dla pinów. W sumie mamy do dyspozycji trzy piny oznaczone jako : INT, INT0, INT1. Nas będą interesować dwa INT0, INT1 , można je ustawić jako zwykłe GPIO lub jako eventy/przerwania dla RX i TX. Konfiguracji pinów dokonujemy w rejestrze IOCON.

Trzecim elementem sprzętowej konfiguracji kontrolera jest konfiguracja pamięci RAM i przygotowanie jej do obsługi wiadomości, które chcemy nadawać lub otrzymywać. W tym celu musimy ustalić  z jakich bloków funkcjonalnych będziemy korzystać i w jakim trybie je ustawić (RX lub TX) oraz jaką głębokość bloku będzie nam potrzebna (ile elementów FIFO lub inaczej ile wiadomości). Potrzebujemy również ustalić jaki będzie rozmiar danych w wiadomościach.
Weźmy jakiś prosty przykład np chcemy skonfigurować blok FIFO1 aby był blokiem TX (nadawczym) i miał wewnątrz dwa elementy (dwie wiadomości = dwie ramki CAN) o rozmiarze pola danych 64 bajty. W tym celu musimy :

- w rejestrze CiFIFOCON1 ustawić pole TXEN = 0b1 (Set Transmit FIFO)
- w rejestrze CiFIFOCON1 ustawić pole FSIZE = 0b00001 (Set 2 Message deep)
- w rejestrze CiFIFOCON1 ustawić pole PLSIZE = 0b111 (Set 64 data bytes)

Trochę dziwnie może wyglądać ustawienie pola PLSIZE , wartość Dec 7 (0b111) ale jak zerkniemy w to pole w rejestrze CiFIFOCONm to wszystko się rozjaśni.

Rozbudujmy nasz przykład np. chcemy skonfigurować inny blok np. FIFO2 aby był blokiem RX (odbiorczym) i miał wewnątrz trzy elementy (trzy wiadomości = trzy ramki CAN) o rozmiarze pola danych 16 bajtów. W tym celu musimy :

- w rejestrze CiFIFOCON2 ustawić pole TXEN = 0b0 (Set Receive FIFO)
- w rejestrze CiFIFOCON2 ustawić pole FSIZE = 0b00010 (Set 3 Message deep)
- w rejestrze CiFIFOCON2 ustawić pole PLSIZE = 0b010 (Set 16 data bytes)

Po skonfigurowaniu bloków , które chcemy wykorzystywać w pamięci RAM dobrym zwyczajem jest sprawdzenie za pomocą narzędzia RAM usage Calculations dostarczanego przez Microchipa , czy nasza konfiguracja nie wyszła poza rozmiar RAM-u. Poniżej zrzut z tego narzędzia uwzględniający nasz powyższy przykład konfiguracji bloków w RAM.


Z kalkulatora wynika , że nasze ustawienia bloków FIFO w pamięci RAM zajmie
216 bajtów co nie wykracza poza maksymalny rozmiar 2048 bajtów i taka konfiguracja jest OK.

Z grubsza konfiguracja byłaby za nami czyli trzy wyżej opisane elementy plus Bit Timing . Warto teraz może przyjrzeć się jak konfiguracja wygląda od strony naszego API.

API dostarczone przez Microchipa dla MCP2517FD przenosi nas na wyższy poziom abstrakcji a obsługa rejestrów de fakto jest przed nami ukryta. Musimy zdać sobie tutaj sprawę z tego , że korzystając z gotowego API mamy dużą wygodę ale jednocześnie  utrudnioną drogę do dogłębnego poznania MCP2517FD. Ponieważ przyjąłem , że będę korzystał z tego softu i na nim opieram ten wpis dlatego w dalszej części artykułu będziemy posługiwać się funkcjami z API.

Zatem zerknijmy jak wygląda przykładowa konfiguracja kontrolera CAN MCP2517FD od strony naszego API :



Jak widzimy funkcje API są przyjazne dla użytkownika i wygodne w użyciu. Sam aspekt konfiguracji Bit Timingu jest oparty o jedną funkcję. Tylko jak pisałem takie wysokopoziomowe API to nie jest dobra droga do poznania kontrolera bo zwalnia nas z potrzeby znajomości kontrolera od strony rejestrów. I tu dygresja, w taką samą pułapkę wpadają użytkownicy popularnego u nas ekosystemu STM-a gdzie zalecanymi obecnie przez producenta narzędziami są biblioteka HAL i wspomagacz Cube a to jest w/g mnie droga do nikąd a na pewno nie do poznania mikrokontrolerów STM-a.

Budowa i wysłanie wiadomości / ramki CAN w kontrolerze MCP2517FD

Jeśli chcemy wysyłać wiadomości po sieci CAN to warto mieć pojęcie jaką strukturę ma taka wiadomość. Krótko mówiąc jak wygląda ramka CAN od strony naszego kontrolera. Ramkę generalnie formujemy za pomocą zdefiniowanej wcześniej struktury w języku C, strukturę tę potem ciurkiem przepisujemy do pamięci RAM w kontrolerze i wysyłamy lub pakujemy kolejną ramkę i tak w koło Macieju aż np po ostatniej ramce odpalamy transfer. Poniżej obraz ramki jaką musimy upakować do struktury , obraz od strony kontrolera CAN :



skorelujmy to z procesem formowania ramki i wysłania jej za pomocą naszego API, posłużę się tutaj przykładem z manuala :


W ramach uzupełnienia wiedzy proponuję na stronach 5,6,7 manuala zapoznać się z opisem i rysunkami fizycznej struktury ramek CAN dla różnych jej wariantów tzn CAN FD Base, CAN FD EXT, CAN2.0 Base i EXT. Nas w głównej mierze będzie interesować wariant CAN FD Base

Spróbujmy rozszyfrować powyższy kod napisany przy użyciu API a mający za zadanie uformować wiadomość i wysłać ją w eter czyli w sieć CAN :

  •  powołujemy nowy obiekt txObj w oparciu o typ unii zdefiniowany w API.
  •  tworzymy tablicę txd do której zapakujemy pole danych do wysłania czyli fizycznie nasze dane , które chcemy przesłać ramką CAN np . odczyty z czujników, komendy etc.
  •  następnie zerujemy T0 i T1 (patrz tabelka nad kodem) za pomocą wpisów do txObj.word[0] i txObj.word[1] aby ten myk rozszyfrować należy zerknąć na budowę unii CAN_TX_MSGOBJ w API i skorelować to z tabelką Table 4-1 podaną nad kodem.
  • w kolejnych wierszach ustalamy pola ramki CAN : adres ID, elementy kontrolne, długość pola danych, wszystkie pola są wpisywane do obiektu txObj
  • inicjalizacja danych do wysłania , w pętli wypełniamy tablicę txd kolejnymi liczbami
  • powołujemy nowy obiekt txFlags w oparciu o typ zdefiniowany w API, tutaj znajdzie się event/przerwanie sygnalizujące stan wybranego  FIFO
  •  tworzymy zmienną logiczną Flush, jej stanem wybieramy czy chcemy dane wysłać od razu czy tylko pakujemy do FIFO. Flush = True, oznacza , że dane wysyłamy niezwłocznie po spakowaniu.
  • wywołujemy funkcję ....EventGet , która pobiera aktualny event dla kanału FIFO1
  • sprawdzamy warunek za pomocą eventów czy wybrane FIFO nie jest zajęte. Pakowanie danych do zajętego FIFO to nie jest dobry pomysł. Jeśli w FIFO jest wolne miejsce kopiujemy obiekt txObj i tablicę z danymi txd do pamięci RAM , po skopiowaniu nastąpi wysłanie ramki z RAM do sieci CAN.
Ciekawym aspektem jest skąd mamy wiedzieć pod jakim adresem należy umieścić kolejną porcję danych czyli naszą wiadomość. Tu z pomocą przychodzi rejestr CiFIFOUAm – FIFO USER ADDRESS REGISTER m, (m = 1 TO 31) gdzie sprzętowo jest  inkrementowany adres w miarę wpisywania danych i dostępny w każdej chwili dla użytkownika.
Fizyczny adres dla następnej wiadomości wyliczamy z poniżej podanego wzoru :

Od strony API takie niuanse sprzętowe są przed nami ukryte.

Odbiór wiadomości / ramki CAN w kontrolerze MCP2517FD

Na początek przyjrzyjmy się jak od strony naszego kontrolera CAN wygląda struktura odebranej wiadomości / ramki CAN


Widzimy, że są delikatne różnice w stosunku do analogicznej tabelki dla trybu TX. Pojawia się m.in pole dla znacznika czasowego. Teraz zasadnicze pytanie , skąd mamy wiedzieć, że przyszła jakaś wiadomość ?? Ano do sygnalizacji  wiadomości przychodzących RX, jest przeznaczony pin INT1 w MCP2517FD. Po wygenerowaniu przerwania na tym pinie musimy sprawdzić stan eventów na poszczególnych blokach w pamięci. Wtedy otrzymamy dokładną informację do jakiego bloku przyszła wiadomość. Adres pierwszej nie odczytanej wiadomości uzyskamy identycznie jak w przypadku nadawania, patrz Equation 4-1.

Rozważmy przykładowy kod API jaki znajdziemy w manualu kontrolera i który pokazuje kolejne proste kroki jakie trzeba zrobić aby za pomocą API odebrać wiadomość :


I podobnie jak w opisie nadawania rozszyfrujemy kod :

  • powołujemy nowy obiekt rxObj w oparciu o typ unii zdefiniowany w API.
  • tworzymy tablicę rxd w której znajdą się dane z naszej odebranej ramki
  • powołujemy nowy obiekt rxFlags w oparciu o typ zdefiniowany w API, tutaj znajdzie się event/przerwanie sygnalizujące stan wybranego  FIFO
  • wywołujemy funkcję ....EventGet , która pobiera aktualny event dla kanału FIFO2
  • sprawdzamy warunek za pomocą eventów czy wybrane FIFO nie jest zajęte przez nie odczytaną wcześniej wiadomość.  Jeśli FIFO jest wolne , zostanie tam zapisana nasza odebrana wiadomość, którą API przepisuje do  obiektu rxObj i tablicy z danymi rxd .
  • sprawdzamy przykładowy warunek czy adres odebranej ramki wynosi np. 0x300 i czy jest to ramka nie rozszerzona czyli Base. Do odebranych pól  ranki CAN dostajemy się wygodnie za pomocą elementów struktury obiektu rxObj, której wzorzec zdefiniowany jest w API
 W sumie wygląda to prosto.

Filtrowanie odebranej wiadomości / ramki CAN w kontrolerze MCP2517FD

Teraz zerkniemy jakie mamy możliwości filtrowania odebranej wiadomości i jak to się robi od strony API . Generalnie mamy do dyspozycji 32 filtry i maski .Od strony rejestrów za konfigurację filtrów odpowiada CiFLTCONm, aby grzebać w ustawieniach filtrów i masek bit FLTENm musi być wyzerowany (filtr off). W rejestrze tym wskazujemy za pomocą pola FnBP do którego FIFO, będzie przyporządkowany filtr o numerku wynikającym z FLTENm. W rejestrach CiFLTOBJm i CiMASKm wpisujemy fizyczną postać filtra i maski.  Ustawienie bitu na 0  w masce oznacza , że korespondujący bit adresu ID będzie akceptowany.

Uporządkujmy zatem jakie ogólnie działania w zakresie filtrowania musimy podjąć.
  1. Wyłączamy filtr/filtry z którym będziemy działać, bit FLTENm = 0 w rejestrze CiFLTCONm
  2. Definiujemy  fizyczną postać jednego lub kilku filtrów max 32 szt służy do tego rejestr CiFLTOBJm
  3. Definiujemy  fizyczną postać jednej lub kilku masek max 32 szt służy do tego rejestr CiMASKm
  4. Linkujemy  numer FIFO (od 1 do 32) z konkretnym numerkiem zdefiniowanego wcześniej filtra/maskim. Musimy zatem skorelować numer filtra na który wskazuje numerek bitu FLTENm (rejestr CiFLTCONm) z numerem FIFO  wskazywanym przez pole FnBP (rejestr CiFLTCONm)
  5. Włączamy filtr/filtry z którym będziemy działać, bit FLTENm = 1 w rejestrze CiFLTCONm
 Teraz przykład jak to się robi za pomocą API :


Zinterpretujmy powyższy kod :

  • Dezaktywujemy Filtr nr 0
  • Powołujemy nowy obiekt fObj w oparciu o typ unii zdefiniowany w API.
  • Ustalamy obraz filtra i wypełniamy odpowiednie pola w powołanym obiekcie fObj
  • Za pomocą funkcji ...FilterObjectConfigure() przepisujemy fizycznie zawartość obiektu fObj do rejestru CiFLTOBJ0
  • Powołujemy nowy obiekt mObj w oparciu o typ unii zdefiniowany w API.
  • Ustalamy obraz maski i wypełniamy odpowiednie pola w powołanym obiekcie mObj
  • Za pomocą funkcji ...MaskObjectConfigure() przepisujemy fizycznie zawartość obiektu mObj do rejestru CiMASK0 
  • Za pomocą funkcji ...FilterToFifoLink() linkujemy Filtr nr 0 z FIFO2 i włączamy filtr.
Na początku miałem jakiś problem werbalny aby zrozumieć linkowanie ale za kolejnym spojrzeniem w rejestr CiFLTCONm, neurony powiązały mi zależności i klapka zrozumienia się otworzyła. W sumie mechanizm prosty.

Dodatkowo w filtrowaniu możemy uwzględnić kombinacje w której akceptujemy ramki standardowe lub/i rozszerzone. I tak przykładowo z punktu widzenia rejestrów jeśli w CiMASKm bit MIDE ustawimy na 0 ramki standardowe i rozszerzone będą akceptowane. Jeśli chcemy tylko akceptować ramki standardowe to MIDE = 1 i dodatkowo w rejestrze CiFLTOBJm bit EXIDE = 0. Do akceptacji tylko ramek rozszerzonych potrzebujemy ustawić
MIDE = 1 i EXIDE = 1

Prosty test komunikacji pomiędzy PIC32MM a kontrolerem MCP2517FD

Najprostszy test aby sprawdzić czy jest poprawna wymiana danych między PIC32MM a kontrolerem MCP2517FD to zapisać jakieś dane do kontrolera i sprawdzić czy zostały poprawnie zapisane. Moja propozycja takiego testu to zapis do 32 bitowego rejestru CiFLTCON0 np.czterech bajtów o wartości dec 140 każdy.


Poniżej obraz wymiany danych po SPI, między PIC32MM a MCP2517. Zapis do rejestru CiFLTCON0 (adres rejestru : 0x1D0) liczby dec 140 powtórzonej na 4 bajtach rejestru (rejestr jest 32 bitowy)



33 -> 0b00100001 + 208 -> 0b11010000 (zapis do rejestru CiFLTCON0 pod adres 0x1D0)
49 -> 0b00110001 + 208 -> 0b11010000 (odczyt z rejestru CiFLTCON0  pod adresem 0x1D0)

Na wykresie od góry mamy odpowiednio :
  • sygnał zegara SPI3
  • dane odbierane przez MCU
  • dane wysyłane przez MCU
  • stan wyjścia CS
Struktura wysłanej ramki SPI to 4 bity (rodzaj operacji Read/Write) plus 12 bitów adres rejestru lub komórki pamięci RAM do , którego chcemy się odwołać potem idą 4 bajty danych zapisywane do rejestru (rejestry kontrolera są 32 bitowe ale fizycznie reprezentowane są przez 4 bajty) w naszym przypadku dane to powielona cztery razy wartość dec 140.  Adres początku rejestru CiFLTCON0 to 0x1D0 (0b000111010000) adresy kodujemy na 12 bitach w wysyłanej ramce SPI.

W pierwszych dwóch bajtach wysyłanych na linii SDO mamy zatem sekwencję  (0010 + 0001)+(11010000). Cztery pierwsze bity 0010 to wybór operacji zapisu do kontrolera CAN a pozostałe 12 bitów to adres rejestru CiFLTCON0 w naszym przypadku 0x1D0.
Dopóki na linii CS mamy sygnał niski dopóty wymiana danych pomiędzy MCU a kontrolerem CAN jest możliwa.
Na linii SDI mamy odpowiedź od kontrolera CAN , dane które bierzemy pod uwagę to 4 ostatnie bajty.
Trzeci bajt od lewej reprezentuje zawartość najmłodszego bajtu (bity 0-7) rejestru CiFLTCON0, czwarty bajt od lewej to drugi bajt rejestru (bity 8-15) etc..

A tutaj obrazek ku pamięci jak wygląda ogólnie struktura ramki SPI wysyłanej do kontrolera CAN i opis instrukcji :


Propozycja kodu w pliku main.c dla przeprowadzenia testu :

w pętli głównej co 100 ms wywołujemy funkcję testową której postać mamy poniżej, jeśli dane zostaną poprawnie zapisane i odebrane dioda LED się nie zapali:


W artykule jak dotychczas zaledwie liznąłem szeroką tematykę jaką jest MCP2517FD. Nie poruszam zagadnień związanych z przerwaniami i eventowaniem bo jest to bardzo obszerny temat ale w  dokumentacji kontrolera dobrze opisany. Nie poruszam tematu Time Stamping-u czyli znaczników czasowych. Jest to bardzo ciekawy aspekt , który umożliwia synchronizację czasową sieci. Mamy to w pakiecie razem z genialnym kontrolerem MCP2517FD. Mój artykuł proszę traktować jako taką zajawkę mniej lub bardziej udolną, która ma zachęcić w jakimś stopniu do poznania tego wspaniałego kontrolera CAN FD. Należy tutaj wspomnieć, że API przygotowane przez Microchipa da się skonwertować przy odrobinie chęci do dowolnego MCU

Testowanie praktyczne API 

W sumie jak najszybciej chciałem przejść do tego punktu i przebrnąć przez część ogólno-opisową. Teoria teorią ale najszybciej człowiek się uczy przez praktykę okraszoną przykładami.

API mam przygotowane do współpracy z PIC32MM0256GPM048, komunikacja z kontrolerem sprawdzona. Teraz czas potestować API w jakimś ciekawym przykładzie tak aby nie klepać powielonych z dokumentacji . Poznamy zatem jak za pomocą API , nadawać, odbierać, filtrować, wykorzystywać przerwanie czy może nawet użyjemy znaczników czasowych. Przykład będzie już nie z manuala ale własnego pomysłu.

do testów wykorzystam dwa zestawy jak na zdjęciu , które będą ze sobą rozmawiały po sieci CAN FD




Ponieważ w module z kontrolerem CAN mam do dyspozycji czujnik temperatury MCP9808 to spróbujemy go wykorzystać w przykładzie. Zatem na tę chwilę mój pomysł jest taki aby jeden węzeł CAN nadawał do drugiego temperaturę odczytaną z MCP9808. Odebraną temperaturę wyświetlimy na LCD. Jak znajdę czas na lutowanie to dorobię kolejny węzeł na razie skupiam się na dwóch.

Podczas testowania API i komunikacji pomiędzy modułami CAN FD natrafiłem na problem z odbiorem ramki. Aby wykluczyć czy problem leży po stronie sprzętu/modułu muszę wspomóc się prowizorycznym układem z dwoma kontrolerami MCP2517FD z pominięciem transciverów, czyli od strony połączeń najprościej jak się da. Wiem , że taka wymiana danych pomiędzy samymi kontrolerami jest możliwa. Kontrolery obsadziłem na płytkach uniwersalnych SOIC 150mils --> DIP


Prowizoryczny układ wykluczył problemy sprzętowe, udało się na nim złapać za pomocą analizatora stanów logicznych  obraz wysyłanej ramki CAN.
Kilka długich dni medytowania , wertowania dokumentacji , podglądu rejestrów i nadal odebranej ramki nie udało mi się zobaczyć w RAM kontrolera. Normalny zdrowy na umyśle człowiek dawno by się już poddał :) i rzucił to w cholerę .
Mam jednak charyzmę w dociekaniu i nie poddaję się łatwo.
Udało mi się złamać tajemnicę "enigmy" czyli dlaczego odebrana ramka nie pojawia się w pamięci RAM kontrolera MCP2517FD. Otóż aby Bit Stream Processor w MCP2517FD, zapisał odebraną ramkę do pamięci RAM, musi być koniecznie ustanowiony filtr i/lub maska odebranej wiadomości. Krótko mówiąc bez skonfigurowanego filtra i/lub maski odebrana wiadomość nie zostanie zapisana do pamięci RAM z której dopiero możemy ją odczytać.
Dojście do tego zajęło mi jak pisałem kilka dni, ale nie były to stracone dni bo dzięki temu miałem możliwość zapoznania się głębiej z rejestrami kontrolera.
A im głębiej poznaję ten kontroler CAN tym mój zachwyt nim rośnie logarytmicznie. 

Ponieważ w przypadku problemów bez zajrzenia w rejestry się nie obędzie , poniżej podaję przepis jak to prosto z wykorzystaniem funkcji API możemy zrobić. Mamy do wyboru dwie możliwości, odczyt poszczególnych bajtów rejestru lub całego rejestru w postaci jednego słowa 32 bitowego. Ja korzystałem z bajtowego odczytu a wygląda ono tak :

/* Read data back from registers*/
        uint8_t rxdreg[4];
        /*read register */
        DRV_CANFDSPI_ReadByteArray(0x5C, rxdreg, 4); /*API function */
         /*readout on the display */            
        lcd_Locate(2,1);
        lcd_Integer(rxdreg[0]);
        lcd_String(":");
        lcd_Integer(rxdreg[1]);
        lcd_String(":");
        lcd_Integer(rxdreg[2]);
        lcd_String(":");
        lcd_Integer(rxdreg[3]);
        lcd_Locate(1,1);
        lcd_String("C1FIFOCON1 :")

Powyżej mamy przykład odczytu zawartości rejestru konfiguracyjnego dla FIFO1 - C1FIFOCON1. Adres rejestru w pamięci kontrolera to 0x5C. Adresy mamy podane w datasheet. Do odczytu wykorzystujemy funkcję dostępną w API. Pierwszy odczytany bajt jest najmłodszym bajtem rejestru 32 bitowego. Bajty na wyświetlaczu są oddzielone dwukropkiem dla poprawy czytelności. Trzeba przyznać , że API Microchipa to kawałek naprawdę dobrego i pomocnego kodu.

Rzecz , która mi się niezwykle przydała przy próbach komunikacji pomiędzy węzłami sieci to pole bitowe EFMSGCNT w rejestrze CiBDIAG1BUS DIAGNOSTICS REGISTER 1. Pole to o rozmiarze dwóch bajtów jest automatycznie inkrementowane przy każdej poprawnie odebranej/nadanej ramce CAN. Czyli jeśli uda nam się nadać lub odebrać ramkę to licznik nam się zwiększa w tym polu. Zwiększenie licznika w przypadku poprawnie odebranych wiadomości nie jest równoznaczne z ich zapisem do pamięci RAM o tym trzeba pamiętać.

Ponieważ w praktyce przydało mi się zrozumienie tego co się dzieje fizycznie w buforach FIFO kontrolera dlatego poniżej trochę o tym napiszę . Przybliży nas to znacząco do poznania specyfiki kontrolera. Poniższy opis wzoruję na tym co znajdziemy w manualu ale rozszerzę go o swoje spostrzeżenia. Rozważymy nadanie wiadomości i odebranie.

Nadanie wiadomości  :

Na początek założenia :
FIFO1 skonfigurowany jako TX FIFO o głebokości 5, rejestr kontrolny dla FIFO1 to C1FIFOCON1. W rejestrze C1FIFOSTA1 znajdziemy flagi statusowe dla FIFO1 i index FIFO (FIFOCI), który jest inkrementowany przez głębokość bufora. W rejestrze C1FIFOUA1 znajdziemy tzw. adres użytkownika , który po dodaniu stałej wartości 0x400 da nam fizyczny adres początku wiadomości w pamięci RAM.


Powyżej mamy obrazek , który pokazuje stan po inicjalizacji FIFO1. Adres użytkownika początku wiadomości to np. 0x1D0 (adres fizyczny = 0x1D0 + 0x400). W rejestrze statusowym C1FIFOSTA1 odczytamy trzy flagi i indeks FIFO.  
FIFOCI = 0 (wskazuje na pierwszy element w FIFO)
TFEIF (TFERFFIF) = 1 , ustawione kiedy FIFO jest puste ((nie ma żadnej nie wysłanej wiadomości)

TFHIF (TFHRFHIF) = 1 , ustawione kiedy FIFO jest w połowie wolne

TFNIF (TFNRFNIF) = 1 , ustawione kiedy FIFO nie jest pełne

W rejestrze C1FIFOCON1 bit TXREQ = 0, oznacza , że wiadomość nie jest wysyłana, ustawienie tego bitu na 1 wysyła załadowaną do FIFO wiadomość. Na razie nic nie mamy załadowane więc TXREQ = 0.

Przechodzimy do sytuacji kiedy do FIFO załadujemy jedną wiadomość pod adres
0x1D0 (adres fizyczny = 0x1D0 + 0x400). I tu musimy się skupić bo jest ważna sprawa. Po załadowaniu do FIFO wiadomości, musimy koniecznie w aplikacji użytkownika ustawić w rejestrze C1FIFOCON1 bit UINC to ustawienie spowoduje zmianę adresu użytkownika w rejestrze C1FIFOUA1 na nową przykładową wartość 0x218 wskazującą na adres następnego elementu FIFO. Jeśli tego nie zrobimy to kolejna wiadomość wpisywana do FIFO nadpisze nam wcześniej wpisaną wiadomość. W przypadku korzystania z API wszystko dzieje z automatu i nie są potrzebne dodatkowe wpisy w rejestry.
Zatem do FIFO1 zostaje wpisana pierwsza wiadomość co pokazuje symbolicznie obrazek poniżej.


Zerkamy jak sytuacja wygląda po załadowaniu jednej wiadomości .
Rejestr C1FIFOUA1 = 0x218 ((adres fizyczny = 0x218 + 0x400) ustawiony jest na poczatek nowego wolnego miejsca dla kolejnej wiadomości
W rejestrze statusowym C1FIFOSTA1 odczytamy trzy flagi i indeks FIFO.

FIFOCI = 0 (wskazuje na pierwszą wiadomość w FIFO)
TFEIF (TFERFFIF) = 0 , ustawione kiedy FIFO jest puste ((nie ma żadnej nie wysłanej wiadomości)

TFHIF (TFHRFHIF) = 1 , ustawione kiedy FIFO jest w połowie wolne

TFNIF (TFNRFNIF) = 1 , ustawione kiedy FIFO nie jest pełne

W rejestrze C1FIFOCON1 bit ustawiamy w aplikacji użytkownika TXREQ = 1, oznacza , że wiadomość jest wysyłana. Po wysłaniu z powodzeniem wiadomości a o tym się dowiemy m.in jeśli bit TXREQ zostanie sprzętowo wyzerowany , indeks FIFO jest zwiększany o 1. 

Teraz zerkamy jakie zmiany mamy po wysłaniu jednej wiadomości. 


Widzimy , że po wysłaniu wiadomości indeks FIFO zwieksza nam wartość o 1.

FIFOCI = 1 (wskazuje na drugi element FIFO)
TFEIF (TFERFFIF) = 1 , ustawione kiedy FIFO jest puste (nie ma żadnej nie wysłanej wiadomości )

TFHIF (TFHRFHIF) = 1 , ustawione kiedy FIFO jest w połowie wolne

TFNIF (TFNRFNIF) = 1 , ustawione kiedy FIFO nie jest pełne

W rejestrze C1FIFOCON1 bit  TXREQ = 0, sprzętowo wyzerowany po wysłaniu wiadomości.

Tutaj jednak mała dygresja a w zasadzie wątpliwość, mianowicie co się dzieje fizycznie ze wpisaną do RAM kontrolera wiadomością i wysłaną już. Czy kontroler zwalniając miejsce dla następnej wiadomości czyści nam ten fragment pamięci RAM w której jest wysłana wiadomość czy nie , jeśli nie czyści to znaczy , że możemy ponownie wrócić do tej wiadomości i ją jeszcze raz wysłać po zrobieniu kolejki FIFO lub po resecie FIFO ??? Aspekt ten wymaga sprawdzenia na żywym organizmie co ninniejszym czynię ..... Mam już odpowiedź, wpisana wiadomość do FIFO i wysłana nie jest kasowana fizycznie z RAM kontrolera. Czyli następna nowa wiadomość wpisana w to miejsce  nadpisze pamięć RAM.
Jeszcze tu warto wspomnieć o zachowaniu się flagi statusowej TFEIF (TFERFFIF) jeśli wiadomość wyślemy to ten element FIFO jest z punktu widzenia statusu wolny, pomimo, że nie zwolniony z pamięci RAM, jeśli byśmy nie wysłali wiadomości to status dla tego miejsca w FIFO byłby zajęty. Ten aspekt też był dla mnie niejasny do pewnego momentu.


Odebranie wiadomości  :
 
Na początek założenia :
FIFO2 skonfigurowany jako RX FIFO o głebokości 16 elementów, rejestr kontrolny dla FIFO2 to C1FIFOCON2. W rejestrze C1FIFOSTA2 znajdziemy flagi statusowe dla FIFO2 i index FIFO (FIFOCI), który jest inkrementowany przez głębokość bufora. W rejestrze C1FIFOUA2 znajdziemy tzw. adres użytkownika , który po dodaniu stałej wartości 0x400 da nam fizyczny adres początku odebranej wiadomości w pamięci RAM.

Poniżej obraz FIFO2 po resecie lub inicjalizacji :


Adres użytkownika początku wiadomości to np. 0x338 (adres fizyczny w RAM = 0x338 + 0x400). W rejestrze C1FIFOSTA2 odczytujemy flagi :

FIFOCI = 0 (wskazuje na pierwszy element FIFO)
RFEIF (TFERFFIF) = 0 , ustawione kiedy FIFO jest pełne

RFHIF (TFHRFHIF) = 0 , ustawione kiedy FIFO jest co najmniej w połowie pełne

RFNIF (TFNRFNIF) = 0 , ustawione kiedy w FIFO jest przynajmniej jedna odebrana wiadomość.

RXOVIF = 0 , ustawione kiedy przyjdzie nowa wiadomość a FIFO jest pełne na full.

Poniżej mamy obraz kiedy nastąpi odbiór jednej wiadomości :

do pierwszego elementu na który wskazuje adres w rejestrze C1FIFOUA2 = 0x338, zostaje odebrana wiadomość.
W rejestrze C1FIFOSTA2 odczytujemy flagi :

FIFOCI = 1 (wskazuje na drugi element FIFO)
RFEIF (TFERFFIF) = 0 , ustawione kiedy FIFO jest pełne

RFHIF (TFHRFHIF) = 0 , ustawione kiedy FIFO jest co najmniej w połowie pełne

RFNIF (TFNRFNIF) = 1 , ustawione kiedy w FIFO jest przynajmniej jedna odebrana wiadomość.

RXOVIF = 0 , ustawione kiedy przyjdzie nowa wiadomość a FIFO jest pełne na full.

Po odebraniu wiadomości w aplikacji użytkownika musimy ustawić w rejestrze C1FIFOCON2 bit UINC, to ustawienie spowoduje zmianę adresu użytkownika w rejestrze C1FIFOUA2 na nową przykładową wartość 0x384 wskazującą na adres następnego elementu FIFO. Choć tutaj mała uwaga, ten powyższy adres podany w przykładzie z manuala jest dosyć niefortunny bo wskazuje, że jedna wiadomość ma rozmiar 76 bajtów co jest błędną wartością bo powinna mieć 72 bajty i wtedy adres następnej wiadomości powinien być 0x380. Zwrócił mi na to uwagę jeden z czytelników , za co mu dziękuję. Ale dla naszych rozważań w tym miejscu nie ma to istotnego znaczenia.

Poniżej obrazek po odebraniu i odczytaniu przez aplikację użytkownika wiadomości :


Rejestr C1FIFOUA2 wskazuje na nowy adres użytkownika 0x384 (adres fizyczny w RAM = 0x384 + 0x400) a flagi w rejestrze C1FIFOSTA2 będą w konfiguracji jak poniżej :

FIFOCI = 1 (wskazuje na drugi element FIFO)
RFEIF (TFERFFIF) = 0 , ustawione kiedy FIFO jest pełne

RFHIF (TFHRFHIF) = 0 , ustawione kiedy FIFO jest co najmniej w połowie pełne

RFNIF (TFNRFNIF) = 0 , ustawione kiedy w FIFO jest przynajmniej jedna odebrana wiadomość.

RXOVIF = 0 , ustawione kiedy przyjdzie nowa wiadomość a FIFO jest pełne na full.


Przerwanie INT1 dla RX (odbiór)

W MCP2517FD mamy pin oznaczony jako INT1, pin ten może być zwykłym pinem I/O lub pinem na którym otrzymamy zmianę stanu z 1 na 0 po odebraniu jakiejkolwiek wiadomości. Ponieważ mam zamiar w programie użyć tego pinu do sygnalizacji odebrania wiadomości, dlatego przyjrzymy się krokom jakie należy zrobić aby to tego doprowadzić. Czyli jak ten pin ustawić i jak uaktywnić przerwanie wyczulone na odebranie wiadomości (RX) dla przykładu z FIFO1. Z punktu widzenia API musimy wywołać trzy funkcje :

/*Input/Output configuration : use nINT0 and nINT1*/
DRV_CANFDSPI_GpioModeConfigure(GPIO_MODE_INT,GPIO_MODE_INT);
/*Interrupt RX enable*/
DRV_CANFDSPI_ModuleEventEnable(CAN_RX_EVENT);
/*Interrupt RX enable for RX FIFO Not Empty*/ 
DRV_CANFDSPI_ReceiveChannelEventEnable(CAN_FIFO_CH1, CAN_RX_FIFO_NOT_EMPTY_EVENT);

Jak widzimy działania za pomocą API są proste i zbyt nudne i kompletnie nic nam nie mówią co tu się dzieje wewnątrz kontrolera CAN. Cała prawdziwa akcja jest przed nami ukryta. Zdejmijmy zatem zasłonę API i zerknijmy od strony prawdziwego życia czyli rejestrów co i gdzie należy ustawić.
Aby ustawić pin INT1 na tryb sygnalizacji przerwania musimy zajrzeć do rejestru IOCON i upewnić się , że bit PM1 jest wyzerowany. W rejestrze tym mamy też możliwość podglądu stanu pinów.
Drugim krokiem jest włączenie przerwania RX. W tym celu zaglądamy w rejestr C1INT i tam ustawiamy bit RXIE. Jeśli przyjdzie wiadomość to flaga RXIF w rejestrze C1INT zostanie ustawiona a na pinie INT1 nastąpi zmiana stanu z 1 na 0. Ostatnim krokiem jest ustawienie na jakie konkretnie zdarzenie odbiorcze ma reagować przerwanie, w naszym przypadku ustawiamy reakcję na RX FIFO not empty w tym celu w rejestrze C1FIFOCON1 ustawiamy bit TFNRFNIE. W rejestrze C1RXIF odnajdziemy szczegółowe informacje na którym konkretnie FIFO nastąpiło wywołanie przerwania odbiorczego.

Po stronie MCU przerwanie sygnalizujące odbiór INT1 (z kontrolera) przyjmuje na klatę pin RA2, który konfiguruję dokładnie tak jak opisałem w artykule Input Change Notification (ICN) w PIC32MM


Rozminąłem się z tematem API na maksa ale doszedłem podczas pisanie tego artykułu do wniosku, że poznanie samego API nie jest trudne i zbyt ambitne a nawet można powiedzieć , że nudne, wystarczy zajrzeć w pliki nagłówkowe i zorientować się co do czego służy z grubsza. Natomiast poznanie kontrolera CAN od jego środka/rejestrów jest dopiero fascynującą przygodą i nie małym wyzwaniem aby to ogarnąć i ujarzmić.
Kontroler CAN MCP2517FD jest jak nie odkryta tajemnicza księga pełna magii i nieoczekiwanych zwrotów akcji. Żaden z dotychczas testowanych komponentów Microchipa nie sprawił mi tyle satysfakcji w poznawaniu jego tajemnic. MCP2517FD sklasyfikowałbym jako komponent przeznaczony dla ambitnych hobbystów :) tu trzeba naprawdę parę ładnych nocy przespać się zamiast z dziewczyną to z manualem i datasheet a nawet te parę nocy może okazać się nie wystarczające. Po bliższym zapoznaniu się z MCP2517FD stwierdzam z całą odpowiedzialnością za rzeczone słowa :), że ten kontroler CAN jest wybitnym układem i konstrukcją , gdyby nie jego mikraśne gabaryty to bym go oprawił w ramki i powiesił na ścianie. Cieszę się jednocześnie , że wykonałem moduły z kontrolerem CAN do mojej płytki developerskiej dla PIC32MM , bo walka na płytce stykowej gdzie coś potrafi nie "styknąć" dodatkowo przysporzyłoby niepotrzebnych problemów i wydłużyło czas do zgłębienia tajemnic MCP2517FD.

Sprzęt i program

Do testów kontrolera CAN FD i API ustanowiłem dwa węzły jeden węzeł nadawczy , który odczytuje temperaturę z MCP9808 wyświetla ją na LCD, następnie co 275 ms pakuje dane w ramkę CAN FD nadaje jej adres 0x300 i wysyła w kabel skrętkę. Po drugiej stronie kabla ramkę z temperaturą odbiera węzeł odbiorczy , który dekoduje ramkę i wyświetla na LCD pole z danymi. W przypadku kiedy jeden z węzłów będzie nieaktywny np odłączony od zasilania to w drugim na LCD nic nie będzie wyświetlane. W obu węzłach wykorzystuję FIFO1 o głębokości 0 czyli jeden element. W węźle nadawczym FIFO1 jest skonfigurowane jako nadawcze a w węźle odbiorczym jako odbiorcze.


Temperaturę przesyłamy w znakach ASCII bez znaku stopnia celcjusza. W odbiorniku widzimy dokładnie to co zostało nadane w ramce CAN.

Węzeł Nadawczy :
Piny  jakie są wykorzystywane w programie dla węzła nadawczego:

RB10,RB11,RC3,RC4,RC5,RD0 --> wyświetlacz LCD (patrz schemat płytki developerskiej)
RA7(SDI),RA8(SDO),RA10(SCK) --> SPI3 
RB5(SDA),RC9(SCL) --> I2C1 (piny alternatywne ustawiamy tak :  AFDEVOPTbits.ALTI2C = 0;) 
RB6 --> dioda LED
RB14 --> nCS(select dla MCP2517FD)

Poniżej najważniejsze fragmenty programu dla węzła nadawczego :

Fizyczna konfiguracja kontrolera MCP2517FD jaką zastosowałem w programie:


Prędkość Nominal Bit Rate czyli prędkość z jaką będzie przesyłane m.in pole arbitrażu to 125kbit/s , prędkość Data Bit Rate  czyli prędkość z jaką będzie przesyłane pole danych i CRC to 500kbit/s

Pętla główna programu, plik main.c :


Po stronie nadajnika nie używam przerwania TX sprawdzam tylko przed wysłaniem event czy bufor nie jest pełny.

W pętli głównej wykorzystałem Timer programowy oparty o Timer sprzętowy nr 2. Co 275 ms wysyłamy wiadomość do sieć CAN. Konfiguracja Timera nr 2 i obraz przerwania poniżej :


Definicja obiektu do wysłania, przepisanie go do pamięci RAM kontrolera i wysłanie wszystko za pomoca jednej funkcji API:


argument true oznacza wysłanie natychmiast a false wstrzymanie wysłania.

Odnośnie węzła nadawczego to by było tyle, program dostępny jest już na GitHub w postaci projektu do MPLABX-IDE


Węzeł Odbiorczy :
Piny  jakie są wykorzystywane w programie dla węzła odbiorczego:

RB10,RB11,RC3,RC4,RC5,RD0 --> wyświetlacz LCD (patrz schemat płytki developerskiej)
RA7(SDI),RA8(SDO),RA10(SCK) --> SPI3 
RB5(SDA),RC9(SCL) --> I2C1 (piny alternatywne ustawiamy tak :  AFDEVOPTbits.ALTI2C = 0;) 
RB6 --> dioda LED
RB14 --> nCS(select dla MCP2517FD)
RA2 --> INT1 MCP2517FD

Obraz Pin Module w MCC dla MPLABX-IDE :


Poniżej najważniejsze fragmenty programu dla węzła odbiorczego :

Fizyczna konfiguracja kontrolera MCP2517FD jaką zastosowałem w programie:


Pętla główna programu, plik main.c :
W pętli głównej reagujemy na flagę przerwania, która sie pojawi jak nastąpi jakieś zdarzenie w odbiorze w FIFO1, sprawdzamy potem jeszcze na wszelki wypadek jaki konkretnie event wywołał przerwanie jeśli jest to event CAN_RX_FIFO_NOT_EMPTY_EVENT to odbieramy wiadomość z FIFO1. Pole danych zostanie umieszczone w tablicy rxData natomiast inne dane ramki takie jak SID , DLC , Timestaping etc zostaną przypisane do obiektu rxObj. Jeśli chcemy odczytać z obiektu np SID to robimy to tak : rxObj.id.SID. Odczyt DLC : rxObj.ctrl.DLC. Jeśli podejrzymy typ CAN_RX_MSGOBJ i podtypy w nim zawarte to zorientujemy się jak wyłuskać inne dane z obiektu.


Poniżej obsługa przerwania odbiorczego. Przerwanie generuje MCP2517FD na swoim pinie INT1 jeśli w FIFO1 nastąpiło jakieś zdarzenie w odbiorze, przerwanie przyjmuje pin RA2 w mikrokontrolerze skonfigurowany jako CN reagujący na zbocze opadające.



I to by było na tyle jeśli chodzi o węzeł odbiorczy.

Cała wymiana danych w trybie CAN FD chodzi jak zegarek szwajcarski a może nawet lepiej. Jestem pod dużym wrażeniem zarówno technologii jak i samego kontrolera MCP2517FD. Zabaw z nim nie kończę ale artykuł muszę, bo niedługo zrobi się z niego materiał na książkę :) Nie ruszyłem Timestapingu bo muszę znaleźć dla niego jakieś zastosowanie  w warunkach amatorskich a jest to na pewno ciekawy wątek i funkcjonalność.

Podsumowując krótko, API dostarczane przez Microchipa dla MCP2517FD sprawuje się bardzo dobrze i jest wygodne w użyciu. Konwersja API do współpracy z PIC32MM nie była trudna. Sam kontroler MCP2517FD jest po prostu genialny a technologia CAN FD bardzo ciekawa.Mam nadzieję, że tym artykułem przybliżyłem choć trochę specyfikę kontrolera MCP2517FD. Co dalej można z tym zrobić ? Jeśli zbudujemy sobie w domu lub zagrodzie sieć CAN a mając opanowany kontroler CAN naprawdę jest to dalej już proste zagadnienie, możemy tę sieć podłączyć do internetu za pomocą platformy embedded. Jednym z ciekawy tutaj propozycji jest BeagleBone Black , które ma na pokładzie CAN.



Pozdrawiam
picmajster.blog@gmail.com


Linki:
MCP2517FD - strona producenta 
PIC32MM0256GPM048 - strona producenta 
Węzeł nadawczy - projekt dla MPLABX-IDE na GitHub 
Węzeł odbiorczy - projekt dla MPLABX_IDE na GitHub 
Projekt płytki modułu MCP2517FD - plik Gerbera

Brak komentarzy:

Prześlij komentarz