LinkedIn YouTube Facebook
Szukaj

Wstecz
Artykuły

Mikrokontrolery AVR XMEGA w praktyce, część 3. Nowe metody konfiguracji rejestrów

Porty są najprostszym układem peryferyjnym każdego mikrokontrolera. Mimo to, w XMEGA do obsługi portu mamy aż… 21 rejestrów na każdy port! Wszystkich rejestrów konfiguracyjnych w procesorze może być kilkaset lub nawet ponad tysiąc! W tym artykule pokażę jak to ogarnąć i nie zwariować. Choć początek tego artykułu może wydawać się trochę mętny – proszę się nie zniechęcać, bo w dalszej części zamieściłem proste praktyczne przykłady.

Wartości można wpisywać do rejestrów w sposób znany ze starszych mikrokontrolerów AVR, czyli:

NAZWA_REJESTRU = (1 << BIT1) | (1 << BIT2);

Jednak mając do dyspozycji kilkaset rejestrów, taki sposób staje się mało wygodny. Inżynierowie Atmela wpadli na pomysł, by do konfiguracji rejestrów wykorzystać struktury, przez co kod wygląda nico inaczej:

NAZWA_PERYFERIUM.NAZWA_REJESTRU = ...;

Tak jak pisałem w poprzednim artykule, układy peryferyjne mikrokontrolerów XMEGA są wielokrotnie powielone, a różnią się jedynie adresami rejestrów w pamięci oraz nazwą peryferium (PORTA, PORTB, PORTC…). Poza tym wszystko jest identyczne. Można zatem wpisać jakąś wartość do rejestrów portów w ten sposób:

PORTA.DIR = ...;
PORTB.DIR = ...;
PORTC.DIR = ...;
PORTA.OUT = ...;
PORTB.OUT = ...;
PORTC.OUT = ...;

Pisanie kodu programu przy użyciu struktur niesie ze sobą bardzo ważną zaletę – raz napisany kod dla jakiegoś peryferium może być użyty do obsługi wszystkich jego kopii. Tak więc jeśli mamy do dyspozycji 8 interfejsów USART, to w przypadku starych AVR­-ów funkcje obsługujące USART należałoby skopiować osiem razy i pozmieniać w nich nazwy rejestrów. W przypadku XMEGA wystarczy napisać funkcję raz, a jako argument podać jaki konkretnie układ peryferyjny nas interesuje.

Zobaczmy przykład, w jaki sposób można sterować różnymi portami przy pomocy jednej funckji:

void UstawPort(PORT_t *nazwaportu) {
	nazwaportu -> DIR = ...;
	nazwaportu -> OUT = ...;
}

Funkcja jako argument przyjmuje nazwę portu. Sposób jej użycia wygląda następująco:

UstawPort(&PORTA);
UstawPort(&PORTB);
UstawPort(&PORTC);

Dzięki zastosowaniu funkcji operującej na strukturach, można znacząco zmniejszyć rozmiar programu. Choć w przypadku portów, sposób ten może wydawać się trochę bez sensu, to zapewniam, że przy bardziej skomplikowanych peryferiach taki sposób zdecydowanie przyspiesza pisanie programu.

Do rejestrów można wpisywać wartości heksadecymalne lub binarne, jednak jest to proszenie się o błędy i marnowanie czasu. Takich metod lepiej nie stosować!

Dopuszczalny jest sposób znany ze starych AVR-ów, wykorzystujący operator przesunięcia bitowego << oraz predefiniowane symbole z końcówką _bp, czyli bit posiotion, określające numer bitu w rejestrze.

PORTA.DIR = (1 << PIN1_bp) | (1 << PIN0_bp);

Dostępna jest nowa metoda, wykorzystująca predefiniowane symbole z końcówką _bm, czyli bit mask. Dzięki wyeliminowaniu znaczków-krzaczków zapis staje się bardziej czytelny.

PORTA.DIR = PIN1_bm | PIN0_bm

Bardziej skomplikowane peryferia mogą mieć kilka bitów odpowiedzialnych za realizację jakiegoś procesu. Dobrym przykładem jest tu źródło taktowania timera, wybierane przy pomocy czterech bitów. Stosujemy w takim przypadku symbole z końcówką _gc (group configuration). Aby zilustrować przykład, zobaczmy na rysunku 1, jakie mamy możliwości ustawienia źródła taktowania i preskalera w timerze.

 

 Rys. 1. Przykład rejestru oraz wartości dostępne do wpisania

Rys. 1. Przykład rejestru oraz wartości dostępne do wpisania

 

Zatem, by ustawić preskaler timera TCC0 na wartość 64, musimy do rejestru CTRLA wpisać odpowiednią grupę konfiguracyjną CLKSEL. Wszystko wyjaśnia przykład:

TCC0.CTRLA = TC_CLKSEL_DIV64_gc;

Atmel Studio posiada bardzo przydatną funkcję przewidywania, co programista zamierza wpisać, przez co program podpowiada, jakie są dostępne możliwości. Przykład działania tej funkcji przedstawiono na rysunku 2.

 

Rys. 2. Automatyczne podpowiedzi w Atmel Studio 

Rys. 2. Automatyczne podpowiedzi w Atmel Studio

 

Gdyby w rejestrze CTRLA było więcej bitów do skonfigurowania, poszczególne symbole _gc, _bm możemy oddzielić operatorem |. Dla zwiększenia czytelności kodu, można instrukcje podzielić na kilka linijek oraz opatrzyć je stosownym komentarzem.

PERYFERIUM.REJESTR = CONFIG1_gc |   // komentarz
                     CONFIG2_gc |   // komentarz
                     CONFIG3_bm |   // komentarz
                     CONFIG4_bm ;   // komentarz

Nic nie stoi na przeszkodzie, by predefiniowane symbole były argumentami funkcji. Na przykład, można napisać funkcję konfigurującą jakiś układ peryferyjny i wywoływać ją w ten sposób:

TimerInit(&TCCO, TC_CLKSEL_DIV64_gc, inne argumenty...);
TimerInit(&TCC1, TC_CLKSEL_DIV2_gc, inne argumenty...);
TimerInit(&TCD0, TC_CLKSEL_EVCH0_gc, inne argumenty...);
TimerInit(&TCD1, TC_CLKSEL_OFF_gc, inne argumenty...);

W ten sposób przy pomocy jednej funkcji TimerInit skonfigurowaliśmy cztery timery o nazwach TCC0, TCC1, TCD0, TCD1.

Więcej opisów i przykładów jest w materiale szkoleniowym dostępnym na stronie firmy Atmel

AVR1000: Getting Started Writing C-code for XMEGA

oraz w książce Tomasza Francuza AVR. Praktyczne projekty.

Dystrybutorem zestawu X3-DIL64 jest KAMAMI.pl.

Dominik Leon Bieczyński

http://leon-instruments.blogspot.com