Mikrokontrolery AVR XMEGA w praktyce, część 19. Dodatkowy PORTX
Dodatkowy PORTX
W tym odcinku kursu zobaczymy, jak przy pomocy interfejsu SPI oraz rejestrów przesuwnych typu 74595 oraz 74165 stworzyć dodatkowy port IO oraz jak napisać program, aby obsługa tego dodatkowego portu była podobna do zwykłych portów mikrokontrolera. W ten sposób można samodzielnie zbudować odpowiednik ekspandera portu typu PCF8575 za niższą cenę.
Schemat układu przedstawiono rysunku 1, a jego uproszczony łatwiejszy do zrozumienia, uproszczony schemat, przedstawia rysunek 2. Rejestr 74595 ma wejście szeregowe oraz 8 wyjść równoległych, które możemy wykorzystać do dowolnego celu, np. do sterowania diodami. 74165 jest przeciwieństwem tego pierwszego i wyposażono go w 8 wejść. Możemy do nich podłączyć klawiaturę, czujniki lub inne układy cyfrowe.
Zastanówmy się, co się dzieje w tym układzie podczas transmisji jednego bajtu. Sygnał CS zmienia swój stan z 1 na 0, po czym moduł SPI w XMEGA nadaje osiem bitów. Wraz z każdym taktem sygnały zegarowego SCK, rejestry „przesuwają” swoje bity. W ten sposób, bajt danych z XMEGA trafia do 74595. 8 bitów dotychczas przechowywanych z 74595 przesyłane jest do 74165 i de facto są to dane, które nas nie interesują. Natomiast 8 bitów z 74165, odpowiadające sygnałom wejściowym tego rejestru, przesyła się do modułu SPI w XMEGA. Na zakończenie, zmiana CS z 0 na 1 powoduje odświeżenie wartości tych rejestrów. W ten elegancki sposób, transmitując 8 bitów, ustaliliśmy stan wyjść oraz odczytaliśmy stan wejść.
Rys. 1. Schemat dodatkowego portu sterowanego przez SPI
Rys. 2. Schemat uproszczony
Doskonałym pomysłem jest tutaj zastosowanie przerwań. Po każdej transmisji, układ SPI może generować przerwanie i wysyłać kolejny bajt danych, aby wejścia i wyjścia samoczynnie się odświerzały. Jedyne co musimy zrobić, to skonfigurować SPI i wywołać pierwszą transmisję, wpisując cokolwiek do rejestru SPIC.DATA.
Możemy posunąć się o krok dalej i stworzyć „pseudoport”, by kod programu jak najbardziej przypominał obsługę zwykłego portu. W tym celu zdefiniujemy sobie strukturę PORTX, w której znajdować się będą rejestry IN i OUT, analogicznie do zwykłych portów XMEGA. Ważne by nie zapomnieć i modyfikatorze volatile, gdyż zmienne te będą aktualizowane w przerwaniach, a bez tego kompilator mógłby nieprawidłowo je zoptymalizować.
Na płytce eXtrino XL znajduje się 8 przycisków, a przy każdym z nich jest dioda LED. Napiszmy program, w którym świeci się jedna z tych diod tak długo, aż użytkownik naciśnie przycisk przy tej diodzie. Wtedy zapali się sąsiednia dioda i program będzie czekał na naciśnięcie sąsiedniego przycisku, itd.
Kod programu przedstawia listing 1. Zwróć uwagę, że w pętli głównej nigdzie nie ma żadnych funkcjo obsługujących SPI. PORTX działa zupełnie jak normalny port!
Wyjaśnić należy procedurę przerwania, gdyż zaczyna się ona od deaktywowania układów SPI (CS=1), następnie czekamy kilka cykli i znów aktywujemy SPI (CS=0). Jest to podyktowane faktem, że ostatnim poleceniem przerwania powinno być wpisanie danych do rejestru SPIC.DATA. Wtedy moduł SPI transmituje dane, a w tym czasie procesor może wykonywać inne zadania. Kiedy moduł SPI zakończy transmisję, wówczas jest zgłaszane przerwanie. Wtedy, jako pierwsze następuje ustawienie CS w stan wysoki, co oznacza zakończenie transmisji rozpoczętej w poprzednim wywołaniu przerwania. Może to początkowo wydawać się trochę zagmatwane, jednak proszę przeanalizować kod wraz z komentarzami, a wszystko stanie się bardzo proste.
Działanie układu przedstawiono na fotografii 3. Po wciśnięciu przycisku, dioda LED „przeskakuje” na sąsiednią pozycję po lewej stronie.
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 }
Fot. 3. Przykład działania układu demonstrującego PORTX SPI
Dystrybutorem zestawu X3-DIL64 jest KAMAMI.pl. |
Dominik Leon Bieczyński