LinkedIn YouTube Facebook
Szukaj

Newsletter

Proszę czekać.

Dziękujemy za zgłoszenie!

Wstecz
Artykuły

Mikrokontrolery AVR XMEGA w praktyce, część 4. Porty

Elektroniczne Hello World, czyli mruganie diodą

Port jest podstawowym peryferium, pozwalającym mikrokontrolerowi porozumieć się z innymi urządzeniami. W znanych i lubiany 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). Schemat układu pokazano na rysunku 1, a zdjęcie przedstawiono na rysunku 2.

 

Rys. 1. Schemat połączenia diod do płytki testowej. Przycisk połączony z pinem E5 już jest przylutowany na płytce X3-DIL64

Rys. 1. Schemat połączenia diod do płytki testowej. Przycisk połączony z pinem E5 już jest przylutowany na płytce X3-DIL64

 

Rys. 2. Zdjęcie ilustrujące przykładowy sposób podłączenia diod LED

Rys. 2. Zdjęcie ilustrujące przykładowy sposób podłączenia diod LED

 

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 na przeciwny
  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 na przeciwny
     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.

Dystrybutorem zestawu X3-DIL64 jest KAMAMI.pl.

Dominik Leon Bieczyński

http://leon-instruments.blogspot.com

Do pobrania