Jak tworzyć opóźnienia w programach?

Podstawę realizacji każdej funkcji czasowej w programie jest częstotliwość sygnału zegarowego. Jest ona ściśle związana z tzw. cyklem maszynowym. Standardowy 8051 dzieli częstotliwość wejściową sygnału zegarowego przez 12, jednak można się spotkać z różnymi rozwiązaniami. Na przykład 89C51RD2 może dzielić ją przez 6, DS89C430 przez 2 a AduC816 mnoży częstotliwość kwarcu 32768 Hz. Za każdym razem niezbędne wskazówki można znaleźć w karcie katalogowej. Np. zastosowano mikrokontroler AT89S8252 wyposażony w rezonator 3,6864 MHz. Dzieli on częstotliwość zegara przez 12 i dlatego CPU taktowane jest częstotliwością 3,6864 MHz/12=307200 Hz. Cykl zegarowy jest równy 1/3,6864 MHz=0,271 μs, a cykl maszynowy jest równy 12× cykl zegarowy, to jest 3,26 μs. Cykl maszynowy wyznacza szybkość realizacji rozkazów przez CPU mikrokontrolera. W praktyce najczęściej stosowane są dwie metody realizacji opóźnień w programie: zgrubna, wykorzystująca pętle i inne rozkazy w celu wytworzenia opóźnienia i dokładna, wykorzystująca do pomiaru czasu Timer.

Zgrubne odmierzanie czasu

Jak wspomniałem wcześniej, CPU marnuje czas. Najprostszą metodą jest uwikłanie go w wykonywanie pętli, w której znajdą się jakieś mniej lub więcej czasochłonne zadania. Trzeba jednak uważać, bo zbyt obszerna pętla jest trudna do analizy i przez to równie trudno będzie oszacować czas jej realizacji. Trzeba będzie zdać się na pomiary przy pomocy chociażby stopera i wielokrotne próby albo też zgrubny pomiar czasu przez symulator programowy. To są metody niejako związane z innymi narzędziami, a jak oszacować czas wykonywania pętli bez ich użycia? Niezbędna będzie lista rozkazów asemblera 8051, w której umieszczona będzie również informacja na temat tego, ile cykli maszynowych zajmuje realizacja danego rozkazu.
Jeśli używamy kompilatora RC-51 firmy Raisonance, to po skompilowaniu programu, wybieramy opcję View -> Listing from compiler. Wyświetli się program źródłowy w języku asembler. Na listingu należy odszukać fragment odpowiadający funkcji Delay. Teraz trzeba policzyć ręcznie, ile cykli maszynowych zajmie realizacja poszczególnych poleceń pętli. Wymagana jest tutaj chociażby pobieżna znajomość asemblera i metod programowania, ponieważ nie wszystkie instrukcje są jednakowo ważne dla realizowanego opóźnienia. Mniej zaawansowani być może powinni zmierzyć czas za pomocą symulatora.

Dokładne odmierzanie czasu

Dokładne (z dokładnością do pojedynczego cyklu maszynowego) odmierzanie czasu jest możliwe z wykorzystaniem Timera. Na listingu podano przykład funkcji realizującej opóźnienie wykorzystującej Timer 1 pracujący w trybie 16 bitowym. W związku z tym, że do mikrokontrolera doprowadzony jest sygnał zegarowy o częstotliwości 3,6864 MHz a pojemność Timera wynosi 16 bitów, najdłuższy odmierzany czas, można wyznaczyć z wyrażenia: 12×65536/3,6864 MHz i wynosi on nieco więcej, niż 0,2 sekundy. Dla przypomnienia ponownie wyliczymy częstotliwość wynikającą z wewnętrznego podziału i będącą podstawą do wyznaczenia cyklu maszynowego: 3,6864 MHz12=307200 Hz. Można zauważyć, że jeśli uda się częstotliwość tę podzielić przez 30720 (a jest to możliwe, ponieważ pojemność Timera przekracza tę wartość), to w łatwy sposób można uzyskać 10 Hz, czyli 0,1 sekundy. Timer 1 ustawiany jest w trybie Timera 16-bitowego. Zrobiłem to w funkcji main() tak, aby nie tracić czasu na nastawy w przy realizacji opóźnienia i nie wprowadzać dodatkowego błędu odmierzania czasu mimo, iż łatwo jest go skompensować.

/* prosty program demonstracyjny „mrugająca dioda”
   dioda LED dołączona do portu P0.0, stan aktywny=L
   rezonator kwarcowy 3,6864 MHz */

#include <reg51.h>         //dołączenie definicji rejestrów mikrokontrolera
sbit PortLED = P0^0;       //definicja portu diody LED
//opóźnienie około 1 sekundy dla kwarcu 3,6864 MHz
void Delay(unsigned int time)
{
     unsigned char i;
     for (i=0; i<10; i++)  //pętla wykonywana 10x, podstawą czasu jest 0,1s
     {
         TH1 = 0x87;       //wpisanie wartości do rejestrów Timera 1
         TL0 = 0;
         TR1 = 1;          //uruchomienie Timera 1
         while (!TF1);     //oczekiwanie na ustawienie flagi przepełnienia
         TF1 = 0;          //zerowanie znacznika przepełnienia Timera 1
     }
     TR1 = 0;              //zatrzymanie Timera 1
}

//początek programu głównego
void main(void)
{
     TMOD = 0x10;          //Timer 1 w trybie 16 bitowym
     while (1)             //pętla nieskończona
     {
         PortLED = 1;      //zgaszenie diody LED
         Delay(2);         //opóźnienie około 2 sek.
         PortLED = 0;      //zaświecenie LED
         Delay(2);         //opóźnienie około 2 sek.
     }
}

Odpowiedzi

Wydaje mi się, że w programie

Wydaje mi się, że w programie jest mały błąd, a mianowicie funkcja Delay przyjmuje argument "unsigned int time" ale nigdzie go nie wykorzystuje. Najlogiczniejsze wydawałoby się umieścić zmienną time w forze :
for (i=0; i<10*time; i++)
w ten sposób korzystając z Delay(2) rzeczywiście otrzymamy opóźnienie około dwóch sekund.

Oczywiście, racja!

Oczywiście, racja!

Dodaj nowy komentarz

Zawartość pola nie będzie udostępniana publicznie.