LinkedIn YouTube Facebook
Szukaj

Wstecz
Artykuły

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

www.leon-instruments.pl