Kurs XMEGA: porty (04)

|

Kurs XMEGA
Port jest podstawowym peryferium, pozwalającym mikrokontrolerowi porozumieć się z innymi urządzeniami. W znanych i lubianych procesorach ATtiny i ATmega, każdy port miał trzy rejestry PIN, PORT oraz DDR. W mikrokontrolerach XMEGA każdy port ma aż 21 rejestrów, ale bez obawy! Obsługa portów w XMEGA jest nawet łatwiejsza niż w ATmega!

Wszystkie porty posiadają swoją unikalną nazwę: PORTA, PORTB, PORTC… i tak dalej aż do końca alfabetu! W obrębie każdego portu znajduje się szereg rejestrów, a najważniejsze z nich zostały opisane poniżej:
  • DIR – rejestr ten decyduje czy dana nóżka ma być wejściem czy wyjściem. Wpisanie jedynki powoduje skonfigurowanie pinu jako wyjścia, a zero oznacza wejście. Dla przykładu, poniższa instrukcja ustawi pin 3 oraz 6 jako wyjście, a pozostałe piny będą wejściami:
    
    PORTA.DIR = PIN3_bm | PIN6_bm;
     
  • OUT – jest to rejestr wyjściowy. Wpisanie jedynki powoduje pojawienie się stanu wysokiego na odpowiadającej nóżce portu, a wpisanie zera oznacza stan niski.
  • IN – rejestr wejściowy, służący do odczytywania obecnego stanu pinów. Poniżej jest przykład instrukcji warunkowej, sprawdzającej czy na pinie E5 jest stan wysoki:
    
    if(PORTE.IN & PIN5_bm)
     
W mikrokontrolerach ATmega oraz ATtiny, aby ustawić lub wyzerować stan pojedynczego pinu w porcie, należało posłużyć się maskami bitowymi oraz operatorem |=. Dla przykładu, aby ustawić stan wysoki na pinie A1 oraz wyzerować pin A2, należało wpisać poniższe polecenia:
    
    PORTA |= (1<<PA1);      // ustawienie pinu A1=1
    PORTA &= ~(1<<PA2);     // ustawienie pinu A2=0
    
Nie jest to ani wygodne, ani szybkie w działaniu. Na szczęście projektanci procesorów XMEGA wymyślili dużo lepszy i prostszy dostęp do pinów. Powyższe instrukcje można zastąpić, wykorzystując rejestry OUTSET oraz OUTCLR:
    
    PORTA.OUTSET = PIN1_bm;  // ustawienie pinu A1=1
    PORTA.OUTCLR = PIN2_bm;  // ustawienie pinu A2=0
    
Istnieje też rejestr OUTTGL, służący do zamiany stanu bitów portu, wskazanych w tym rejestrze. Mamy do dyspozycji także rejestry DIRSET, DIRCLR i DIRTGL, które ustawiają, zerują lub zmieniają stan bitów odpowiedzialnych to czy wskazana nóżka ma być wejściem czy wyjściem.

Bardzo ważne są rejestry PINxCTRL, pozwalające skonfigurować bardziej zaawansowane opcje poszczególnych pinów. Co ważne, każdy pin ma osobny rejestr kontrolny. Wpisując do niego odpowiednie wartości, możemy włączać rezystory pull-up, pull-down, keeper. Przy pomocy tych rejestrów konfiguruje się także przerwania, szybkość narastania zbocza (slew rate) oraz kilka innych rzeczy.


Do płytki X3-DIL z Leon Instruments podłączymy kilka diod LED, które będą mrugać z częstotliwością zależną od tego, czy jest wciśnięty przycisk E5 zamontowany na płytce (ten sam, który wykorzystuje się do programowania przez FLIP).



W pierwszej kolejności musimy skonfigurować kierunek przepływu sygnałów przez piny w rejestrach DIR należących do odpowiednich portów. Najpierw skonfigurujmy wyjścia A0, B0, C0, D0 oraz E0, do których dołączymy diody. Można to zrobić na różne sposoby – polecam sposób pierwszy z wymienionych. Wklepywanie wartości w kodzie szesnastkowym jest wysoce niewskazane, w szczególności przy konfigurowaniu bardziej skomplikowanych peryferiów.
 
    PORTA.DIR    =    PIN0_bm;        // bit mask
    PORTB.DIR    =    (1<<PORT0);     // po nazwie
    PORTC.DIR    =    0b00000001;     // wartość binarna
    PORTD.DIR    =    0x01;           // wartość szesnatkowa
    PORTE.DIR    =    1;              // wartość dziesiętna
 
Dalej skonfigurujemy przycisk. Jest on przylutowany do nóżki E5 i zwiera ją do masy, kiedy jest wciśnięty. Kiedy jest zwolniony, pin E5 powinien mieć stan wysoki logiczny wymuszony rezystorem pull-up. W pierwszej linijce kodu zerujemy bit 5 w rejestrze DIR, poprzez wpisanie wartości PIN5_bm do rejestru DIRCLR. Następnie musimy włączyć rezystor podciągający za pomocą rejestru PIN5CTRL. Należy na to zwrócić szczególną uwagę, gdyż sposób włączania pull-upów w XMEGA różni się od ATmega i ATtiny.
    
    PORTE.DIRCLR    =    PIN5_bm;
    PORTE.PIN5CTRL  =    PORT_OPC_PULLUP_gc;
 
Następnie przechodzimy do pętli głównej while(1), która wykonuje się w nieskończoność. Mruganie diodami również zrealizujemy na kilka sposobów. Przeanalizujmy następujący kod:
    
    _delay_ms(500);                    // czekanie 500ms (1)
    PORTA_OUT      |= (1<<PIN0_bp);    // ustawienie bitu po staremu (2a)
    PORTB.OUTSET    =  PIN0_bm;        // ustawienie bitu po nowemu (3a)
        
    _delay_ms(500);                    // czekanie 500ms (1)
    PORTA_OUT      &= ~(1<<PIN0_bp);   // zerowanie bitu po staremu (2b)
    PORTB.OUTCLR    =  PIN0_bm;        // zerowanie bitu po nowemu (3b)
 
Polecenie _delay_ms(500) powoduje czekanie przez pół sekundy. Następnie ustawiamy pin A0 oraz B0 w stan wysoki. Widać wyraźnie, że nowy sposób sterowania pinami, opisany w linijce 3a jest zdecydowanie bardziej zwięzły i czytelny. Co ważniejsze, jest również szybciej wykonywany i zajmuje mniej miejsca w pamięci, jako że stosujemy operator = zamiast |=. W dalszej części kodu zerujemy piny A0 oraz B0 i sposobem typowym dla ATmega oraz z XMEGA.

Możemy zrealizować mruganie diodą jeszcze inaczej. Można wywołać np. taką funkcję:
    
    toggle(&PORTC);
        
…a jej definicja wygląda następująco:
    
    void toggle(PORT_t *io) {     // zamiana stanu pinu 0 wskazanego portu 
        io->OUTTGL = PIN0_bm;
    }
 
Funkcja ta za argument przyjmuje nazwę portu i zamienia stan ostatniego bitu na przeciwny, przy pomocy wpisania wartości PIN0_bm do rejestru OUTTGL wskazanego portu. Funkcję tę można użyć w następnych przykładach.

Niech dwie kolejne diody mrugają z różną częstotliwością, w zależności czy przycisk jest wciśnięty czy nie.
    
    if(!(PORTE.IN & PIN5_bm)) {    // jeżeli przycisk wciśnięty
        toggle(&PORTD);
    } else {                       // jeżeli przycisk zwolniony
        toggle(&PORTE);
    }
 
Instrukcja logiczna PORTE.IN & PIN5_bm sprawdza, czy obecna jest jedynka logiczna na piątej pozycji w rejestrze IN. Jeśli tak, to instrukcja zwraca wartość prawdziwą. Jednak pamiętajmy, że pin E5 ma włączony rezystor pull-up, więc stanem domyślnym jest stan logicznej jedynki, a wciśnięcie przycisku powoduje zwarcie pinu do masy i tym samym pojawienie się zera. Dlatego wyrażenie to zostało zanegowane przy pomocy operatora negacji !. Następnie wywołujemy znaną już funkcję toggle, która za argument przyjmuje PORTE, kiedy przycisk jest wciśnięty lub PORTD, kiedy przycisk jest zwolniony.

Oto cały kod programu:
    
    #define     F_CPU    2000000UL
    #include    <avr/io.h>
    #include    <util/delay.h>

    void toggle(PORT_t *io) {            // zamiana stanu pinu 0 wskazanego portu
        io->OUTTGL = PIN0_bm;
    }

    int main(void) {
       
       // różne sposoby na ustawienie pinu 0 każdego portu jako wyjście
       PORTA.DIR    =    PIN0_bm;        // bit mask
       PORTB.DIR    =    (1<<PORT0);     // po nazwie
       PORTC.DIR    =    0b00000001;     // wartość binarna
       PORTD.DIR    =    0x01;           // wartość szesnatkowa
       PORTE.DIR    =    1;              // wartość dziesiętna
       
       // pin E5 jako wejście z podciągnięciem do zasilania
       PORTE.DIRCLR     =    PIN5_bm;
       PORTE.PIN5CTRL   =    PORT_OPC_PULLUP_gc;
       
       while(1) {
        _delay_ms(500);                  // czekanie 500ms
        PORTA_OUT      |=  (1<<PIN0_bp); // ustawienie bitu po staremu
        PORTB.OUTSET    =   PIN0_bm;     // ustawienie bitu po nowemu
        
        _delay_ms(500);                  // czekanie 500ms
        PORTA_OUT      &= ~(1<<PIN0_bp); // zerowanie bitu po staremu
        PORTB.OUTCLR    =   PIN0_bm;     // zerowanie bitu po nowemu
        
        toggle(&PORTC);
        
        if(!(PORTE.IN & PIN5_bm)) {      // jeżeli przycisk wciśnięty
            toggle(&PORTD);
        } else {                         // jeżeli przycisk zwolniony
            toggle(&PORTE);
        }
       }
    }
 
Jednak to nie wszystkie możliwości portów, a jedynie wierzchołek góry lodowej. Oprócz tego, porty w XMEGA mają jeszcze inne możliwości, takie jak:
  • kontrola szybkości narastania zbocza (slew rate)
  • remapowanie pinów
  • konfiguracje pull-up, pull-down, keeper, wired or, wired and
  • zgłaszanie przerwań
  • generowanie zdarzeń
  • porty wirtualne
Funkcje te są opisane w książce Tomasza Francuza AVR. Praktyczne przykłady i można je wygodnie przetestować korzystając z płytki rozwojowej X3-DIL64 produkcji Leon Instruments.
Kurs XMEGA:Moduły prototypowe:

14 komentarze :

Slawek h pisze...

Ani w kursie w EP ani rozszerzonym tu nie znalazłem informacji na temat samego rdzenia i instrukcji. Czy XMEGA to to samo co zwykły AVR tylko rozbudowany wewnętrznie? Czy oba mają ten sam zestaw instrukcji, czy są zupełnie różne?

Dominik Leon Bieczyński pisze...

Różnice nie są duże, aczkolwiek przy programowaniu w assemblerze mogą być istotne. Przy programowaniu w C nie trzeba się zagłębiać w takie informacje. Rdzeń został trochę rozbudowany, żeby między innymi działać z wielopoziomowym kontrolerem przerwań (PMIC). Zmiany w rdzeniu nie są duże, natomiast peryferia wszystkie bez wyjątku zostały zrobione od nowa.

Slawek h pisze...

A jest gdzieś wykaz instrukcji assemblerowych, które obsługuje ten rdzeń?

Dominik Leon Bieczyński pisze...

Spis instrukcji assemblera znajdziesz tu http://www.atmel.com/images/doc0856.pdf a dokładna lista obsługiwanych instrukcji jest w dokumentacji konkretnego procesora.

B&W pisze...

Właśnie chcę kupić pokazany tu moduł, ale mnie interesuje jak to wszystko zrobić w assemblerze. A skoro ma to być kurs podstaw Xmega to proponowałbym uzupełnić go o analogiczne procedurki w tym właśnie języku.

Dominik Leon Bieczyński pisze...

Mikrokontrolery AVR zostały zaprojektowane z myślą o wykorzystaniu języka C i kursu Assemblera tutaj nie będzie. Polecam C, bo jest bardzo łatwy, a kompilator bardzo dobrze optymalizuje kod.

Tomek pisze...

Gdy podłączam X3-DIL64 do AVRDragon dostaję komunikat Got error setting up PDI mode: Device is not supported in this emulator mode. Debugger command setParameter failed., ModuleName: TCF (TCF command: Device:startSession failed.) Co to może być ?

Dominik Leon Bieczyński pisze...

Dragon nie obsługuje PDI. Podłącz przez JTAG i wszystko będzie działać.

Jacek pisze...

"wywołujemy znaną już funkcję toggle, która za argument przyjmuje PORTE, kiedy przycisk"

Nie chciałbym się mądrować ale do funkcji toggle argumentem przekazywanym jest adres: &PORTE.
Natomiast void toggle(PORT_t *io) , operuje dalej gdyż *io jest wskaźnikiem przechowującym adres &PORTE.
Jeśli to kurs dla początkujących, to warto wspomnieć o wskaźnikach i adresach, bo czasami niektórzy myślą, że pracują na zmiennych ze znaczkami * oraz &.
Z kontekstu tekstu autora również można domniemać, że pracujemy na zmiennych.

Anonimowy pisze...

trochę lipa , stm32 ma część wejść ,,5V tolerant'.

Dominik Leon Bieczyński pisze...

5V to już przeszłość i nie ma za czym płakać ;) wszystkie współczesne scalaki działają na 3,3V

Jacek M pisze...

Ok, jak się np definiuje lub robi alias swojej nazwy pod pin portu?
Np nazwamoja alias PORTC.x
i jak nim machać jak było w bas SET/RESET nazwamoja?
Lub nazwamoja = 1/0?

Dominik Leon Bieczyński pisze...

Można wykorzystać #define

Anonimowy pisze...

Cześć, Leon popełniłeś błąd w nazwie tytułu książki Tomasza, napisałeś "przykłady", a powinno być projekty. Pozdrawiam Annon

Prześlij komentarz

Skomentuj!

Sklep Leon Instruments