Skocz do zawartości

[Kurs] Kurs programowania ARM cz.03 - porty I/O


Elvis

Pomocna odpowiedź

Poprzednia lekcja

Lekcja 3 - Porty I/O

Następna lekcja

Procesor LPC2114 jest układem w pełni 32-bitowym. Dzięki temu porty I/O również mogą być 32-bitowe. W przypadku rodziny AVR porty oznaczane były kolejnymi literami A,B,C, itd. (PORTA, PORTB, PORC, itd.). Każdy port miał maksymalnie 8 linii (bo procesor był 8-bitowy). W przypadku LPC2114 porty są oznaczane numerami. Mamy więc port 0 oraz 1. Każdy może mieć maksymalnie 32 linie (w rzeczywistości mają nieco mniej).

W dokumentacji, linie oznaczane są na dwa sposoby:

1) P0[5] - oznacza linię 5 portu 0, P1[16] - linię 16 portu 1 itd.

2) PORT0.2 - oznacza linię 2 portu 0, PORT1.20 - linię 20 portu 1 itd.

W pliku LPC2114_2124_datasheet.pdf znajdujemy na stronie 5 rysunek opisujący wszystkie wyprowadzenia naszego procesora.

Po resecie (uruchomieniu) procesora, wszystkie piny są skonfigurowane jako wejścia. Zamiast czytać dokumentację procesora, czasem łatwiej jest odczytać dane o portach I/O ze schematu układu. W dokumentacji naszej płytki możemy znaleźć schemat, poniżej widzimy symbol procesora oraz opisane jego wyprowadzenia:

Warto poświęcić chwilę na dokładniejsze przeczytanie schematu naszej płytki. Na początek zajmiemy się sterowaniem diodami LED. Jak widać są one podłączone do linii nazwanych LCD0, LCD1, LCD2, itd. (nazwa wynika stąd, że te same linie posłużą nam później do sterowania wyświetlaczem LCD. Z poziomu procesora sterujemy liniami za pomocą logicznych zer oraz jedynek. Ustawienie bitu, czyli logicznej jedynki powoduje pojawienie się napięcia zasilającego procesor (3.3V) na odpowiednim wyprowadzeniu. Natomiast wyzerowanie bitu, czyli logiczne zero powoduje zwarcie pinu do masy (GND).

Następujący kod:

IO0SET = _BV(2);

Powoduje, że linia 2 portu 0 zostanie ustawiona, czyli pojawi się na niej napięcie 3.3V.

Aby wyzerować stan linii używamy następującego kodu:

IO0CLR = _BV(2);

Widzimy więc, że inny rejestr jest używany do ustawiania bitów (IO0SET), inny do zerowania (IO0CLR). W AVR wykorzystywany był jeden rejestr (np. PORTA), a zapalanie gaszenie bitów wykonywane było za pomocą operacji na bitach (|= oraz &= ). Za pomocą jednej instrukcji można ustawić więcej niż jeden bit. Służy do tego operator alternatywy |.

IO0SET = _BV(2)|_BV(3)|_BV(4);

Powoduje jednoczesne ustawienie logicznej jedynki na liniach P0.2, P0.3 oraz P0.4.

Niemal identycznie można wyzerować kilka bitów na raz.

IO0CLR = _BV(2)|_BV(3)|_BV(4);

Uwaga! Kod znany z AVR, czyli IO0SET |= _BV(2); też zadziała, ale jest błędny. Może powodować niepoprawne działanie programu. Zagadka: kto zgadnie kiedy i jaki może powstać błąd?

Podobnie jak w przypadku AVR, przed użyciem linii, musimy ustalić kierunek jej działania (wejście/wyjście). Służy do tego rejestr IO0DIR (oraz IO1DIR). Ustawienie bitu powoduje przełączenie linii w tryb wyjścia, wygaszenie (domyślnie) ustawia linię jako wejście.

Do rejetru IO0DIR używamy zwykłych operacji bitowych (|= oraz &=).

Pierwszy program

Nasz pierwszy przykładowy program wygląda następująco (Program01.zip):

#include "lpc2114.h"
#define _BV(x) 	(1<<(x))

int main(void)
{
volatile int i;
GPIO1_IODIR |= _BV(16)|_BV(17);
while (1) {
	GPIO1_IOSET = _BV(16);
	GPIO1_IOCLR = _BV(17);
	for (i=0;i<100000;i++)
		;
	GPIO1_IOCLR = _BV(16);
	GPIO1_IOSET = _BV(17);
	for (i=0;i<100000;i++)
		;
}
}

Program definiuje makro _BV() - jest ono bardzo wygodne i znane wszystkim programującym w C na AVR. Instrukcja GPIO1_IODIR |= _BV(16)|_BV(17); sprawia, że linie 16 oraz 17 portu 1 pracują jako wyjścia. IO1SET = _BV(16) sprawia, że na linii P1.23 pojawia się napięcie 3.3V. Efektem jest zapalenie diody D0 (na schemacie ma numer 16, ale na płytce 0). IO1CLR = _ BV(17) gasi diodę D1.

GPIO1_IOSET = _BV(16);
GPIO1_IOCLR = _BV(17);
for (i=0;i<100000;i++)

1) Zapala diodę D0

2) Gasi diodę D1

3) Czeka pewien czas w pętli (niestety nie mamy dostępnej funkcji _delay_ms() - w kolejnych lekcjach nauczymy się jak samemu ją dodać)

Następny blok instrukcji gasi diodę D0, zapala D1 i ponownie czeka pewien czas.

Całość zamknięta jest w nieskończonej pętli - w efekcie program w nieskończoność (no może do wyłączenia zasilania) na zmianę zapala diodę D0 i D1.

Program drugi - więcej diod

Pierwszy program miał kilka istotnych wad. Przede wszystkim sterował tylko 2 pierwsze diody. Pozostałe świeciły cały czas. Teraz czas na Program02.zip. Dodamy obsługę wszystkich diod. Pierwszy krok to zdefiniowane stałych - poprzednio odwoływaliśmy się do linii sterujących LED-ami bezpośrednio. Kod _BV(17) nie jest zbyt czytelny. Dla dwóch diod mogliśmy zapamiętać, która jest podłączona do której linii, jednak dla 8 może już być trudniej. Zamiast uczyć się schematu na pamięć zdefiniujemy, gdzie są podłączone kolejne diody:

#define LED_0		_BV(16)
#define LED_1		_BV(17)
#define LED_2		_BV(18)
#define LED_3		_BV(19)
#define LED_4		_BV(20)
#define LED_5		_BV(21)
#define LED_6		_BV(22)
#define LED_7		_BV(23)

Dzięki temu program będzie bardziej czytelny. Definiujemy również "maskę", czyli wartość odpowiadającą wszystkim liniom z podłączonymi diodami. Dzięki temu łatwiej będzie ustawić wszystkie linie sterujące LED-ami w tryb wyjścia.

#define LED_MASK (LED_0|LED_1|LED_2|LED_3|LED_4|LED_5|LED_6|LED_7)

Gdy mamy już zdefiniowane stałe, kolejnym krokiem jest utworzenie procedur, czyli fragmentów programu, które będą wywoływane wielokrotnie, z różnymi parametrami.

Pierwsza procedura posłuży nam do zapalania wybranych diod, a wygaszania pozostałych.

void leds_set(unsigned int leds)
{
GPIO1_IOCLR = LED_MASK;
GPIO1_IOSET = leds;
}

Najpierw gasimy wszystkie diody (zdefiniowana wcześniej maska ułatwia to zadanie).

Następnie zapalamy wybrane.Druga procedura pomocnicza zapewni opóźnienia.

void delay(unsigned int d)
{
volatile int i;
for (i=0;i<d;i++)
	;
}

Warto zwrócić uwagę na słowo kluczowe volatile. Bez niego optymalizator próbowałby przyspieszyć działanie naszej pętli opóźniającej. Mając przygotowane procedury, program główny jest już bardzo prosty:

GPIO1_IODIR |= LED_MASK;	

while (1) {
	leds_set(LED_0);
	delay(DELAY);

	leds_set(LED_1);
	delay(DELAY);		
	leds_set(LED_2);
	delay(DELAY);		
	leds_set(LED_3);
	delay(DELAY);		
	leds_set(LED_4);
	delay(DELAY);		
	leds_set(LED_5);
	delay(DELAY);		
	leds_set(LED_6);
	delay(DELAY);		
	leds_set(LED_7);
	delay(DELAY);
}

Najpierw wszystkie linie, do których podłączone są diody ustawiamy jako wyjścia, następnie w nieskończonej pętli zapalamy kolejne diody.

Następna część opublikowana zostanie już w najbliższą niedzielę, a jej tematem również będą porty I/O.

Autor kursu:

Elvis

Pomoc przy edycji materiałów wideo, formatowaniu i publikacji:

Treker

Program02.zip

Program01.zip

  • Lubię! 1
Link do komentarza
Share on other sites

Dołącz do dyskusji, napisz odpowiedź!

Jeśli masz już konto to zaloguj się teraz, aby opublikować wiadomość jako Ty. Możesz też napisać teraz i zarejestrować się później.
Uwaga: wgrywanie zdjęć i załączników dostępne jest po zalogowaniu!

Anonim
Dołącz do dyskusji! Kliknij i zacznij pisać...

×   Wklejony jako tekst z formatowaniem.   Przywróć formatowanie

  Dozwolonych jest tylko 75 emoji.

×   Twój link będzie automatycznie osadzony.   Wyświetlać jako link

×   Twoja poprzednia zawartość została przywrócona.   Wyczyść edytor

×   Nie możesz wkleić zdjęć bezpośrednio. Prześlij lub wstaw obrazy z adresu URL.

×
×
  • Utwórz nowe...

Ważne informacje

Ta strona używa ciasteczek (cookies), dzięki którym może działać lepiej. Więcej na ten temat znajdziesz w Polityce Prywatności.