środa, 16 marca 2011

Kurs XMega (04): Porty i piny

Autor: Dominik Leon Bieczyński
Redakcja: Dondu

Artykuł jest fragmentem cyklu: Kurs mikrokontrolerów XMega by Leon-Instruments


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 operatorami |= lub &=. 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 projekty i można je wygodnie przetestować korzystając z płytki rozwojowej X3-DIL64 produkcji Leon Instruments.

Dominik Leon Bieczyński
leon-instruments.pl

2 komentarze:

  1. No to zacząłem:
    pierwsze zdanie : "Port jest podstawowym peryferium, pozwalającym mikrokontrolerowi porozumieć się z innymi urządzeniami." ok to do przełknięcia, trochę teorii na początek może być.
    Następne zdanie: "W znanych i lubianych procesorach ATtiny i ATmega, każdy port miał trzy rejestry PIN, PORT oraz DDR." Po dwóch zdaniach mamy już kilka nowych pojęć takich jak port, rejestry. Dalej pewnie będzie wyjaśnienie.
    Trzecie zdanie: "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!" Nie ma wyjaśnienia jest tylko trochę reklamy. Następnie jest trochę o nazwach - taki przerywnik, ok. I później garść następnych informacji : "DIR – rejestr ten decyduje czy dana nóżka ma być wejściem czy wyjściem...." no i dopiero tu pojawia się powiązanie abstrakcji komputerowo-elektronicznych z fizyczną kością. Niestety nadal nie wiem jak wyprowadzenie procesora jest realizowanie z poziomu programu.

    Jeśli ten kurs jest dla początkujących-poczatkujących to jest do dupy.
    I jeszcze na koniec zauważyłem takie coś błąd-nie-błąd: " aby ustawić lub wyzerować stan pojedynczego pinu w porcie, " Czy gdzieś wczęśniej było wyjaśnione co to jest "pin"? Ja to wiem ale początkujący ^3 może nie wiedzieć. Z tego zdania rozumiem/wnioskuję że porty składają się z pinów. Musze to sprawdzić.

    OdpowiedzUsuń
    Odpowiedzi
    1. Cóż, każdy kurs zakłada pewną elementarną wiedzę czytelnika... Gdybyś zamiast skupiać się na szukaniu dziur w całym, skupił się na przeczytaniu więcej niż 3 zdań, zapewne wyniósłbyś z tego kursu więcej.

      Usuń