Elvis Napisano Styczeń 23, 2011 Udostępnij Napisano Styczeń 23, 2011 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 Cytuj Link do komentarza Share on other sites More sharing options...
Bobby Listopad 14, 2011 Udostępnij Listopad 14, 2011 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. Cytuj Link do komentarza Share on other sites More sharing options...
baton Sierpień 26, 2013 Udostępnij Sierpień 26, 2013 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. 1 Cytuj Link do komentarza Share on other sites More sharing options...
Pomocna odpowiedź
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!