Skocz do zawartości

[Kurs] Programowanie ARM LPC1114 cz.2 - porty I/O


Pomocna odpowiedź

Zobacz: Poprzednia część kursu!

Programowanie ARM LPC1114 cz.2 - porty I/O

Porty wejścia-wyjścia są niewątpliwie najważniejszymi układami peryferyjnymi każdego procesora. W poprzedniej wersji kursu opisywałem dokładnie ich działanie. Teraz skoncentruję się nad zmianami w nowym mikrokontrolerze. Podobnie jak poprzednio, najważniejsza jest umiejętność korzystania z dokumentacji procesora. Całą dokumentację znajdziemy na stronie producenta. Najciekawszy jest dokument nazwany „User Manual” (co ciekawe Datasheet to jedynie ogólny opis procesora). Link do dokumentacji: http://ics.nxp.com/support/documents/microcontrollers/pdf/user.manual.lpc11xx.lpc11cxx.pdf

Podobnie jak poprzednio, w dokumentacji możemy znaleźć rozmieszczenie pinów procesora:

W procesorze LPC2114 porty I/O były 32-bitowe. Nowy procesor ma również 32-bitowe porty, jednak każdy port ma maksymalnie 12 pinów. Ułatwia to adresowani linii procesora.

Dostępne są 4 porty:

  • PORT0
  • PORT1
  • PORT2
  • PORT3

Piny oznakowywane są jako: PIO0_0, PIO0_1, itd. PIOx to numer portu, a część po podkreśleniu (_) to numer linii.

Sterowanie wyjściami procesora

Dostęp do portów nieco się zmienił w nowej wersji procesora. Zamiast bezpośrednio odwoływać się do rejestrów, wykorzystujemy wskaźniki. Każdy port posiada zdefiniowany wskaźnik na strukturę w pamięci. Szczegóły znajdziemy w pliku LPC11xx.h. Do portów odwołujemy się więc przez wskaźniki:

  • LPC_GPIO0
  • LPC_GPIO1
  • LPC_GPIO2
  • LPC_GPIO3

Każdy wskaźnik wskazuje na strukturę nazwaną LPC_GPIO_TypeDef. Jej pola odpowiadają rejestrom procesora.

Przykładowo pole DIR odpowiada za kierunek działania linii wejścia-wyjścia. Ustawienie bitu ustawia linię jako wyjście (identycznie jak w przypadku rejestrów DDRA, DDRB itd. procesorów AVR). W zestawie ZL32ARM dioda LED1 podłączona jest do linii P0_6. Aby ustawić tę linię jako wyjście użyjemy kodu:

LPC_GPIO0->DIR |= _BV(6);

Nie należy się więc obawiać wskaźników. Jest to nieco inny zapis niż byliśmy dotychczas przyzwyczajeni, ale działanie jest identyczne jak dotychczas. Kolejny rejestr który będzie nas interesował to rejestr DATA. Zapis do niego ustawia stan linii danego portu. Działa więc identycznie jak odwołanie do PORTA, PORTB, itd. w procesorach AVR. Aby zapalić diodę LED1 wykonujemy kod (zapalamy wystawiając logiczne zero!)

	LPC_GPIO0->DATA &= ~_BV(6);

Diodę gasimy podobnie:

	LPC_GPIO0->DATA |= _BV(6);

Rejestr DATA można również wykorzystać do odczytania stanu linii, gdy pracuje ona jako wejście.

Wykorzystanie rejestru DATA nie jest najlepszym rozwiązaniem, ale na początek może wydawać się najłatwiejsze. Pozwala na programowanie w stylu znanym z procesorów AVR. W pliku program01.zip znajdziemy przykładowy program. Ustawia on linię P0_6 jako wyjście, a następnie cyklicznie zapala i gasi diodę LED1. Plik program02.zip zawiera kolejny przykład, tym razem sterowane są diody LED1 oraz LED2 i zapalane naprzemiennie.

Dostęp maskowany

Korzystanie z rejestru DATA może powodować problemy, gdy w programie używane będą przerwania. Dodatkowo problemem może być jednoczesne ustawianie i wygaszanie bitów - konieczne może być wykonanie najpierw zerowanie, następnie ustawianie bitów. Aby w jednej instrukcji umożliwić zapis zarówno zer jak i jedynek do wybranych linii portu, dodany został rejestr MASKED_ACCESS. Rejestr ma postać tablicy o rozmiarze 4096 (czyli 2^12). W celu dostępu do wybranych linii zapisujemy (lub odczytujemy) pod indeks taki jak maska zmienianych bitów.

Przykładowo, załóżmy że do linii P1_4, P1_5, P1_6, P1_7 mamy podłączony wyświetlacz alfanumeryczny. Chcemy w jednej instrukcji zapisać wartość 5 do linii wyświetlacza, nie zmieniając jednocześnie pozostałych wartości. Chcemy więc wyzerować P1_7, P1_5, a ustawić P1_4 i P1_6, nie zmieniając stanu pozostałych linii.

Za pomocą rejestru DATA moglibyśmy wykonać następujący kod:

LPC_GPIO1->DATA |= _BV(6)|_BV(4);
LPC_GPIO1->DATA &= _BV(5)|_BV(6);

Aby wykorzystać rejestr MASKED_ACCESS, najpierw musimy ustalić jaka jest maska naszego zapisu. Maska ma „1” tam gdzie chcemy zmieniać dane, a „0” w miejscach które mają pozostać niezmienione. Czyli powinna wyglądać następująco:

Możemy ją zapisać jako:

_BV(7)|_BV(6)|_BV(5)|_BV(4)

Albo, co chyba nieco wygodniejsze po prostu:

0x0f0

Teraz możemy w jednej instrukcji dokonać zapisu danych:

LPC_GPIO1->MASKED_ACCESS[0x0f0] = 0x050;

Podobnie można odczytywać stan wybranych linii. Przykład użycia rejestru MASKED_ACCESS znajdziemy w pliku program03.zip. W kodzie zaprezentowane są 2 wersje. Pierwsza jednocześnie zapala diodę LED2 i gasi LED1:

LPC_GPIO0->MASKED_ACCESS[LED_MASK] = LED_1;

Druga wersja oddzielnie odwołuje się do każdego pinu. Ustawienie bitu (czyli zgaszenie diody) realizuje kod:

LPC_GPIO0->MASKED_ACCESS[LED_1] = LED_1;

Natomiast wygaszenie bitu:

LPC_GPIO0->MASKED_ACCESS[LED_2] = 0;

Funkcje dostępu do pinów

Przyznam, że wykorzystanie rejestru MASKED_ACCESS chociaż wydajne, nie zawsze jest intuicyjne. Aby nieco ułatwić programowanie, zdefiniujemy funkcje do ustawiania i wygaszania odpowiednich linii procesora.

Funkcja ustawiająca „1” na odpowiednich wyjściach wygląda następująco:

void GPIOSet(LPC_GPIO_TypeDef* gpio, uint32_t pin)
{
gpio->MASKED_ACCESS[pin] = pin;
}

Natomiast ustawienie „0” wygląda tak:

void GPIOClear(LPC_GPIO_TypeDef* gpio, uint32_t pin)
{
gpio->MASKED_ACCESS[pin] = 0;
}

Warto zwrócić uwagę na parametr gpio. Jest to wskaźnik na strukturę opisującą port. Dzięki temu funkcja może obsługiwać każdy port GPIO procesora.

W pliku program04.zip znajdziemy opisane funkcje oraz przykład ich użycia:

	while(1) {
	GPIOSet(LPC_GPIO0, LED_1);
	GPIOClear(LPC_GPIO0, LED_2);
	for (i=0;i<1000000;i++)
		;
	GPIOClear(LPC_GPIO0, LED_1);
	GPIOSet(LPC_GPIO0, LED_2);
	for (i=0;i<1000000;i++)
		;
}

W pliku program05.zip zobaczymy pewne ułatwienie w definiowaniu, gdzie podłączone są diody. Zamiast wpisywać LPC_GPIO0 w wywołaniu GPIOClear/GPIOSet definiujemy następujące stałe:

#define LED_1_PIN	_BV(6)
#define LED_2_PIN	_BV(7)

#define LED_1		LPC_GPIO0, LED_1_PIN
#define LED_2		LPC_GPIO0, LED_2_PIN

Dzięki takiej definicji kod jest jeszcze łatwiejszy do analizy, a zmiana podłączenia peryferiów na inny port wymaga jedynie zmiany w definicji.

GPIOSet(LED_1);
GPIOClear(LED_2);

Plik program06.zip zawiera kolejną wersję programu, tym razem definicje podłączeń układów zostały przeniesione do nowego pliku hardware.h. Ułatwia to dostosowywanie programu do zmian w elektronice. Opisy podłączeń są zebrane w oddzielnym pliku.

Odczyt stanu linii

Gdy potrafimy już sterować liniami procesora, czas odczytać stan linii. W przykładowym zestawie znajdziemy joystick, którego stan postaramy się odczytywać. Jak widać przycisk środkowy (JOY_OK) jest podłączony do portu 0, pozostałe do portu 1. Dzięki opisanej wcześniej definicji linii nie stanowi to problemu.

#define JOY_OK_PIN	_BV(3)
#define JOY_U_PIN	_BV(8)

#define JOY_OK		LPC_GPIO0, JOY_OK_PIN
#define JOY_U		LPC_GPIO1, JOY_U_PIN

Teraz potrzebujemy funkcji odczytu stanu linii. Wygląda następująco:

int GPIOGet(LPC_GPIO_TypeDef* gpio, uint32_t pin)
{
if (gpio->MASKED_ACCESS[pin]) return 1;
return 0;
}

Aby sprawdzić kod w działaniu, będziemy zapalać diodę po przyciśnięciu przycisku joysticka

(trzeba pamiętać, że na wejściu pojawia się 0, gdy przycisk jest naciśnięty):

if (GPIOGet(JOY_OK)==0)
		GPIOClear(LED_1);
	else
		GPIOSet(LED_1);

W pliku program07. zip znajdziemy przykład odczytu stanu linii wejścia-wyjścia.

Własna biblioteka funkcji

Kolejny plik - program08.zip zawiera opisywane wcześniej funkcje wydzielone do nowego modułu. Pliki gpio.c oraz gpio.h będą wykorzystywane w kolejnych programach.

Dodatkowe funkcje

Warto wspomnieć o pozostałych możliwościach portów I/O.

Większość pinów oprócz działania jako wejście-wyjście może też pełnić inne funkcje, jak chociażby wyprowadzenia modułów UART, I2C, czy SPI. Do konfiguracji funkcji alternatywnych służy wskaźnik LPC_IOCON. Wskazywana struktura posiada pola dopowiadające kolejnym pinom. Możemy w niej ustawić funkcje pełnione przez linie, włączyć lub wyłączyć rezystory podciągające i histerezę. Z możliwości tych będziemy korzystać w kolejnej części kursu - gdy będziemy poznawać komunikację przez RS-232 oraz działanie przetwornika analogo-cyforwego (ADC).

Warto również wspomnieć o możliwości generowania przerwań w reakcji na zmianę stanu pinu (lub poziom). Nie będziemy tego wykorzystywać, jednak czasem może być to użyteczne - szczególnie, gdy zależy nam na natychmiastowej reakcji np. na wykrycie przeszkody czujnikiem.

program01.zip

program02.zip

program03.zip

program04.zip

program05.zip

program06.zip

program07.zip

program08.zip

Link to post
Share on other sites

Jest mi w stanie ktoś wytłumaczyć, dlaczego nie jestem w stanie wystawić 1 na GPIO0.9 i GPIO0.10? IOCON ustawione odpowiednio, tj 0x00 dla 0.9 i 0x01 dla 0.10 (czyli gpio, a nie MOSI tudzież inne MAT). Próbowałem zapisywać do portu przeróżnie, od makra _BV();, po chamskie wpisanie binarnie 0b11000000000.

Link to post
Share on other sites

Może trochę kotlet, ale to chyba jedyna polska strona, którą znalazłem, szukając rozwiązania powyższego problemu - czyli co zrobić, gdy wszystko mamy ustawione poprawnie, ale linia PIO 0.10 nie chce działać - także pozwolę sobie zamieścić rozwiązanie, dla potomnych.

Otóż - odpinamy programator od układu.

  • Lubię! 1
Link to post
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.