Kurs XMEGA: nowe metody konfiguracji rejestrów (03)

|

Kurs XMEGA
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 funkcji:
     
    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, jakie mamy możliwości ustawienia źródła taktowania i preskalera w timerze.



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.



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.
Kurs XMEGA:Moduły prototypowe:

6 komentarze :

Anonimowy pisze...

świetny pomysł z tymi _bm i _gc bo w AVRach zawsze mnie irytowały te dziwne zapisy z (1<<COŚTAM) w szczególności gdy było kilka bitów do skonfigurowania

B&W pisze...

A propos; oznaczenie "1<<coś tam" to podobno przesunięcie bitowe. Ale z którego bitu na który, kierunek raczej w lewo i chyba o jedną pozycję. Czy tak?

Dominik Leon Bieczyński pisze...

Przesunięcie jedynki w lewo na pozycję "coś tam". Czyli zapis REJESTR = (1<<3) | (1<<0) spowoduje zapisanie 00001001 do rejestru. Na stronie http://pl.wikibooks.org/wiki/C jest fajny kurs języka C z którego kiedyś często kożytsałem.

B&W pisze...

Wynika z tego, że słowo "przesunięcie" (zazwyczaj związane z rotacją) nie jest tu najszczęśliwsze, bo to jest po prostu ustawianie danego bitu (wpisanie jedynki) bez względu na sąsiada z prawej strony. (Wszak nie spotkałem zapisu: "cośtam>>1" który odnosiłby się do sąsiada z lewej) Ale to tak na marginesie...
Mnie interesuje dziś coś innego;
Po paru godzinach wgryzania się w obfitująca w błędy dokumentację ze strony Atmela, chyba zrozumiałem na czym (z grubsza) polega idea nowości w kontrolerach xmega i zdecydowałem się kupić polecany tu moduł. Ponieważ jednak chcę pozostać (póki co) przy assemblerze, z którym się dość mocno zżyłem emocjonalnie ;) - jak to elektronik, mam pytanie następujące: Jak wygląda nazwa pliku nagłówkowego dla xmega128a3u, którą umieszcza się po dyrektywie ".include"? Np. dla mega8 było to "m8def.inc" a tu... próbuję na różne sposoby, bezskutecznie :/
Co do kursu języka C - dzięki za link, być może kiedyś skorzystam. Może nawet szybciej niż mi się wydaje, ale na razie... :)
Kupiłem też polecaną tu książkę i niestety muszę stwierdzić, że był to błąd (z mojego dzisiejszego punktu widzenia), bo jest ona wybitnie ukierunkowana na wykorzystanie języka C, a nie na budowę tego kontrolera (po prostu jest mało... elektroniczna).

Dominik Leon Bieczyński pisze...

Pliki nagłówkowe do XMEGA nazywają się tak jak procesory, czyli "ATxmega128A3Udef.inc".

Naprawdę polecam zapoznanie się z językiem C, bo dzisiaj jest to standard. Język ten jest baaaaaardzo prosty, kompilator jest bardzo efektywny i nie widzę sensu pisania programów w Assemblerze.

B&W pisze...

Dzięki, zadziałało :)
Czasem najtrudniej wpaść na najprostsze rozwiązanie.

Prześlij komentarz

Skomentuj!

Sklep Leon Instruments