Skocz do zawartości
Zaloguj się, aby obserwować  
Elvis

[Kurs] Kurs programowania ARM cz.04 - porty I/O c.d.

Pomocna odpowiedź

Poprzednia lekcja

Lekcja 4 - Porty I/O (ciąg dalszy)

Następna lekcja

Sterowanie diodami - usprawnianie programu

Poprzednia wersja programu działała całkiem ładnie, jednak w pętli głównej 8 razy (tyle ile jest diod) powtarzał się ten sam kod.

Nie jest to dobra metoda programowania. Po pierwsze program jest długi (wyobraźmy sobie sterowanie 100 diodami).

Po drugie, niewygodnie jest program modyfikować - zmiana na zapalanie diod parami wymaga przeglądania całej pętli głównej.

Jeśli wielokrotnie wykonujemy taki sam (lub prawie taki sam) program, zamiast kopiowania kodu, możemy zastosować pętlę.

W naszym przypadku pętla będzie miała 8 kroków (każdy, aby zapalić kolejną diodę).

Pozostaje pytanie, jak zapisać, kiedy ma się zapalić która dioda.

Moglibyśmy wykorzystać operacje na bitach oraz fakt, że diody są przyłączone do kolejnych linii. Jednak w ten sposób bardzo ciężko będzie zmienić program.

Użyjemy więc tablicy. Będziemy w niej przechowywać maski zapalanych diod w kolejnych krokach pokazu.

const unsigned int LEDS_PATTERN[8] = 
	{ 
		LED_0, LED_1, LED_2, LED_3, LED_4, LED_5, LED_6, LED_7
	};

Definiujemy tablicę 8 liczb. Są to maski kolejno zapalanych LED-ów.

Do programu dodamy licznik leds_index. Będzie on wskazywał, który indeks w tablicy mamy aktualnie "wyświetlać".

int leds_index = 0;

while (1) {
	leds_set(LEDS_PATTERN[leds_index]);
	leds_index++;
	if (leds_index>=8)
		leds_index = 0;

	delay(DELAY);
}

Instrukcja leds_set(LEDS_PATTERN[leds_index]); odczytuje maskę z tablicy LEDS_PATTERN i za pomocą procedury leds_set() zapala odpowiednie diody.

Następnie leds_index jest zwiększane o jeden, aby przejść do kolejnego etapu wyświetlania.

Za pomocą IF-a sprawdzamy, czy nie wyszliśmy poza zakres tablicy. Jeśli tak, rozpoczynamy animację od nowa.

W pliku program03.zip znajdziemy pełny kod programu.

Zmieniając dane w tablicy LEDS_PATTERN[] możemy zmienić układ zapalanych diod. Możemy jednocześnie zapalać kilka LED-ów, używając znanego operatora |.

Poniżej widzimy działanie programu dla tablicy LEDS_PATTERN[]. Cała reszta programu pozostaje bez zmian.

const unsigned int LEDS_PATTERN[8] = 
	{ 
		0, LED_3|LED_4, LED_2|LED_5, LED_1|LED_6, LED_0|LED_7, LED_1|LED_6, LED_2|LED_5, LED_3|LED_4
	};

W pliku program04.zip znajdziemy jeszcze jedną wersję programu. Tym razem diody są zapalane najpierw w kolejności zdefiniowanej w tablicy LEDS_PATTERN[], a później od końca. Jest to przykład na wykorzystanie dodatkowej zmiennej do przechowywania kierunku zmian. W podobny sposób sterujemy kierunkiem pracy silnika krokowego.

Wejścia - obsługa przycisku

Gdy mamy już dość zapalania i gaszenia diodek, czas coś odczytać z linii procesora.

Odnajdujemy na schemacie oraz płytce przycisk S2 - INT1.

Jest on podłączony (przez zworkę INT1) do linii P0.14 procesora.

Programowa obsługa wejścia jest bardzo prosta. Po resecie, linie procesora ustawiane są jako wejścia, więc nie musimy ich konfigurować (za pomocą rejestru IO0DIR).

Pozostaje jedynie odczytać stan wejścia. Służą do tego rejestry:

IO0PIN dla portu 0 oraz IO1PIN dla portu 1.

Jeśli linia połączona jest z masą, odczytamy na odpowiednim miejscu bit o wartości 0. Jeśli ma potencjał bliski napięciu zasilania (3.3V), to odczytamy 1.

Aby z 32 bitów składających się na rejestr wybrać interesujący używamy operatora koniunkcji (&).

Czyli wyrażenie

IO0PIN & _BV(14)

daje wartość 0, gdy na linii P0.14 jest napięcie 0V (masa), oraz 1 gdy podłączymy napięcie 3.3V.

Na naszej płytce rezystor R5 łączy zasilanie (3.3V) z linią P0.14. Więc, gdy S2 jest rozłączony, na linii P0.14 pojawi się logiczne 1.

Po naciśnięciu S2, linia P0.14 zostanie zwarta do masy. Otrzymamy więc wartość 0.

Czas napisać program, który odczyta stan przycisku S2. Pełny kod znajdziemy w pliku program05.zip

while (1) {
	if (GPIO0_IOPIN & _BV(14))
		leds_set(LED_0);
	if ((GPIO0_IOPIN & _BV(14))==0)
		leds_set(LED_4);
}

W języku C warunek jest prawdziwy, gdy jest dowolną liczbą różną od 0. Stąd pętla nieskończona while (1) ; ma jako warunek 1, czyli zawsze logiczną prawdę. Równie dobrze można napisać while (-5), czy while (1020).

Instrukcja warunkowa "if" warunek przyjmuje w nawiasach (). Jest on obliczany jako liczba, i jeśli wynosi 0 instrukcja po if jest pomijana, jeśli cokolwiek różnego od 0 to wykonywana.

if (GPIO0_IOPIN & _BV(14)) - linia P0.14 będzie podłączona do 3.3V to instrukcja po "if" zostanie wykonana (czyli leds_set(LED_0)).

Natomiast if ((GPIO0_IOPIN & _BV(14))==0) działa odwrotnie - wykona instrukcję po "if" jeśli P0.14 połączymy z masą.

Program działa następująco:

1) Gdy S2 jest rozwarty, na P0.14 mamy napięcie 3.3V, pierwszy if jest spełniony, wykonywana jest instrukcja po nim - zapalana jest dioda D0

2) Gdy S2 zostanie przyciśnięty, na P0.14 pojawi się potencjał masy (0V), drugi if będzie spełniony - zapali się dioda D4.

Czekanie na naciśnięcie przycisku

Sposób obsługi przycisków pokazany w poprzednim przykładzie jest prosty ma jednak dużą wadę. Kod zapalający diodę wykonywany jest wiele razy. Często nie chcemy, aby program tak działał - chcemy wykryć naciśnięcie przycisku, wykonać działanie dokładnie raz (np. uruchomić robota).

Do czekania na zdarzenie posłuży nam pętla while(). Wykonywana jest ona tak długo, jak jest parametr jest prawdziwy (różny od zero).

	while (GPIO0_IOPIN & _BV(14)) 
		;

Taka pętla będzie się wykonywać (i nic nie robić ; oznacza pustą instrukcję) tak długo, jak przycisk S2 będzie zwolniony (nie naciśnięty).

Warunek działa jak poprzednio. S2 jest zwolniony, na P0.14 jest napięcie 3.3V, odczytujemy wartość bitu 1, więc warunek jest prawdziwy.

Aby poczekać na zwolnienie przycisku, zmieniamy warunek pętli.

	while ((GPIO0_IOPIN & _BV(14))==0)
		;	

Pętla główna wygląda następująco:

while (1) {
	while (GPIO0_IOPIN & _BV(14)) 
		;
	leds_index++;
	if (leds_index>=8) 
		leds_index = 0;			
	leds_set(LEDS_PATTERN[leds_index]);
	while ((GPIO0_IOPIN & _BV(14))==0)
		;	
}

Najpierw czekamy na naciśnięcie S2.

Zapalamy kolejną diodę (kod wygląda znajomo - był opisywany wcześniej).

Następnie czekamy na zwolnienie S2.

Drgania styków

Osoby, które wcześniej programowały obsługę mikroprzełączników zwrócą uwagę, że program ma błąd - przy naciskaniu i zwalnianiu przycisku generowanych jest wiele bardzo krótkich impulsów. Są one efektem drgania styków wewnątrz przełącznika.

Powinniśmy je eliminować za pomocą kilkukrotnego odczytu stanu przycisku.

Jednak na naszej płytce ewaluacyjnej program działa w pełni poprawnie.

Dzieje się tak za sprawą kondensatora C9. Eliminuje on drgania styków sprzętowo.

Jest to układ wart zapamiętania - czasem zamiast komplikować program, można po prostu dodać kondensator.

Pełny kod źródłowy przykładu znajdziemy w pliku program06.zip.

W pliku program07.zip znajdziemy nieco inny program - działa podobnie, jednak zamiast zapalać kolejne diody, przy kolejnych naciskaniach S2 zapala lub gasi diodę D0. Schemat działania jest identyczny jak omawianego przykładu.

Program07.zip

Program06.zip

Program05.zip

Program04.zip

Program03.zip

  • Lubię! 1

Udostępnij ten post


Link to post
Share on other sites

Witam.

Chciałbym serdecznie podziękować Elvisowi.

Pierwsze dwie lekcje umożliwiły mi zaczęcie zabawy z ARMami.

Okazało się, że mam płytkę nie ZL1 (stosowaną w kursie) a ZL5 z trochę innym procesorem,
ale początek - opis środowiska WinARM i jego konfiguracja

umożliwiły mi start.

Wśród przykładów z LEDami brakuje mi instrukcji jak wyświetlić jakąś wartość

w postaci binarnej.

Dzięki temu kursowi zabrałem się za programowanie ARMów,
więc postanowiłem przedstawić przykładowy kod zliczania binarnego:

#include "LPC21xx_keil.h"

#define FOREVER 1
#define _BV(x) 	(1<<(x))
#define LED0	16
#define LED1	17
#define LED2	18
#define LED3	19
#define LED4	20
#define LED5	21
#define LED6	22
#define LED7	23

const unsigned int LEDS[8] = 
{
	LED0, LED1, LED2, LED3, LED4, LED5, LED6, LED7
};


void clear_all(void);

int main(void)
{

volatile int i;
volatile int j;

for(i=0;i<8;i++)
	IODIR1 |= _BV(LEDS[i]);

clear_all();

while (FOREVER)
{

	for(j=0; j<256; j++)
	{
		clear_all();
		IOSET1 |= j << 16;
		for(i=0; i<30000; i++);
	}

}
}

void clear_all(void)
{
volatile int i;
for(i=0; i<8; i++)
IOCLR1 |= _BV(LEDS[i]);
}

Kod ten jest dla procesora LPC2129 a nie 2114,
ale przeróbki rejestrów OI są niewielkie.

Może ktoś będzie zainteresowany...

Udostępnij ten post


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!

Gość
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.

Zaloguj się, aby obserwować  

×
×
  • Utwórz nowe...