Brak produktów
PORTX to wirtualny układ peryferyjny na płytkach prototypowych eXtrino XL. Jest on jak najbardziej rzeczywisty i funkcjonalny, a słowo wirtualny oznacza to, że dzięki odpowiednim sztuczkom programistycznym, działa on zupełnie tak, jak zwyczajne standardowe porty w mikrokontrolerach XMEGA, dzięki czemu nawet początkujący wirtuoz programowania poradzi sobie z PORTX w jeden wieczór.
Zanim zaczniemy, ściągnij bibliotekę extrino_portx - będzie wykorzystywana w tym poradniku, jak i w wielu innych odcinkach kursu XMEGA.
Pliki do pobrania:
W najprostszym zastosowaniu, biblioteka PORTX wykorzystuje interfejs SPI do komunikacji z rejestrami przesuwnymi oraz przerwania do automatycznego odświeżania. Jeśli są Ci obce te mechanizmy - nic nie szkodzi, czytaj dalej.
Utwórz nowy projekt w Atmel Studio (jeśli masz z tym problem, przeczytaj poradnik Pierwszy program w Atmel Studio). Kliknij prawym przyciskiem myszy na drzewko projektu po prawej stronie. Pojawi się menu, z któego wybierz Add, a następnie Existing Item. Wskaż plikuextrino_portx.c oraz extrino_portx.h. Powinny pojawić się w drzewku projektu, obok pliku głównego main.c (chyba, ze nazwałeś go inaczej).
Na początku programu, musimy dodać plik extrino_portx.h przy pomocy #include. Przy okazji zdefiniujemy częstotliwość taktowania procesora definicją F_CPU na 2000000Hz oraz dodamy ulubioną bibliotekę z funkcją _delay_ms(), którą posłużymy się do generowania opóźnień. W funkcji main() musimy wywołać funkcję PortxInit(), która skonfiguruje wszystkie wymagane peryferia i już nic więcej nie musimy dodawać do programu, by korzystać z PORTX.
PORTX, tak jak wszystkie inne porty w XMEGA, mają rejestry PORTX.OUT oraz PORTX.IN. Zajmiemy się najpierw tym pierwszym - jest to 8-bitowy rejestr wyjściowy, który na płytce eXtrino XL odpowiada za zapalanie ośmiu diod LED. Wpisanie wartości 1 do dowolnego bitu tego rejestru, powoduje zapalenie się odpowiadającej diody. Wpisanie 0 powoduje wyłączenie diody. Tyle wiadomości nam wystarczy, by napisać pierwszy program z wykorzystaniem PORTX - niech wszystkie diody naprzemiennie zapalają się i gaszą z odstępem 500ms.
#define F_CPU 2000000UL #include <avr/io.h> #include <util/delay.h> #include "extrino_portx.h" int main(void) { PortxInit(); // inicjalizacja PORTX while(1) { PORTX.OUT = 0b11111111; _delay_ms(500); PORTX.OUT = 0b00000000; _delay_ms(500); } }
Prawda, że proste?
Zobaczmy, jak działa rejestr PORTX.IN. Jest to 8-bitowy rejestr wejściowy, który na płytce prototypowej eXtrino XL połączony jest ośmioma przyciskami. Wciśnięcie przycisku powoduje pojawienie się 1 na odpowiadającym bicie, a wartość 0 oznacza przycisk zwolniony. Wypróbuj poniższy program - będą zapalały się diody przy wciśniętych przyciskach.
#define F_CPU 2000000UL #include <avr/io.h> #include <util/delay.h> #include "extrino_portx.h" int main(void) { PortxInit(); // inicjalizacja PORTX while(1) { PORTX.OUT = PORTX.IN; } }
W kolejnym przykładzie poznamy rejestry PORTX.OUTSET oraz PORTX.OUTCLR. Rejestry te są dostępne również w zwykłych portach, a dzięki nim nie musimy używać operatorów logicznych |= oraz &= z maskami bitowymi, jakże często używanymi do konfiguracji rejestrów w starych mikrokontrolerach ATmega i ATtiny.
Jak to działa? Wpisanie samych zer do rejestru OUTSET nie powoduje nic. Wpisanie jedynek, powoduje przepisanie ich do OUT w taki sposób, że jedynki w OUT cały czas pozostają jedynkami, a zera zmieniają się w jedynki, jeśli odpowiadające im bity w OUTSET miały wartość 1. Można powiedzieć, że na dotychczasową wartość rejestru OUT jest nakładana wartość OUTSET, w którym zera są przezroczyste. Jest to operacja sumy logicznej.
Rejestr OUTCLR działa w sposób odwrotny. Wpisanie do niego jedynek powoduje wyzerowanie odpowiadających bitów w rejestrze OUT. Wpisanie zer do OUTCLR nic nie robi. Jest to operacja iloczynu logicznego (choć nie do końca jest to prawda, gdyż wtedy zero powinno zerować bity, a jedynka nic nie robić).
W kolejnym programie będziemy decydować przyciskiem FLIP czy wartość rejestru PORTX.IN ma trafić do PORTX.OUTSET czy PORTX.OUTCLR. Przycisk FLIP podłączony jest do pinu E5 procesora. Dla prostoty możemy pominąć jego inicjalizację, gdyż wszystkie piny XMEGA domyślnie są skonfigurowane jako wejście, a na płytce eXtrino XL przycisk FLIP ma fizyczny rezystor podciągający pull-up. Kiedy przycisk FLIP jest zwolniony, wciśnięcie przycisku na klawiaturze będzie powodowało zapalenie się odpowiadającej mu diody. Po zwolnieniu przycisku, dioda będzie cały czas się świecić. Aby diodę zgasić, trzeba jednocześnie trzymać wciśnięty FLIP i wcisnąć przycisk przy diodzie LED.
#define F_CPU 2000000UL #include <avr/io.h> #include <util/delay.h> #include "extrino_portx.h" int main(void) { PortxInit(); // inicjalizacja PORTX while(1) { if(PORTE.IN & PIN5_bm) // jeśli FLIP zwolniony PORTX.OUTSET = PORTX.IN; else // jeśli FLIP wciśnięty PORTX.OUTCLR = PORTX.IN; } }
Kolejnym rejestrem jest PORTX.OUTTGL. Wpisanie jedynek do tego rejestru, powoduje on zanegowanie odpowiadających im bitów w OUT. Kolejny program zademonstruje działanie OUTTGL - przyciśnięcie klawisza spowoduje mruganie diody z odstępem 100ms.
#define F_CPU 2000000UL #include <avr/io.h> #include <util/delay.h> #include "extrino_portx.h" int main(void) { PortxInit(); // inicjalizacja PORTX while(1) { PORTX.OUTTGL = PORTX.IN; _delay_ms(100); } }
Podsumujmy rejestry PORTX.
PORTX wykorzystuje interfejs SPI na porcie C oraz przerwania o niskim priorytecie. W momencie zakończenia transmisji SPI, wywoływane jest przerwanie, które rozpoczyna następną transmisję. Dzięki temu port odświeża się samoczynnie bez potrzeby zawracania Twojej uwagi. Ponieważ wszystkie peryferia eXtrino, takie jak karta SD, pamięć SPI, potencjometr cyfrowy, wzmacniacz programowalny oraz nakładki Arduino wykorzystują ten sam interfejs SPI - automatyczne odświeżanie portu może być czasami niewskazane.
Otwórz plik extrino_portx.h. Znajdziesz w nim definicję PORTX_AUTOREFRESH, która może przyjmować wartość 1, aby włączyć autoodświeżanie lub 0, by autoodświeżanie wyłączyć. Aby ręcznie odświeżyć PORTX, należy wywołać funkcję PortxRefresh(). Następny przykład ilustruje działanie trybu ręcznego odświeżania - stan rejestru wejściowego IN zostanie przepisany na wyjściowy OUT tylko wtedy, kiedy wciśnięty jest przycisk FLIP. W przeciwnym wypadku, PORTX pozostanie bez zmian.
#define F_CPU 2000000UL #include <avr/io.h> #include <util/delay.h> #include "extrino_portx.h" int main(void) { PortxInit(); // inicjalizacja PORTX while(1) { PORTX.OUT = PORTX.IN; if(!(PORTE.IN & PIN5_bm)) { // jeżeli przycisk FLIP wciśnięty PortxRefresh(); // odświeżenie PORTX } } }
To wszystko! Jeśli chcesz poznać techniczne aspekty działania PORTX, zachęcam do lektury poniższych odcinków kursu XMEGA:
#ifndef EXTRINO_PORTX_H_ #define EXTRINO_PORTX_H_ #include <avr/io.h> #include <avr/interrupt.h> // AUTOMATYCZNE ODŚWIEŻANIE PORTU X // PORTX należy odświeżać ręcznie, gdy SPI jest wykorzystywane jeszcze do innych celów // 1 - włączone // 0 - wyłączone #define PORTX_AUTOREFRESH 0 struct PORTX_t { uint8_t volatile IN; uint8_t volatile OUT; uint8_t volatile OUTSET; uint8_t volatile OUTCLR; uint8_t volatile OUTTGL; }; volatile struct PORTX_t PORTX; // inicjalizacja PORTX void PortxInit(void); // odświerzenie rejestrów PORTX #if PORTX_AUTOREFRESH == 0 void PortxRefresh(void); #endif #endif /* EXTRINO_PORTX_H_ */
#include "extrino_portx.h" volatile struct PORTX_t PORTX; void PortxInit(void) { // sygnały SS dla wszystkich peryferów na eXtrino XL PORTA.OUTSET = PIN3_bm | PIN4_bm; // SPI MEM, OP AMP PORTA.DIRSET = PIN3_bm | PIN4_bm; // SPI MEM, OP AMP PORTE.OUTSET = PIN3_bm | PIN6_bm | PIN7_bm; // SD, PORTX, DIGPOT PORTE.DIRSET = PIN3_bm | PIN6_bm | PIN7_bm; // SD, PORTX, DIGPOT PORTC.DIRSET = PIN4_bm | PIN5_bm | PIN7_bm; PORTC.DIRCLR = PIN6_bm; 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| SPI_MASTER_bm| SPI_MODE_3_gc| //SPI_CLK2X_bm| SPI_PRESCALER_DIV128_gc; #if PORTX_AUTOREFRESH // przerwania SPIC.INTCTRL = SPI_INTLVL_LO_gc; PMIC.CTRL = PMIC_HILVLEN_bm| // włączenie przerwań o priorytecie HI PMIC_MEDLVLEN_bm| // włączenie przerwań o priorytecie MED PMIC_LOLVLEN_bm; // włączenie przerwań o priorytecie LO sei(); SPIC.DATA = 0; // pierwsza transmisja, zerowanie #else PortxRefresh(); #endif } #if PORTX_AUTOREFRESH == 0 void PortxRefresh(void) { PORTE.OUTCLR = PIN6_bm; //for(uint8_t i = 255; i; i--); // opóźnienie PORTX.OUT |= PORTX.OUTSET; PORTX.OUT &= ~PORTX.OUTCLR; PORTX.OUT ^= PORTX.OUTTGL; PORTX.OUTSET = 0; PORTX.OUTCLR = 0; PORTX.OUTTGL = 0; SPIC.DATA = PORTX.OUT; while(SPIC.STATUS == 0); PORTX.IN = SPIC.DATA; for(uint8_t i = 255; i; i--); // opóźnienie PORTE.OUTSET = PIN6_bm; } #endif ISR(SPIC_INT_vect) { PORTE.OUTSET = PIN6_bm; // chip deselect PORTX.OUT |= PORTX.OUTSET; PORTX.OUT &= ~PORTX.OUTCLR; PORTX.OUT ^= PORTX.OUTTGL; PORTX.OUTSET = 0; PORTX.OUTCLR = 0; PORTX.OUTTGL = 0; PORTE.OUTCLR = PIN6_bm; // chip select PORTX.IN = SPIC.DATA; SPIC.DATA = PORTX.OUT; }
UWAGA - korzystaj z rozwiązań programistycznych oferowanych przez producenta, firmę Atmel - chodzi mi tu środowisko Atmel Studio. Środowisko to jest darmowe bez żadnych ograniczeń również do zastosowań komercyjnych, obsługuje najnowsze układy i oferuje całą gamę możliwości.
Zobacz więcej