Mikrokontrolery AVR XMEGA w praktyce, część 18. SPI w XMEGA
SPI w XMEGA
Mikrokontroler ATxmega128A3U posiada trzy interfejsy SPI, dostępne w portach C, D, E, a nazwy tych interfejsów to odpowiednio: SPIC, SPID, SPIE. Są one funkcjonalnie identyczne, a my w naszych przykładach wykorzystamy moduł SPIC. Wszystkie przykłady zostały przedstawione w jednym zbiorczym listingu 1.
Pierwsza rzecz, od której najlepiej zacząć, to konfiguracja pinów IO. W starych AVR-ach, włączenie interfejsu powodowało automatyczne skonfigurowanie pinów wejść i wyjść. W XMEGA musimy to zrobić samodzielnie. Konfiguracja pinów IO została opisana w odcinku 4. Spójrzmy zatem do ATxmega128A3U datasheet i w tabeli przedstawionej na rysunku 1, sprawdźmy, które piny jaką funkcję realizują. Zamieszczam tą tabelkę, by podkreślić dwie bardzo istotne sprawy:
- Pin SS (slave select) można wykorzystać jako CS do sterowania slavem, kiedy ustawimy go jako wyjście. Jednak jeśli będzie on ustawiony jako wejście, (tak jest domyślnie po włączeniu procesora!), to pojawienie się stanu niskiego na pinie SS spowoduje przełączenie modułu SPI z trybu master do trybu slave. Jeśli Twój procesor zawsze ma pracować w trybie master, ustaw pin SS jako wyjście, nawet jeśli jako CS wykorzystujesz inne piny.
- Przypis 4 pod tabelą mówi, że piny MOSI i SCK można zamienić miejscami. Po co? Zwróć uwagę na interfejs USART C1. Piny MOSI i SCK są funkcjonalnie identyczne jak TXD i XCK. Jeśli projektując płytkę, zamienimy te dwa piny, później pisząc program będziemy mogli wybrać, czy chcemy korzystać z SPI oraz USART. SPI jest prostsze, a USART jest trochę bardziej skomplikowany lecz daje większe możliwości. Zamiany dokonuje się, wpisując wartość PORT_SPI_bm do rejestru PORTx.REMAP. Pamiętaj, że w płytce eXtrino XL, którą wykorzystamy w tym kursie, piny MOSI i SCK są zamienione.
Rys. 1. Piny IO w XMEGA
Po skonfigurowaniu pinów IO, przechodzimy do konfiguracji właściwego interfejsu. Wystarczy wpisać odpowiednie wartości do zaledwie jednego rejestru o nazwie CTRL. Ustawiamy w nim kilka prostych parametrów:
- SPI_ENABLE_bm – ustawienie tego bitu powoduje uaktywnienie interfejsu,
- SPI_MASTER_bm – włączenie trybu master,
- SPI_MODE_x_gc – grupa konfiguracyjna, decydująca m.in. o próbkowaniu i polaryzacji sygnału zegarowego (patrz rysunek 4 z poprzedniej części)
- SPI_DORF – przesyłanie danych od najmłodszego bitu,
- SPI_PRESCALER oraz SPI_CLK2X – ustawianie częstotliwości zegara.
Jeśli chcemy wykorzystywać przerwania, musimy jeszcze ustawić priorytet przerwań w rejestrze INTCTRL oraz uruchomić kontroler przerwać PMIC (opisane w odcinku 6). SPI może generować tylko jeden rodzaj przerwań o nazwie SPIx_INT_vect.
Do wysyłania i odbierania danych służy rejestr DATA. Pamiętaj, że SPI jest interfejsem full-duplex i nawet jeśli chcesz tylko odebrać jakieś dane, musisz coś wysłać, za przykład 0. Transmisja rozpoczyna się po wpisaniu bajtu danych do rejestru DATA. Następnie należy poczekać, aż dane zostaną przesłane, sprawdzając w pętli rejestr STATUS. Pojawienie się jedynki na pozycji SPI_IF_bm oznacza, zakończenie transmisji (co wywoła przerwanie, jeśli jest odblokowane). Dane wysłane ze slave’a do mastera możesz odczytać również z rejestru DATA.
Przed rozpoczęciem transmisji musisz ustawić pin CS wybranego slave’a w stan niski, a następnie poczekać na ustabilizowanie się napięcie na tym pinie. Do tego celu można użyć instrukcji czekania _delay_us(1) albo kilkukrotnie wkleić asm volatile(„nop”);. Po zakończeniu transmisji, pin CS musisz ustawić w stan wysoki.
Przykłady inicjalizacji, funkcji przesyłającej dane oraz procedury przerwania znajdziesz na listingu 1.
Listing 1. Kod programu demonstrującego działanie SPI w XMEGA
#include#include struct PORTX_t { // struktura danych volatile uint8_t IN; // rejestr wejściowy volatile uint8_t OUT; // rejestr wyjściowy } PORTX; uint8_t SpiTransmit(uint8_t data) { // transmisja SPI SPIC.DATA = data; // wysyłanie danych while(SPIC.STATUS == 0); // czekanie na zakończenie transmisji return SPIC.DATA; // odczytanie danych } int main(void) { // sygnały CS dla peryferiów eXtrino XL PORTE.OUTSET = PIN3_bm | PIN6_bm; // SD, PORTX, DIGPOT PORTE.DIRSET = PIN3_bm | PIN6_bm; // SD, PORTX, DIGPOT // konfiguracja SPI PORTC.DIRSET = PIN4_bm | PIN5_bm | PIN7_bm; // wyjścia SPI PORTC.DIRCLR = PIN6_bm; // wejście SPI PORTC.OUTCLR = PIN7_bm | PIN6_bm | PIN5_bm | PIN4_bm; PORTC.REMAP = PORT_SPI_bm; // zamiana miejscami SCK i MOSI SPIC.CTRL = SPI_ENABLE_bm| // włączenie SPI SPI_MASTER_bm| // tryb master SPI_MODE_3_gc| // tryb 3 SPI_PRESCALER_DIV64_gc; // preskaler SPIC.INTCTRL = SPI_INTLVL_LO_gc; // niski priorytet przerwań // przerwania PMIC.CTRL = PMIC_LOLVLEN_bm; // włączenie przerwań o priorytecie LO sei(); // pierwsza transmisja SPIC.DATA = 0; // pętla główna while(1) { if(PORTX.OUT == PORTX.IN) { // jeśli wciśnięto przycisk przy świecącej diodzie PORTX.OUT = PORTX.OUT << 1; // przesuń diodę na następną pozycję if(PORTX.OUT == 0) PORTX.OUT = 1; // jeśli ostatnia, zacznij od nowa } } } ISR(SPIC_INT_vect) { PORTE.OUTSET = PIN6_bm; // chip deselect asm volatile("nop"); // czekanie na ustabilizowanie się pinu E6 asm volatile("nop"); asm volatile("nop"); asm volatile("nop"); asm volatile("nop"); PORTE.OUTCLR = PIN6_bm; // chip select PORTX.IN = SPIC.DATA; // odczytanie danych SPIC.DATA = PORTX.OUT; // rozpoczęcie nowej transmisji // i wyjście z przerwania }
Dystrybutorem zestawu X3-DIL64 oraz eXtrino XL jest KAMAMI.pl. |
Dominik Leon Bieczyński