Wydział Informatyki, Elektroniki i Telekomunikacji Katedra Elektroniki Systemy akwizycji i transmisji sygnałów Laboratorium Implementacja komunikacji CAN. Kraków, 2012 r. 1
Spis treści 1. Wykorzystywane płytki... 3 1.1 Płytka mikrokontrolera... 3 1.2 Płytka z driverem CAN... 3 2. Program mikrokontrolera... 3 2.1 Opis przygotowanego projektu... 3 2.2 Dołączamy bibliotekę do projektu... 4 2.3 Dołączamy pliki użytkownika... 5 2.4 Uzupełniamy ciało funkcji CAN_Config()... 6 2.4.1 Odblokowanie zegara... 6 2.4.2 Konfiguracja przerwania... 7 2.4.3 Konfiguracja wyprowadzeń... 7 2.4.4 Konfiguracja interfejsu CAN... 8 2.4.5 Konfiguracja filtru akceptowanych wiadomości... 10 2.5 Uzupełniamy ciało funkcji CAN_RX_Interrupt()... 12 2.6 Uzupełniamy ciało funkcji CAN_TX_Message()... 12 2.7 Kompilujemy projekt i wgrywamy do mikrokontrolera... 12 3. Program komputera PC... 12 4. Pomiary / Zabawa... 13 Cel Ćwiczenia: Napisanie programu mikrokontrolera STM32F103RBT6 w języku C, który będzie wysyłał i odbierał dane z wykorzystaniem magistrali CAN. Wymagane umiejętności: - czytanie ze zrozumieniem; - obsługa komputera PC. 2
1. Wykorzystywane płytki 1.1 Płytka mikrokontrolera Opis płytki mikrokontrolera jest zawarty w instrukcji do pierwszego ćwiczenia. 1.2 Płytka z driverem CAN Schemat płytki z driverem CAN jest przedstawiony na rysunku 1. Rys. 1. Schemat płytki z driverem CAN. Płytka zawiera jeden układ scalony driver CAN o oznaczeniu SN65HVD230. Ten driver poprawnie pracuje przy napięciu zasilającym wynoszącym 3,3 V. Z lewej strony widoczne jest gniazdo połączeniowe z płytką mikrokontrolera. W środkowej części umieszczono kondensatory odsprzęgające linie zasilające oraz cztery rezystory umożliwiające dopięcie drivera do różnych wyprowadzeń mikrokontrolera. Standardowo interfejs CAN wykorzystuje piny PA11 i PA12 (na płytce mikrokontrolera jest tu dołączone gniazdo USB). Jednakże interfejs CAN może zostać zmapowany do wyprowadzeń PB8 i PB9 lub PD0 i PD1 (na płytce mikrokontrolera jest tu dołączony kwarc 8 MHz). Magistralę CAN dołącza się do gniazda JP1. Na końcach magistrali należy dołączyć rezystor terminujący 120 Ω poprzez założenie zwory na gniazdo J2. Płytka wykorzystywana na zajęciach ma wlutowane rezystory R2 i R4 interfejs CAN będzie wykorzystywał linie PB8 i PB9. 2. Program mikrokontrolera 2.1 Opis przygotowanego projektu Otwórz przygotowany projekt znajdujący się na dysku D:\Zaoczni\CAN\Template\Tests.rprj Przygotowany projekt realizuje większość wymaganych rzeczy i jest bardzo podobny do projektu wykonanego na poprzednich zajęciach. Po włączeniu zasilania mikrokontroler: - konfiguruje sygnały zegarowe; - odłącza JTAG i SWD od pinów mikrokontrolera; 3
- ustawia przerwania; - konfiguruje interfejs UART poprzez wywołanie funkcji RS232_HardwareInitFunction(). Przygotowany projekt ma też napisane funkcje do komunikacji z komputerem za pomocą interfejsu RS232. Z tego powodu próba kompilacji projektu zakończy się niepowodzeniem (RS232 wykorzystuje funkcje i zmienne które za chwilę dołożymy). O szczegóły wykorzystanego protokołu RS232 można zapytać prowadzącego. Naszym zadaniem będzie dołożenie plików bibliotecznych i oprogramowanie interfejsu CAN, tak by możliwe było wysyłanie i odbieranie ramek. 2.2 Dołączamy bibliotekę do projektu 2.2.1 Dodajemy plik CAN biblioteki producenta Aby możliwe było korzystanie z funkcji i zmiennych przygotowanych przez producenta należy do projektu dodać plik biblioteki. W tym celu w Ride7 szukamy zakładki Project, klikamy prawym przyciskiem myszy na folderze Library i wybieramy Add Item. Znajdujemy folder z projektem, wybieramy Lib src stm32f10x_can.c. 2.2.2 Modyfikujemy plik konfiguracyjny Wybieramy File Open With Source Editor i otwieramy plik /User/inc/stm32f10x_conf.h. Plik ten jest powiązany z pozostałymi plikami biblioteki producenta. Aby korzystać z funkcji i definicji poszczególnych plików biblioteki należy w tym pliku od komentować definicje interfejsu. Ponieważ chcemy korzystać z pliku bibliotecznego dotyczącego CAN, to w tym pliku usuwamy komentarz w linii 41 chcemy mieć w projekcie definicję _CAN. Plik można zapisać i zamknąć. 4
2.3 Dołączamy pliki użytkownika 2.3.1 Plik nagłówkowy My_CAN.h Na potrzeby ćwiczeń został przygotowany plik My_CAN.h, który należy teraz otworzyć. Plik znajduje się w \User\inc\My_CAN.h. W czwartym wierszu jest dołączony główny plik nagłówkowy biblioteki producenta. W wierszach 6-8 deklaracje funkcji użytkownika (jeszcze nie napisane). Wiersze 1,2 oraz 10 zabezpieczają przed wielokrotnym dołączaniem tych samych definicji/deklaracji. Plik można zamknąć nie będziemy tu wprowadzać modyfikacji. 2.3.2 Plik nagłówkowy main.h Otwieramy plik \User\inc\main.h i wpisujemy pod #include Comunication.h : # include My_CAN.h Wszystkie pliki nagłówkowe są dołączone w pliku main.h. W plikach źródłowych można dołączać plik main.h lub tylko niektóre z nich. Plik main.h można zamknąć, nie będziemy wprowadzać tu więcej modyfikacji. 2.3.3 Plik źródłowy My_CAN.c Na potrzeby zajęć został przygotowany plik \User\src\My_CAN.c. Proszę dodać go do projektu (podobnie jak w punkcie 2.2.1 z tą różnicą że dodajemy go do folderu User a nie Library ). Następnie proszę go otworzyć. 5
W tym pliku znajduje się dołączenie głównego pliku nagłówkowego main.h. Definicje zmiennych wykorzystywanych do konfiguracji interfejsu CAN, konfiguracji przerwań i wyprowadzeń. Oraz dwie struktury przechowujące wiadomość wysyłaną oraz odebraną przez interfejs CAN. W pliku są też trzy puste funkcje: - CAN_Config przeprowadzi jednorazową konfigurację interfejsu; - CAN_RX_Interrupt funkcja wykonywana za każdym razem gdy CAN zgłosi przerwanie; - CAN_TX_Message funkcja do wysyłania danych przez CAN. Otwieramy plik main.c i szukamy fragmentu zamieszczonego poniżej. Uzupełniamy część zaznaczoną czerwoną ramką. int main(void) { ///////////// Hardware Init //////////// RCC_Init(); //Clocks and PLL init GPIO_PinRemapConfig(GPIO_Remap_SWJ_Disable, ENABLE); // Disable JTAG and SW GPIO_Config(); //GPIO Configuration InterruptConfig(); //Interrupt Configuration //////////// RS 232 /////////////// RS232_HardwareInitFunction(); //////////// CAN ///////////////// CAN_Config(); //////////// START ///////////// while (1) { } // while } // main Tuż po włączeniu zasilania mikrokontroler przeprowadzi konfigurację CAN, a następnie zacznie wykonywać główną pętlę programu. Otwieramy plik stm32f10x_it.c i szukamy wiersza 378. Usuwamy komentarz. W ten sposób za każdym razem gdy CAN zgłosi przerwanie zostanie wywołana funkcja CAN_RX_Interrup zdefiniowana w pliku My_CAN.c. Project. W tym momencie próba zbudowania projektu powinna zakończyć się sukcesem. Project Build 2.4 Uzupełniamy ciało funkcji CAN_Config() Funkcja CAN_Config przeprowadzi konfigurację interfejsu CAN. 2.4.1 Odblokowanie zegara Większość interfejsów mikrokontrolera tuż po włączeniu zasilania jest fizycznie wyłączonych poprzez bramkowanie sygnału zegarowego. Ma to na celu ograniczenie pobieranej energii ze źródła 6
zasilającego. Aby móc korzystać z danego interfejsu w pierwszej kolejności należy dołączyć do niego sygnał zegarowy. Realizuje się to poprzez wywołanie funkcji RCC_APB2PeriphClockCmd lub RCC_APB1PeriphClockCmd z odpowiednim argumentem. Otwieramy plik Firmware library.pdf na stronie 282. Szukamy interfejsu CAN w tabeli 375. Jeżeli go tam nie ma to sprawdzamy tabelę 377. Następnie w Ride7 w pliku MyCAN.c wpisujemy wewnątrz funkcji CAN_Config: // CAN Periph clock enable RCC_APB2PeriphClockCmd( NazwaZTabeli375, ENABLE); Lub RCC_APB1PeriphClockCmd( NazwaZTabeli377, ENABLE); 2.4.2 Konfiguracja przerwania Po włączeniu zasilania przerwania są zablokowane. Konfiguracja wspólnych opcji układu przerwań NVIC jest realizowana w funkcji InterruptConfig() w pliku main.c. Jeżeli chcemy korzystać z jakiegoś kanału przerwania, to należy go skonfigurować. Proszę otworzyć Firmware library.pdf na stronie 225. Znajduje się tam opis funkcji NVIC_Init() wraz z opisem struktury NVIC_InitTypeDef. W pierwszym kroku wypełnimy pola struktury a następnie wywołamy funkcję NVIC_Init(). Do funkcji CAN_Config() proszę wpisać: NVIC_InitStructure.NVIC_IRQChannel = USB_LP_CAN_RX0_IRQChannel; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); Konfiguracja jest bardzo prosta. Wskazujemy który kanał przerwania chcemy ustawić (USB_LP_CAN_RX0_IRQChannel przerwanie zgłaszane gdy CAN odbierze wiadomość), następnie nadajemy priorytety (0 oznacza najwyższy priorytet) i informujemy czy chcemy przerwanie włączyć czy wyłączyć. Na koniec wywołujemy funkcję konfigurującą z strukturą jako argument. 2.4.3 Konfiguracja wyprowadzeń Po włączeniu zasilania wyprowadzenia GPIO mikrokontrolera są ustawione jako wejścia. Chroni to układy dołączone do mikrokontrolera (i sam mikrokontroler) przed uszkodzeniem. Proszę otworzyć Firmware library.pdf na stronie 171. Znajduje się tam opis funkcji GPIO_Init() wraz z opisem struktury GPIO_InitTypeDef. W pierwszym kroku wypełnimy pola struktury a następnie wywołamy funkcję GPIO_Init(). Konfigurację przeprowadzimy dla dwóch wyprowadzeń mikrokontrolera. 7
Do funkcji CAN_Config() proszę dopisać: // Configure CAN pin: RX GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8; GPIO_InitStructure.GPIO_Mode = TuCośWpisz; GPIO_Init(GPIOB, &GPIO_InitStructure); // Configure CAN pin: TX GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; GPIO_InitStructure.GPIO_Mode = TuCośWpisz; GPIO_Init(GPIOB, &GPIO_InitStructure); Uzupełnij pole TuCośWpisz tak by pin B8 był skonfigurowany jako Input Pull-Up (tabela 186). Pin B9 ma być skonfigurowany jako Alternate Function Push-Pull (tabela 186). Na koniec wywołamy funkcję remapującą interfejs CAN do wyprowadzeń B8 i B9 (domyślnie CAN jest dołączony do PA11 i PA12). W tym celu proszę otworzyć Firmware library.pdf na stronie 181 i zaznajomić się z funkcją GPIO_PinRemapConfig(). Do funkcji CAN_Config() proszę dopisać: GPIO_PinRemapConfig( TuCośWpisz, ENABLE); Uzupełnij pole TuCośWpisz tak by włączyć Remap1 interfejsu CAN (tabela 203). 2.4.4 Konfiguracja interfejsu CAN W tym podrozdziale przedstawiono konfigurację działania interfejsu CAN. Podobnie jak poprzednio realizuje się to poprzez wypełnienie odpowiedniej struktury a następnie wywołanie funkcji. Na początek wykonamy deinicjalizację interfejsu CAN, następnie wypełnimy strukturę domyślnymi wartościami. Do funkcji CAN_Config() dopisujemy: // CAN register init CAN_DeInit(); //Reset registers to their default reset values CAN_StructInit(&CAN_InitStructure); //Fill Structure with default value Proszę otworzyć Firmware library.pdf na stronie 98. Znajduje się tam opis funkcji CAN_Init() wraz z opisem struktury CAN_InitTypeDef. Do funkcji CAN_Config() dopisujemy: /* CAN cell init */ CAN_InitStructure.CAN_TTCM= TuCośWpisz; //Disable time triggered communication mode CAN_InitStructure.CAN_ABOM= TuCośWpisz; //Disable automatic bus-off management CAN_InitStructure.CAN_AWUM= TuCośWpisz; //Disable Automatic Wake-up mode CAN_InitStructure.CAN_NART= TuCośWpisz; //Disable No-automatic retransmission mode CAN_InitStructure.CAN_RFLM= TuCośWpisz; //Disable Receive fifo locked mode CAN_InitStructure.CAN_TXFP= TuCośWpisz; //Disable Transmit fifo priority CAN_InitStructure.CAN_Mode= TuCośWpisz; CAN_InitStructure.CAN_SJW= TuCośWpisz; // (1-4) Time quanta difference for resynchronization CAN_InitStructure.CAN_BS1= TuCośWpisz; // (1-16) Time quanta difference for Bit Segment 1 CAN_InitStructure.CAN_BS2= TuCośWpisz; // (1-8) Time quanta difference for Bit Segment 2 CAN_InitStructure.CAN_Prescaler= TuCośWpisz; // (0-1024) Transmission prescaler CAN_Init(&CAN_InitStructure); Uzupełnij powyższy kod tak by: - wyłączyć Time Triggered Communication Mode; 8
- wyłączyć Automatic Bus-Off Mode; - wyłączyć Automatic Wake-Up Mode; - wyłączyć No-Automatic Retransmission Mode; - wyłączyć Receive Fifo Locked Mode; - wyłączyć Transmit Fifo Priority. Pole Mode może przyjmować wartości: - CAN_Mode_Normal normalna praca interfejsu (tą wybieramy); - CAN_Mode_Silent interfejs odbiera wiadomości ale nie może nic nadawać, gdyż pin TX jest odłączony; - CAN_Mode_LoopBack możemy wysyłać na magistralę ale nie odbieramy z magistrali. Wszystko co wyślemy trafia do bufora odbiorczego; - CAN_Mode_Silent_LoopBack jesteśmy całkowicie odłączeni od magistrali CAN. Wszystko co wyślemy trafia do bufora odbiorczego (możemy wysyłać wiadomości tylko do siebie). Polu SJW nadajemy wartość 1 time quanta (zgodnie z tabelą 75). Jest to liczba kwantów o którą interfejs może wydłużyć lub skrócić czas bitu w celu przeprowadzenia resynchronizacji. Polu BS1 nadajemy wartość 8 time quanta (zgodnie z tabelą 76). Jest to liczba kwantów czasu trwania Bit Segment 1. Patrz rysunek poniżej. Polu BS2 nadajemy wartość 7 time quanta (zgodnie z tabelą 77). Jest to liczba kwantów czasu trwania Bit Segment 2. Patrz rysunek poniżej. Proszę przyjąć wartość Preskalera równą 1. Wtedy magistrala CAN będzie działać z przepływnością bitową: 1 BaudRate No min albittime t q 1 1 t ( BS1 1) t ( BS 2 1) t (3 BS1 BS 2) f PCLK 24M 24M bit 666666 (3 BS1 BS 2)(Prescaller 1) (3 8 7)(1 1) 36 s q q q 9
2.4.5 Konfiguracja filtru akceptowanych wiadomości Interfejs CAN w mikrokontrolerach STM32 posiada sprzętowy filtr odebranych wiadomości. Jeżeli identyfikator odebranej wiadomości nie spełnia żadnego z podanych filtrów, wtedy wiadomość jest ignorowana. W przeciwnym razie trafia do odbiorczego Fifo (i ewentualnie zgłaszane jest przerwanie odebranej wiadomości). W wykorzystywanym mikrokontrolerze możemy ustawić do 14 filtrów. W naszym programie przyjmiemy że wszystkie wiadomości będą odbierane. Dokładniejsze informacje można znaleźć w Firmware library.pdf na stronie 102 (i w Reference Manual). W funkcji CAN_Config() dopisujemy: /* CAN filter init */ CAN_FilterInitStructure.CAN_FilterNumber=0; // (0-14) Filter number CAN_FilterInitStructure.CAN_FilterMode=CAN_FilterMode_IdMask; CAN_FilterInitStructure.CAN_FilterScale=CAN_FilterScale_32bit; CAN_FilterInitStructure.CAN_FilterIdHigh=0x0000; // All frames will be received CAN_FilterInitStructure.CAN_FilterIdLow=0x0000; CAN_FilterInitStructure.CAN_FilterMaskIdHigh=0x0000; CAN_FilterInitStructure.CAN_FilterMaskIdLow=0x0000; CAN_FilterInitStructure.CAN_FilterFIFOAssignment=0; // filtered frames will be stored in fifo_0 CAN_FilterInitStructure.CAN_FilterActivation=ENABLE; // Enable filter CAN_FilterInit(&CAN_FilterInitStructure); // CAN FIFO0 message pending interrupt enable CAN_ITConfig(CAN_IT_FMP0, ENABLE); W ten sposób skonfigurowaliśmy filtr o numerze 0. FilterMode może mieć wartości: - CAN_FilterMode_IdMask podajemy identyfikator wiadomości oraz maskę mówiącą które bity identyfikatora mają się zgadzać w odebranej wiadomości; - CAN_FilterMode_IdList podajemy listę identyfikatorów; odebrane wiadomości o innym identyfikatorze będą sprzętowo odrzucane. FilterScale może mieć wartości: - CAN_FilterScale_Two16bit filtrujemy 11 bitów standardowego identyfikatora i 3 najstarsze bity rozszerzonego identyfikatora wiadomości (oraz pole RTR i IDE wiadomości); - CAN_FilterScale_One32bit filtrujemy 29 bitowy identyfikator (oraz pole RTR i IDE wiadomości). Chcemy aby wiadomości spełniające filtr były umieszczane w FIFO0. Filtr ma być aktywny. Na koniec uruchamiamy filtr. W ostatnim wierszu odblokowujemy przerwanie. Wiadomości CAN mają swój identyfikator (im niższy identyfikator tym wyższy priorytet), który w standardowych wiadomościach ma 11 bitów a w standardzie 2.0B 29 bitów. Pole IDE definiuje czy ramka ma standardowy identyfikator czy też rozszerzony. Pole RTR definiuje czy przesyłamy wiadomość z danymi czy prosimy inny węzeł o przesłanie danych. Widomość może zawierać maksymalnie 8 bajtów 10
(ilość przesyłanych bajtów definiuje pole DLC). Format ramki i CRC jest generowany automatycznie przez interfejs CAN znajdujący się w STM32. 11
2.5 Uzupełniamy ciało funkcji CAN_RX_Interrupt() Za każdym razem gdy CAN odbierze wiadomość spełniającą któryś z filtrów interfejs może zgłosić przerwanie (my skonfigurowaliśmy aby tak się działo). Układ przerwań NVIC zatrzyma normalną pracę mikrokontrolera i zacznie wykonywać instrukcję z tablicy przerwań. W pliku stm32f10x_it.c ustawiliśmy aby przy każdym przerwaniu RX0 wywoływać funkcję CAN_RX_Interrupt() zdefiniowaną w pliku My_CAN.c. Nadeszła pora by ją uzupełnić. W naszym, bardzo prostym projekcie, przy każdym przerwaniu interfejsu CAN skopiujemy odebraną wiadomość do zmiennej globalnej RxMessage. Zatem uzupełniamy ciało funkcji CAN_RX_Interrupt() : CAN_Receive(CAN_FIFO0, &RxMessage); // Software filtering may be implemented here... 2.6 Uzupełniamy ciało funkcji CAN_TX_Message() Funkcja CAN_TX_Message() jest wykorzystywana w pliku communication.c. Jak tylko odbierzemy dane z komputera wywołujemy tę funkcję by wysłać ramkę CAN. W funkcji następuje przepisanie informacji do struktury TXMessage a następnie wywołanie funkcji bibliotecznej CAN_Transmit(). Poniżej przykładowa realizacja. u8 i; // transmit 1 message TxMessage.StdId = StdID; //11 bits TxMessage.ExtId = ExtID; //18 bits TxMessage.IDE = CAN_ID_EXT; //Extended identifier TxMessage.RTR = CAN_RTR_DATA; //Data message TxMessage.DLC = Length; //from 0 to 7 for(i = 0; i < Length; i++) TxMessage.Data[i] = *pdata++; CAN_Transmit(&TxMessage); 2.7 Kompilujemy projekt i wgrywamy do mikrokontrolera 3. Program komputera PC Na potrzeby zajęć laboratoryjnych został przygotowany prosty program w języku Visual C#. Program ten wysyła i odbiera dane z mikrokontrolera. Dzięki temu możliwe jest przygotowanie wiadomości na komputerze i wysłanie jej przez interfejs CAN. Jak również możliwe jest odczytanie ostatniej odebranej prze interfejs CAN wiadomości. Główne okno programu przedstawiono poniżej. 12
Obsługa programu, podobnie jak w programach Microsoftu, jest intuicyjna. Po otwarciu portu COM w pięć pól tekstowych wpisuje się wartość rozszerzonego identyfikatora oraz jednobajtową daną. Po kliknięciu przycisku Wyślij dane są przesyłane do mikrokontrolera z wykorzystaniem RS232, a mikrokontroler formuje wiadomość CAN i wysyła ją na magistralę CAN. Po kliknięciu przycisku Odbierz komputer odbiera od mikrokontrolera ostatnią wiadomość, którą odebrał przez CAN. 4. Pomiary / Zabawa Sprawdź czy Twój współtowarzysz niedoli siedzący obok zaprogramował mikrokontroler. Jeżeli tak, to sprawdźcie działanie interfejsu CAN. W chwili obecnej każda odebrana wiadomość zgłasza przerwanie. W funkcji obsługi przerwania następuje odczytanie wiadomości z bufora odbiorczego i wpisanie jej do zmiennej globalnej (można ją odczytać poprzez RS232). Zmodyfikuj program tak by przerwanie nie było zgłaszane, a odczyt z bufora odbiorczego CAN następował przy próbie sczytania wiadomości przez komputer. Sprawdź jaką długość ma bufor odbiorczy (zacznij odczyt wiadomości na komputerze gdy mikrokontroler odebrał dwie wiadomości, trzy wiadomości, itd.). Zmodyfikuj konfiguracje filtru wiadomości a następnie sprawdź jakie identyfikatory są akceptowane, a jakie nie. 13