Mikrokontrolery AVR część 6 – timer0

Ta część kursu będzie poświęcona najprostszemu z układów licznikowych w mikrokontrolerach atmega8. Będę mówiło liczniki/czasomierzu 0. Takie licznik/czasomierz to po prostu układ, którego celem jest zliczać przychodzące do niego impulsy.

Tutaj już pojawia się problem kiedy mówimy że coś jest licznikiem, a kiedy czasomierzem. Generalnie to jest to dokładnie ten sam układ, ale zależnie od swojej roli, działa impulsach o różnym pochodzeniu. Czyli licznik, to układ, który tylko zlicza impulsy, a te impulsy pochodzą z zewnątrz układu, a czasomierz, mierzy czas, czyli musi być dokładny, więc zlicza impulsy pochodzące z zegara taktującego kontroler.

Poniżej zamieszczam schemat licznika/czasomierza 0, schemat zaczerpnięty z noty katalogowej.

Licznik ten składa się z:

  • Bloku wejściowego, w którym dokonuje się wyboru źródła impulsów, na schemacie oznaczony jest jako Clock Select
  • Układu sterującego, który zarządza działaniem licznika
  • rejestru TCNTn, w przypadku licznika 0 rejestr nazywa się po prostu TCNT0, jest to najważniejszy rejestr licznika, bo właśnie to w nim są zliczane impulsy
  • rejestru TCCR0, który umożliwia konfigurację licznika

Dodatkowym układem, którego nie ma na schemacie jest preskaler. W układzie wyboru źródła impulsów widać sygnał określony jako From Prescaler. Co takiego te preskaler robi, dzieli on sygnał z zegara na sygnał o mniszej częstotliwości. Możliwy jest podział przez:1, 8, 64, 256, 1024.

Po co to dzielimy? Czasem (nawet bardzo często) zachodzi potrzeba, że częstotliwość taktowania jest zbyt duża, i było by ją ciężko zliczyć, więc gdy ją podzielimy, wtedy automatycznie mamy mniej impulsów do zliczania.

Jednym z najważniejszych parametrów licznika jest jego pojemność wyrażana w bitach. Gdzie ta pojemność jest? Jest to rejestr TCNTn. W który się zlicza impulsy. Im większa pojemność, tym więcej impulsów można zmieścić (zliczyć). W przypadku licznika 0 jego pojemność wynosi 8b, czyli można zliczyć w mim 256 impulsów. A co się dzieje, gdy licznik zostanie zapełniony (wartość FFh), wtedy licznik się ?przewija? i zlicza od początku, ale także wysyła sygnał, że skończył zliczać. I to w tym wszystkim jest najważniejsze. Za każdym razem gdy się przepełni daje nam znać, że to zrobił. I wtedy wiemy, że miął określony czas, bądź dotarła do kontrolera określona ilość impulsów. W tedy też może być generowane przerwanie.

Teraz kiedy już wiemy jak mniej więcej działa licznik, przejdźmy do jego konfiguracji. Zacznijmy od tego jak wybrać sygnał impulsów. Służą do tego trzy bity w rejestrze TCCR0. Poniżej przedstawiam ten rejestr wraz z tabelą możliwych konfiguracji.

Bit 7 6 5 4 3 2 1 0
  CS02 CS01 CS00
Read/Write R R R R R R/W R/W R/W
Initial Value 0 0 0 0 0 0 0 0

 

CS02 CS01 CS00 Description
0 0 0 No clock source (Timer/Counter stopped).
0 0 1 clkI/O/(No prescaling)
0 1 0 clkI/O/8 (From prescaler)
0 1 1 clkI/O/64 (From prescaler)
1 0 0 clkI/O/256 (From prescaler)
1 0 1 clkI/O/1024 (From prescaler)
1 1 0 External clock source on T0 pin. Clock on falling edge.
1 1 1 External clock source on T0 pin. Clock on rising edge.

Jest to chyba na tyle jasne, że nie muszę tłumaczyć, przedstawię to później w przykładzie. A teraz przejdę do następnego rejestru.

Gdy licznik się przepełnia, jak pisałem wcześniej zostaje wysłany sygnał. Ślad po tym sygnale zostaje jako ustawiony bit TOV0 w rejestrze TIFR. Możemy w głównym programie sprawdzać ciągle stan tego bitu i zależnie od tego czy jest ustawiony, czy nie podjąć pewną akcję. Ale po co się męczyć i zawracać ciągle głowę kontrolerowi tym sprawdzaniem, mamy przecież przerwania. Żeby skonfigurować przerwania od przepełnienia licznika/czasomierza0 musimy tylko włączyć te przerwanie. Odpowiedzialny jest za to bit TOIE0 w rejestrze TIMSK.

Kiedy ten bit będzie ustawiony (1), to za każdym razem gdy się przepełni licznik zostanie wywołane przerwanie. Przypominam, że jeżeli chcemy aby nasze przerwanie działało musimy włączyć globalny system przerwań.

Już wiemy jak ustawiać preskaler, ale jak skąd mamy wiedzieć jakie ustawienie jest najlepsze. Powiem teraz o tym jaki dzielnik najlepiej wybrać. Jest to koszmar porządkujących, żeby dobrać takie nastawy, aby czas mierzony był prawidłowy i dokładny, sprawa nie jest taka trudna jak się wydaje, tylko trzeba poznać sposób działania. Wytłumaczę to na przykładzie.

Przykład 1

Załóżmy, że mamy kontroler taktowany częstotliwością 1MHz i chcemy odmierzać dokładnie 1 sekundę.

Na początek trzeba przyjąć zasadę, że impuls musi być zawsze cały nigdy jego część. Czyli na żadnym etapie nie może nam wyjść wartość inna niż całkowita. Cała ta operacja opiera się na dzieleniu liczby 1M, bądź wyników z poprzedniego dzielenia. Więc mamy:

1000 000/64/125 = 125

Teraz wytłumaczę dzielimy przez 64, wartość z preskalera i po tej operacji mamy wartość 15625. Następnie mamy rejestr TCNT0 i w nim dzielimy przez 125, czyli co 125 impulsów będziemy mieli przepełnienie. No ale przecież mamy rejestr 8b, czyli 256 impulsów? I tutaj trzeba zastosować metodę załadowania wartości początkowej. Jak to robimy? Po prostu wpisujemy tam wartość 256-125, czyli 131. Na koniec zostało nam jakieś takie 125, a powinno być 1. Niestety bardziej się nie da podzielić. Trzeba stworzyć jeszcze jeden własny dzielnik w funkcji obsługi przepełnienia timera. Taki licznik to po prostu zmienna, którą zwiększamy o 1 za każdym przepełnieniem licznika 0. A kiedy osiągnie wartość 125 w tedy wiemy, że mamy odmierzoną idealnie 1 s. Na początek może się to wydawać bardzo zawiłe, ale gdy przeliczymy to kilka razy sami dla różnych wartości staje się to łatwe.

Przykład 2

Teraz przedstawię program, który będzie co 1s zmieniał stad diod. W tym celu skorzystam z obliczeń z poprzedniego przykładu. Schemat będzie taki sam jak ten w przykładzie z poprzedniej części kursu.

#define F_CPU 1000000 //ustawienie oscylatora na 1MHz
#include <avr/io.h> //dołączenie podstawowej biblioteki
#include <avr/interrupt.h> //dołączenie biblioteki z przerwaniami

#define LED PORTC //zdefiniowanie stałych
#define PORTD2 2

char licznik = 0; //zmienna dla licznika programowego


int main()
{
     DDRC = ffh;
     //konfiguracja portu jako wyjścia

     TCCR0 |= (1<<CS00) | (1<<CS01);
     //ustawienie preskalera na 64

     TIMSK |= 1<<TOIE0;
     //włączenie przerwania od przepełnienia licznika

     TCNT0 = 131;
     //ustawienie wartości początkowej

     sei();
     //globalne włączenie przerwań

     while() //pusta pętla
     {

     }
}

SIGNAL(SIG_OVERFLOW0) //początek funkcji obsługi przerwania
{
     licznik++; //zwiększenie o 1

     if(licznik > 125) //sprawdzamy, czy nie minęło już 125 przepełnień
     {
          PORTC = ~PORTC; //zmiana stanu na porcie c
          licznik = 0; //wyzerowanie licznika
     }

     TCNT0 = 131; //ustawienie wartości początkowej

}

Program ten nie był sprawdzany, więc nie gwarantuje działania w pełni. Komentarze chyba mówią wystarczająco dokładnie o tym co będzie robił kontroler.

Wydaje mi się, że to już wszystkie podstawowe zagadnienia związane z licznikiem 0 w atmega8. W następnej części omówię licznik/czasomierz 1, którego możliwości już są o wiele większe, za czym też idzie stopień skomplikowania układu.

Komentarze do „Mikrokontrolery AVR część 6 – timer0

  1. „Następnie mamy rejestr TCNT0 i w nim dzielimy przez 125, czyli co 125 impulsów będziemy mieli przepełnienie.”

    A po co się dzieli przez 125? I dlaczego właśnie 125?

    • Te 125 to wartość liczbowa, która określa przez ile ma być podzielona częstotliwość idąca z preskalera. Jest to wartość wyliczona na potrzeby przykładu, tak aby po wszystkich etapach dzielenia częstotliwości uzyskać częstotliwość o okresie 1s.

  2. TCNT0 = 131; /ustawienie wartości początkowej zmień na TCNT0 = 131; //ustawienie wartości początkowej

    będzie lepiej 😉

    • Niedawno miałem awarie na stronie i straciłem część grafiki i powoli w wolnych chwilach zamieniam obrazki na lepsze, postaram się to zrobić dzisiaj jeżeli zaszła taka potrzeba.

  3. Witam, mógłbyś bardziej się wypowiedzieć na temat obliczania czasu przepełnienia rejestru TNCTx
    dlaczego 1000 000/64/125 = 125 dzielisz częstotliwość pracy naszego ukontrolera przez preskaler 64 (a nie 256 lub 1024 lub 8), dlaczego dzielisz przez 125, na podstawie czego obliczyłeś 125?

    • Tak jak pisałem w artykule, musimy tak podzielić częstotliwość taktowania procesora, aby na koniec wyszła nam liczba całkowita, a możliwie jak najmniejsza.
      Mamy dwie możliwości dzielenia tej częstotliwości. Jeden to preskaler, który umożliwia podział przez zdefiniowane wartości. A drugim sposobem jest rejestr TCNTn, do którego możemy wpisywać dowolną wartość z przedziału od 0 do 255. Preskaler i rejestr TCNTn są połączone w szereg.
      Sygnał na wyjściu rejestru TCNTn pojawia się w chwili, kiedy jego wartość przekroczy 255, czyli się przepełni. Ilość impulsów po których rejestr wygeneruje sygnał przepełnienia możemy zmieniać, poprzez wpisanie do tego rejestru wartości początkowej.

      Co do samego obliczania, to sprawa wymaga trochę wprawy, najlepiej jest policzyć kilka razy dla różnych ustawień prescalera, a później ocenić, który daje najbardziej zadowalający efekt.

      W przykładzie, który opisywałem w artykule mieliśmy częstotliwość taktowania 1MHz, czyli 1 milion impulsów na sekundę. Interesuje nas odnierzanie 1s, czyli najlepiej było by podzielić tą częstotliwość przez milion. Ale to rzadko jest możliwe.

      Moim zdaniem najbardziej optymalnym ustawieniem preskalera jest dzielenie przez 64, czyli
      1000000/64 = 15625
      Otrzymaliśmy liczbę całkowitą. Teraz naszym zadaniem jest dobranie takiego dzielnika dla rejestru TCNTn, aby otrzymać jak najmniejszą wartość całkowitą. Na tym etapie, po prostu trzeba wymyślić co by to mogło być, polecam pobawić się kalkulatorem wpisując różne możliwości. Można zaobserwować, że wartość 15625 jest drugą potęgą liczby 125, która jest całkowita, mała i mieści się pomiędzy 0, a 255.
      Aby obliczyć wartość początkową dla rejestru licznika należy wykonać obliczenie:
      256 – 125 = 131

      Pozostaje tutaj kwestia tych 125 impulsów jakie zostają. Kontroler 125 razy na sekundę będzie generował przerwanie od przepełnienia licznika. W tym przerwaniu musimy wykonać licznik programowy, który opisałem w artykule.

  4. Udało mi się przeczytać twój kurs i rzeczywiście jest on dobrym kursem którego język mi odpowiada. Nawiasem mówiąc nie zgadzam się tylko z jednym stwierdzeniem – że jest to kurs „do poduszki” spróbowałem i noc miałem do tyłu bo mnie wciągnęło.
    Pozdrawiam serdecznie x.Piotrek

  5. Witam, czy zamiast char licznik nie powinno być int licznik (lub inna zmienna typu Integer)? Wydaje mi się, że zmienna typu char przechowuje znak, a nie cyfrę. więc obawiam się, że ten program się nie skompiluje.

    Pozdrawiam.

    • Zmienna typu int jest zmienną 16 bitową, a w tym przypadku nie potrzebujemy większej niż 8 bitów, więc można zastosować typ char, który jest 8 bitowy. Z kompilacją problemu nie ma, bo to czy ta zmienna przechowuje znak, czy wartość zależy tylko od naszej interpretacji. W zmiennej typu char przechowuje się numer znaku w tabeli ASCII, a to nadal jest liczba. Taki zabieg w celu zmniejszenia zużycia pamięci.

  6. Z mojej strony ponowna uwaga, podobna do tej którą zamieściłem w części 5.
    U mnie zamiast
    SIGNAL(SIG_OVERFLOW0)
    działa
    ISR(TIMER0_OVF_vect)

    • W rezultacie dzielenia musimy uzyskać liczbę całkowitą, więc trzeba wymyślić taką liczbę która spełni ten warunek. Po podziale w preskalerze otrzymujemy
      1000000/64 = 15625
      i to jest ilość impulsów które wpadają na licznik. teraz musimy ustalić ile tych impulsów ma licznik zliczyć zanim się przepełni. Mamy rejestr 8 bitowy, czy najwięcej może być to 256, ale taka wartość nie da nam liczby całkowitej po podziale 15625, więc musimy wybrać coś innego. Okazuje się, że liczba 125 nadaje się idealnie i taki podział daje nam liczbę całkowitą. Te 125 to liczba wymyślona, trzeba ją dobrać metodą prób i błędów wklepując różne dzielenia w kalkulator. Jeżeli nie otrzymalibyśmy liczby całkowitej na końcu to licznik nie odmierzał by nam równych odstępów czasu

Skomentuj Paweł Janik Anuluj pisanie odpowiedzi

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *

Witryna wykorzystuje Akismet, aby ograniczyć spam. Dowiedz się więcej jak przetwarzane są dane komentarzy.