Skocz do zawartości

[Kurs] Kurs programowania ARM cz.06 - zegary i przerwania c.d.


Pomocna odpowiedź

Napisano

Poprzednia lekcja

Lekcja 6 - Zegary i przerwania (ciąg dalszy)

Następna lekcja

W poprzedniej lekcji uruchomiliśmy PLL oraz Timer0. Jeśli było to nieco niezrozumiałe, nie ma czym się przejmować.

Pętli PLL nie będziemy więcej ruszać. Wystarczy wywołać procedurę uruchamiającą i można o PLL (na razie) zapomnieć.

Podobnie będzie z Timer0. Zmienimy tylko częstotliwość wywoływania przerwań i więcej nie będziemy się zagłębiać w działanie timer-a.

Obecna konfiguracja to wywoływanie przerwania co 1s. Jest to zdecydowanie za rzadko. Ustalmy jakiś inny okres - przykładowo 100us (czyli 10.000 razy na sekundę).

W tym celu zmieniamy linijkę:

 T0_MR0 = 1500;

Od tej pory nie będziemy wracali do procedury timer0_init().

Nasze przerwanie jest wołane 10000 na sekundę, więc do sterowania diodami raczej się nie nadaje.

Wykorzystamy je jako licznik czasu.

Nowa procedura będzie wyglądała następująco:

volatile unsigned int t0_counter = 0;

void timer0(void) __attribute__ ((interrupt ("IRQ")));
void timer0(void)
{
if (T0_IR&0x01) {
	if (t0_counter) t0_counter--;
}

 T0_IR = 0xff;//0x01;
 VICVectAddr = 0;
}

Zmienna t0_counter przechowuje nasz licznik. Jest on co 100us zmniejszany o 1.

Gdy osiągnie 0 jest zatrzymywany.

Do czego nam się to może przydać? Wreszcie możemy napisać znane z AVR procedury delay_us() oraz delay_ms().

Niestety nie będziemy mogli odmierzać czasu z dokładnością do pojedynczych mikrosekund.


void delay_100us(unsigned int dly)
{
t0_counter = dly;
while (t0_counter);
}

void delay_ms(unsigned int ms)
{
t0_counter = ms*10;
while (t0_counter);
}

Procedury działają praktycznie tak samo - ustawiają naszą zmienną licznikową, po czym czekają, aż zostanie wyzerowana.

Bardzo ważne jest zadeklarowanie zmiennej t0_counter jako volatile. Inaczej optymalizator może wyrzucić nasze pętle while z kodu.

Teraz możemy napisać bardzo prosty program migający diodami co pół sekundy:

int main(void)
{
GPIO1_IODIR |= LED_MASK;

pll_init();
timer0_init();

while (1) {
	led_swap();
	delay_ms(500);
}
}

Pełny kod znajdziemy w pliku program10.zip

Program10.zip

Nie do końca się zgodzę, że pomiar wartości będących wielokrotnościami 100us będzie dokładny: z racji iż funkcja opóźniająca jest wywoływana w losowej chwili czasu, a timer będzie naliczał co 100us, to będziemy mieć niedokładność w przedziale 0-100us (rozkład ciągły prawdopodobieństwa), średnio 50us. Należałoby w funkcjach opóźniajacych dodać uruchamianie timera. Może starczy dodać na poczatku każdej z nich:

timer0_init();

albo po prostu

T0_TC=0;

Jak sprawdzić czy to się zgadza? Zostawcie przerwanie co 1s, zostawcie stary celay i w while(1) dajcie:

delay(500000);

led_swap();

delay_100us(1);

Prawidłowo całkowity delay powienien wynosić 1+nie_wiadomo_ile_z_delay(500000) - sprawdźcie z zegarkiem, bez zerowania licznika Timera będzie iść takt w takt ze wskazówką. Jeśli dodacie zerowanie, będzie sie spóźniać, ale jeśli usuniecie dodatkowe opóźnienie, będzie ok.

W tekście usunąłem informację o dużej dokładności. Błąd może wynosić 100µs, ale przy dłuższych opóźnieniach (rzędu setek ms) nie ma to znaczenia.

Nie chciałem modyfikować licznika timera ponieważ w dalszych częściach ten sam timer będzie wykorzystywany do kilku funkcji.

Nadal wolny jest Timer1, więc jeśli potrzebujemy bardzo dokładnie mierzyć czasy rzędu mikrosekund można go wykorzystać.

Hmm. W przypadku ATmeg dało się naprawdę dopracować delaya z pętlą - dokładność do 0.5 promila (wg symulatora). Tutaj dałoby się uzyskać podobną dokładność(która jest gorsza od tej przy użyciu Timera, ma sporo wad, ale nie wykorzystuje się Timera)? Czasami aż szkoda wykorzystywać dodatkowo Timer :/ Głównie chodzi mi o to, czy on sobie jakoś dodatkowo przyspiesza? W AVRach była instrukcja na takt zegara (przez co to działało), tutaj zdaje się czasami wykona więcej instrukcji w jednym takcie.

Aktywne pętle to nie najlepszy pomysł.

Po pierwsze jeśli chcemy, aby były dokładne musimy wyłączyć przerwania. Inaczej błędy będą dowolnie duże - w zależności ile razy wywołane zostanie przerwanie.

Kolejny problem to pobór prądu. Procesor bierze dużo prądu, gdy czeka w pętli.

Dla ARM najlepszym rozwiązaniem jest RTOS i co prawda mniejsza dokładność odmierzania czasu, ale za to wykorzystanie mocy procesora.

Jeśli potrzebujemy bardzo krótkie, dokładne pętle (czasy pojedynczych µs), to można zrobić jak na AVR. Najlepiej jest napisać to w asemblerze, inaczej kompilator (dokładniej - optymalizator) popsuje nam wyliczenia.

Bądź aktywny - zaloguj się lub utwórz konto!

Tylko zarejestrowani użytkownicy mogą komentować zawartość tej strony

Utwórz konto w ~20 sekund!

Zarejestruj nowe konto, to proste!

Zarejestruj się »

Zaloguj się

Posiadasz własne konto? Użyj go!

Zaloguj się »
×
×
  • Utwórz nowe...