Elektroda.pl
Elektroda.pl
X
Proszę, dodaj wyjątek dla www.elektroda.pl do Adblock.
Dzięki temu, że oglądasz reklamy, wspierasz portal i użytkowników.

program licznika w C na 89S52 - gdzie błąd

18 Kwi 2008 00:04 1322 8
  • Poziom 2  
    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.

    Code:
    /*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(;;);
    }
  • Pomocny post
    Poziom 20  
    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:
    Code:

    /*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
  • Poziom 2  
    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?
  • Poziom 20  
    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
  • Specjalista - Mikrokontrolery
    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.
  • Poziom 2  
    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?
  • Specjalista - Mikrokontrolery
    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)
  • Poziom 12  
    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
    Specjalista - Mikrokontrolery
    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:
    Code:
    //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;
          }
       }
    }