logo elektroda
logo elektroda
X
logo elektroda
REKLAMA
REKLAMA
Adblock/uBlockOrigin/AdGuard mogą powodować znikanie niektórych postów z powodu nowej reguły.

Program licznika w C na 89S52 - zliczanie sekund zatrzymuje się na 9

gruchacha 18 Kwi 2008 00:04 1544 8
REKLAMA
  • #1 5042796
    gruchacha
    Poziom 10  
    Posty: 6
    Ocena: 1
    Witam. Poskładałem i napisałem program, który powinien zliczać sekundy i minuty na przerwaniu. Zaznaczę, że jestem początkującym.
    Program działa w ten sposób:
    - zlicza sekundy do cyfry "9", a następnie wyświetla zero na tym wyświetlaczu, na którym zliczał te sekundy, a reszta wyświetlaczy jest wygaszana. Tak już pozostaje przez nieskończoność.
    Analizowałem to już tyle razy, że nerwica mnie brała, a i tak zawsze było źle, więc proszę o wskazanie błędu. Zapewne nie jest to program ogólnie zbyt dobrze napisany, funkcja "subnumber" jest właściwie zbędna, ale wydawało mi się, że i tak powinno to działać. Z góry dzięki wielkie za pomoc.

    /*wyświetlacz LED dołączony do P0,
    	stan aktywny = L,
    	rezonator kwarcowy 12 MHz
    	
    	seg.A = P0.0
    	seg.B = P0.1
    	seg.C = P0.2
    	seg.D = P0.3
    	seg.E = P0.4
    	seg.F = P0.5
    	seg.G = P0.6
    	seg.dp = P0.7
    	
    
    */
    
    #pragma SMALL								//wybór modelu pamięci
    #include <reg51.h>						//def. rejestrów mikrokontrolera
    
    #define PortLED P0						//tu podłączony jest wyświetlacz LED
    #define PortSW P2							//tranzystory jako kucze
    
    #define _A		0xFE						//def segmentów cyfry
    #define _B		0xFD
    #define _C		0xFB
    #define _D		0xF7
    #define _E		0xEF
    #define _F		0xDF
    #define _G		0xBF
    #define _H		0x7F
    
    #define LED_1	0x80
    #define LED_2	0x40
    #define LED_3	0x20
    #define LED_4	0x10
    #define LED_OFF	0x00
    
    //definicje wyglądu cyfr od 0 do 9
    char code Digits[10] =
    		{	_A & _B & _C & _D & _E & _F,			//0
    			_B & _C,										//1
    			_A & _B & _D & _E & _G,					//2
    			_A & _B & _C & _D & _G,					//3
    			_B & _C & _F & _G,						//4
    			_A & _C & _D & _F & _G,					//5
    			_A & _C & _D & _E & _F & _G,			//6
    			_A & _B & _C,								//7
    			_A & _B & _C & _D & _E & _F & _G,	//8
    			_A & _B & _C & _D & _F & _G };		//9
    
    //opóźnienie około 1 milisek. dla kwarcu 8 MHz
    void Delay(unsigned int time)
    {
    	unsigned int j;
    		while (time-- >= 1) for (j=0; j<60; j++);		 
    }
    
    //funkcja zwraca cyfrę będącą na pozycji POSITION (konwersja HEX na BCD)
    char Subnumber(unsigned int number, char position)
    {
    	switch (position)
    	{
    		case 1:
    			return(number);
    											break;
    		case 2:
    			return(number);
    			
    			break;
    		case 3:
    			return(number);
    			
    			break;
    		case 4:
    			return(number);
    
    			break;
    			default:
    			return(0);
    			break;
    	}
    }
    
    //wyświetlanie
    void Display(unsigned int sekj, unsigned int minj, unsigned int sekd, unsigned int mind)
    {
    	unsigned int loop;
    	for (loop = 0; loop < 90; loop++)
    	{
    		PortLED = Digits[Subnumber(sekj,4)];	//zapis kodu cyfry numer 4 do portu
    		PortSW = LED_4; 									//zalaczenie wyswietlania cyfry 4
    		Delay(5);											//opoznienie ok 20ms
    		PortSW = LED_OFF;									//wylaczenie wyswietlania
    		
    		PortLED = Digits[Subnumber(sekd,3)];	//zapis kodu cyfry numer 3 do portu
    		PortSW = LED_3; 									//zalaczenie wyswietlania cyfry 3
    		Delay(5);											//opoznienie ok 20ms
    		PortSW = LED_OFF;									//wylaczenie wyswietlania
    			
    		PortLED = Digits[Subnumber(minj,2)];	//zapis kodu cyfry numer 2 do portu
    		PortSW = LED_2; 									//zalaczenie wyswietlania cyfry 2
    		Delay(5);											//opoznienie ok 20ms
    		PortSW = LED_OFF;									//wylaczenie wyswietlania
    				
    		PortLED = Digits[Subnumber(mind,1)];	//zapis kodu cyfry numer 1 do portu
    		PortSW = LED_1; 									//zalaczenie wyswietlania cyfry 1
    		Delay(5);											//opoznienie ok 20ms
    		PortSW = LED_OFF;									//wylaczenie wyswietlania
    	}
    }
    
    //przerwanie
    void T0_int(void) interrupt 1 using 1
    {
    	static int i;
    	static int sekje;
    	static int minje;
    	static int sekdz;
    	static int mindz;
    	
    	TH0 = 0x3C;		//zaladowanie do licznika wart. poczatkowej
    	TL0 = 0xAF;
    
    	i++;
    	if(i == 20)		//sek minela
    	{
    		i = 0;
    		sekje++;
    		if(sekje == 10)		
    		{
    			sekje = 0;
    			sekdz++;
    		}
    		if(sekdz == 6)		
    		{
    			sekdz = 0;
    			minje++;
    		}
    		if(minje == 10)		
    		{
    			minje = 0;
    			mindz++;
    		}
    		if(mindz == 6)		
    		{
    			mindz = 0;
    		}
    	}
    
       Display(sekje, minje, sekdz, mindz);
    
    }
    
    //program glowny
    void main(void)
    {
    	TH0 = 0x3C;		//zaladowanie do licznika wart. poczatkowej
    	TL0 = 0xAF;
    	
    	TMOD = 0xF1;	//timer1 16 bit
    	TR0 = 1;			//uruchomienie timera1
    	EA = 1; 			//zezwolenie na przyjmowanie przerwań
    	ET0 = 1;       //zalaczenie przerwan od timer1
    	
    	for(;;);
    } 
  • REKLAMA
  • Pomocny post
    #2 5042904
    przemek20
    Poziom 21  
    Posty: 328
    Pomógł: 41
    Ocena: 25
    Witam.
    Według mnie zasadniczy problem (poza ciekawym zastosowaniem funkcji Subnumber ;) leży w umieszczeniu wyświetlania z delayami w przerwaniu.
    Wyświetlanie trwa dłużej niż czas pomiędzy kolejnymi przerwaniami.
    Najprostszą opcją byłoby przeniesienie wyświetlania do programu głównego na przykład tak:
    
    /*wyświetlacz LED dołączony do P0, 
       stan aktywny = L, 
       rezonator kwarcowy 12 MHz 
        
       seg.A = P0.0 
       seg.B = P0.1 
       seg.C = P0.2 
       seg.D = P0.3 
       seg.E = P0.4 
       seg.F = P0.5 
       seg.G = P0.6 
       seg.dp = P0.7 
        
    
    */ 
    
    #pragma SMALL                        //wybór modelu pamięci 
    #include <reg51.h>                  //def. rejestrów mikrokontrolera 
    
    #define PortLED P0                  //tu podłączony jest wyświetlacz LED 
    #define PortSW P2                     //tranzystory jako kucze 
    
    #define _A      0xFE                  //def segmentów cyfry 
    #define _B      0xFD 
    #define _C      0xFB 
    #define _D      0xF7 
    #define _E      0xEF 
    #define _F      0xDF 
    #define _G      0xBF 
    #define _H      0x7F 
    
    #define LED_1   0x80 
    #define LED_2   0x40 
    #define LED_3   0x20 
    #define LED_4   0x10 
    #define LED_OFF   0x00 
    
    //definicje wyglądu cyfr od 0 do 9 
    char code Digits[10] = 
          {   _A & _B & _C & _D & _E & _F,         //0 
             _B & _C,                              //1 
             _A & _B & _D & _E & _G,               //2 
             _A & _B & _C & _D & _G,               //3 
             _B & _C & _F & _G,                  //4 
             _A & _C & _D & _F & _G,               //5 
             _A & _C & _D & _E & _F & _G,         //6 
             _A & _B & _C,                        //7 
             _A & _B & _C & _D & _E & _F & _G,   //8 
             _A & _B & _C & _D & _F & _G };      //9 
    
    //opóźnienie około 1 milisek. dla kwarcu 8 MHz 
    void Delay(unsigned int time) 
    { 
       unsigned int j; 
          while (time-- >= 1) for (j=0; j<60; j++);       
    } 
    
    unsigned char i=0,sekje=0,sekdz=0,minje=0,mindz=0;
    
    
    //przerwanie 
    void T0_int(void) interrupt 1 using 1 
    { 
       TH0 = 0x3C;      //zaladowanie do licznika wart. poczatkowej 
       TL0 = 0xAF; 
    
       i++; 
       if(i == 20)      //sek minela 
       { 
          i = 0; 
          sekje++; 
          if(sekje == 10)       
          { 
             sekje = 0; 
             sekdz++; 
          } 
          if(sekdz == 6)       
          { 
             sekdz = 0; 
             minje++; 
          } 
          if(minje == 10)       
          { 
             minje = 0; 
             mindz++; 
          } 
          if(mindz == 6)       
          { 
             mindz = 0; 
          } 
       } 
    } 
    
    //program glowny 
    void main(void) 
    { 
       TH0 = 0x3C;      //zaladowanie do licznika wart. poczatkowej 
       TL0 = 0xAF; 
        
       TMOD = 0xF1;   //timer1 16 bit 
       TR0 = 1;         //uruchomienie timera1 
       EA = 1;          //zezwolenie na przyjmowanie przerwań 
       ET0 = 1;       //zalaczenie przerwan od timer1 
        
       for(;;)
       {
    //wyświetlanie
          PortLED = Digits[sekj];   //zapis kodu cyfry numer 4 do portu 
          PortSW = LED_4;                            //zalaczenie wyswietlania cyfry 4 
          Delay(5);                                 //opoznienie ok 20ms 
          PortSW = LED_OFF;                           //wylaczenie wyswietlania 
           
          PortLED = Digits[sekd];   //zapis kodu cyfry numer 3 do portu 
          PortSW = LED_3;                            //zalaczenie wyswietlania cyfry 3 
          Delay(5);                                 //opoznienie ok 20ms 
          PortSW = LED_OFF;                           //wylaczenie wyswietlania 
              
          PortLED = Digits[minj];   //zapis kodu cyfry numer 2 do portu 
          PortSW = LED_2;                            //zalaczenie wyswietlania cyfry 2 
          Delay(5);                                 //opoznienie ok 20ms 
          PortSW = LED_OFF;                           //wylaczenie wyswietlania 
                 
          PortLED = Digits[mind];   //zapis kodu cyfry numer 1 do portu 
          PortSW = LED_1;                            //zalaczenie wyswietlania cyfry 1 
          Delay(5);                                 //opoznienie ok 20ms 
          PortSW = LED_OFF;                           //wylaczenie wyswietlania 
    	}
    }
    

    Nieco trudniejszą opcją byłoby usunięcie delaya i wyświetlanie w przerwaniu zegarowym.
    Pozatym polecam stosowac unsigned char wszedzie gdzie jest to możliwe - zwiększa to szybkość wykonywania programu i zmniejsza ilość kodu wynikowego. Nie należy też bać się zmiennych globalnych ;) . Pozdrawiam
  • REKLAMA
  • #3 5050298
    gruchacha
    Poziom 10  
    Posty: 6
    Ocena: 1
    Dzięki bardzo za pomoc. Programik jest o 500B mniejszy. Właśnie nie wiedziałem jak to zrobić, żeby zmienne "sek" itd. były dostępne wszędzie, a to się okazuje takie proste:D.
    A mam jeszcze pytanie co do dokładności tego programu bo widzę, że w stosunku do mojego stopera w komórce po jakimś czasie widać różnicę. Z czego to wynika? Tylko i wyłącznie z niewystarczająco precyzyjnego kwarcu, czy też program jest na tyle kiepski?
  • #4 5052094
    przemek20
    Poziom 21  
    Posty: 328
    Pomógł: 41
    Ocena: 25
    Tylko i wyłącznie z nieprecyzyjnego kwarcu
    Niestety nie zrobisz w ten sposób aby było lepiej. Mozesz zastosowac PCF8583 i skomunikowac sie z nim przez I2C. On korzysta z kwarcu zegarkowego 32768HZ. Albo zastosować procesor typu ATMega8 lub wyższy (AVR) i podłączyc do niego kwarc zegarkowy.
    Pozdrawiam
  • REKLAMA
  • #5 5052395
    michalko12
    Specjalista - Mikrokontrolery
    Posty: 3394
    Pomógł: 462
    Ocena: 321
    gruchacha napisał:
    Dzięki bardzo za pomoc. Programik jest o 500B mniejszy. Właśnie nie wiedziałem jak to zrobić, żeby zmienne "sek" itd. były dostępne wszędzie, a to się okazuje takie proste:D.
    A mam jeszcze pytanie co do dokładności tego programu bo widzę, że w stosunku do mojego stopera w komórce po jakimś czasie widać różnicę. Z czego to wynika? Tylko i wyłącznie z niewystarczająco precyzyjnego kwarcu, czy też program jest na tyle kiepski?


    Błedem jest zastosowanie tego trybu timera, jesli licznik ma pracować jako precyzyjny czasomierz musi być zastosowany tryb z preskalerem i najlepiej z automatycznym przeładowaniem. Ewenualnie trzeba dokładnie obliczyć jaki jest opóźnienie przy aktualizacji timera i uwzględnić to przy przeładowaniu timera. Na pierwszy rzut oka widać że popełniłes błąd w kolejności zapisu do rejestrów TH0 i TL0 w procedurze obsługi przerwania. Zamieniajac te instrukcje juz zmiejszasz błąd.
  • REKLAMA
  • #6 5057555
    gruchacha
    Poziom 10  
    Posty: 6
    Ocena: 1
    Cytat:
    Na pierwszy rzut oka widać że popełniłes błąd w kolejności zapisu do rejestrów TH0 i TL0 w procedurze obsługi przerwania. Zamieniajac te instrukcje juz zmiejszasz błąd

    Rzeczywiście błąd zmniejszył się o ok. 2s na 12 godzin, podczas gdy bez tej modyfikacji błąd wynosi 9s. no to już jest nieźle;).
    Cytat:
    Błedem jest zastosowanie tego trybu timera, jesli licznik ma pracować jako precyzyjny czasomierz musi być zastosowany tryb z preskalerem i najlepiej z automatycznym przeładowaniem.

    Myślałem że to niemożliwe w przypadku 89S52? Jeżeli się mylę, to w jaki sposób włączyć wymieniony tryb?
    Cytat:
    Albo zastosować procesor typu ATMega8 lub wyższy (AVR) i podłączyc do niego kwarc zegarkowy.

    Przy 89S52 tak niska częstotliwość taktowania odpada?
  • #7 5059151
    michalko12
    Specjalista - Mikrokontrolery
    Posty: 3394
    Pomógł: 462
    Ocena: 321
    gruchacha napisał:
    Cytat:
    Na pierwszy rzut oka widać że popełniłes błąd w kolejności zapisu do rejestrów TH0 i TL0 w procedurze obsługi przerwania. Zamieniajac te instrukcje juz zmiejszasz błąd

    Rzeczywiście błąd zmniejszył się o ok. 2s na 12 godzin, podczas gdy bez tej modyfikacji błąd wynosi 9s. no to już jest nieźle;).
    Cytat:
    Błedem jest zastosowanie tego trybu timera, jesli licznik ma pracować jako precyzyjny czasomierz musi być zastosowany tryb z preskalerem i najlepiej z automatycznym przeładowaniem.

    Myślałem że to niemożliwe w przypadku 89S52? Jeżeli się mylę, to w jaki sposób włączyć wymieniony tryb?


    Pasuje tryb MODE0 przeładowujesz w przerwaniu tylko rejestr TH, TL sobie swobodnie liczy i przez to masz preskaler dzielący przez 32. Daje to czas na bezpieczne przeładowanie TH. W przypadku tego procesora do odmierzania czasu lepiej zastosować do tego TIMER2 w trybie AUTO-RELOAD (DCEN=0)
  • #8 5059415
    kazimi
    Poziom 12  
    Posty: 83
    Pomógł: 3
    Albo takie rozwiązanie. Licznik pracuje w dotychczasowym modzie. Jednakże trzeba pamiętać tu o następującym szczególe: licznik zgłasza żądanie obsługi przerwania, po czym liczy dalej - jednakże zanim żądanie to zostanie obsłużone minie troszeczkę czasu i nawet za dokładnie nie wiadomo ile. I teraz jeżeli wpisujesz jakąś wartość początkową, nadpisujesz to, co do tej pory zliczył licznik. Jak temu zapobiec? Ano bardzo prosto, trzeba to po prostu uwzględnić (czyli po prostu dodać), np. jeżeli chciałbyś wpisać wartość początkową 5233h, to wyglądałoby to następująco:
    ORL TL0, #33 ;mniej znaczący bajt
    MOV TH0, #52: bardziej znaczący bajt

    aaaa to ma być w C:
    TL0 |= 33;
    TH0 = 52;

    czyli powinieneś dopisać tylko jeden znak w odpowiednim miejscu "|" ;]
  • Pomocny post
    #9 5059537
    michalko12
    Specjalista - Mikrokontrolery
    Posty: 3394
    Pomógł: 462
    Ocena: 321
    kazimi napisał:
    (czyli po prostu dodać), np. jeżeli chciałbyś wpisać wartość początkową 5233h, to wyglądałoby to następująco:
    ORL TL0, #33 ;mniej znaczący bajt
    MOV TH0, #52: bardziej znaczący bajt

    aaaa to ma być w C:
    TL0 |= 33;
    TH0 = 52;

    czyli powinieneś dopisać tylko jeden znak w odpowiednim miejscu "|" ;]


    Chyba nie przemyślałeś tego do końca. Orowanie to nie dodawanie. Przy takim rozwiązaniu wynik jest calkiem nieprzewidywalny.

    Dodano po 10 [minuty]:

    Ustaw tryb MODE 0, a procedurę przerwania napisz tak:
    //przerwanie
    void T0_int(void) interrupt 1 using 1
    {
       TH0 = 6;      //zaladowanie do licznika wart. poczatkowej
    
       if(++i == 125)      //sek minela
       {
          i = 0;
          sekje++;
          if(sekje == 10)       
          {
             sekje = 0;
             sekdz++;
          }
          if(sekdz == 6)       
          {
             sekdz = 0;
             minje++;
          }
          if(minje == 10)       
          {
             minje = 0;
             mindz++;
          }
          if(mindz == 6)       
          {
             mindz = 0;
          }
       }
    }
    

Podsumowanie tematu

✨ Dyskusja dotyczy problemu zliczania sekund i minut w programie napisanym w języku C dla mikrokontrolera 89S52, gdzie licznik zatrzymuje się na cyfrze 9. Głównym błędem jest umieszczenie funkcji wyświetlania z opóźnieniami w procedurze przerwania, co powoduje, że wyświetlanie trwa dłużej niż odstęp między przerwaniami. Zaleca się przeniesienie obsługi wyświetlacza do głównej pętli programu. Wskazano również, że dokładność pomiaru czasu jest ograniczona przez precyzję kwarcu rezonatora 12 MHz, a poprawę można uzyskać stosując zewnętrzne układy RTC (np. PCF8583) lub mikrokontrolery AVR z kwarcem zegarkowym 32768 Hz. Wskazano także, że tryb pracy timera powinien wykorzystywać preskaler i automatyczne przeładowanie (np. TIMER2 w trybie AUTO-RELOAD), a w procedurze przerwania należy poprawnie ustawiać rejestry TH0 i TL0, unikając błędów takich jak nadpisywanie licznika bez uwzględnienia jego aktualnej wartości. Przykładowo, tryb MODE0 z odpowiednim załadowaniem wartości początkowej i inkrementacją zmiennych sekund i minut pozwala na poprawne odmierzanie czasu. Dyskusja zawiera fragmenty kodu ilustrujące poprawne podejście do obsługi przerwań i licznika czasu w mikrokontrolerze 89S52.
REKLAMA