mikrocontroller.net

Forum: Mikrocontroller und Digitale Elektronik Einfacher Timer in C für 8051er AT89S52


Autor: Oliver D. (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hallo,

nach langer recherche auf 8052.com und im Datenblatt bin ich nun soweit, 
euch mein erstes Stück Code zu präsentieren, was hoffentlich als echter 
funktionierend Timer zu betrachten ist. Ich bitte euch es anzuschauen 
und mir zu sagen ob es so richtig ist.
Es soll ein Internausgelöster 16 Bit timer sein. Ich hoffe das er 
automatisch bei 0 Anfängt zu zählen und bei 65536 aufhört zu zählen um 
dann überzulaufen. Dabei soll er mir auf TF1 eine 1 ausgeben.

# include <reg51.h>
sfr leds=0x80; //Ich habe low current Leds von den Ports auf VCC gelegt.
               // Sie sind also low active.
void main (void)
{
for(;;)
{
TMOD=0x01;
EA=1;

TR0=1;

while(TF0==0)
             {
             leds=0xFF;
             }
     leds=0x00;
}

Mir ist wohl bewusst, das der Zeitraum sehr sehr kurz ist und das es 
vielleicht erscheint als ob die LEDs durchgehend leuchten. Das könnte 
ich ja dann noch mit einer Zählschleife ändern...
Mir gehts nur erstmal um den Timer und seinen korrekten Aufbau.


Danke schonmal :)

Autor: Oliver D. (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Ich mein natürlich auf TF0 eine Null ausgeben

Autor: Ralf Altmann (warpnine)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hallo,

das geht so nicht. Erstmal die Begründung:
for(;;)
{...}
Das ist okay so, kannst auch while(1) {...} schreiben (nur als Info, ist 
vielleicht besser lesbar)
TMOD=0x01;
Ist auch okay, Timer 0 als 16-Bit Timer, kein Gate-Betrieb, Timer 1 
gestoppt.
EA=1;
Bringt nichts, weil du zwei Sachen nicht beachtet hast:
1. Du hast im Register IE (Interrupt Enable) den Timer 0-Interrupt nicht 
freigeschaltet (Bit ET0)
2. Wenn du wirklich im Interrupt-Betrieb arbeiten willst, brauchst du 
auch eine Interrupt-Service-Routine.

Da du wohl Anfänger bist, schlage ich vor, dass du die Interrupt-Sache 
erstmal weglässt, und im sogenannten Polling-Betrieb arbeitest. Das 
bedeutet, der Interrupt wird nicht automatisch abgearbeitet, sondern du 
frägst einfach das Interrupt-Flag des Timer 0 (TF0) ab, ob es gesetzt 
ist. Dies passiert bei aktivem Timer auch dann, wenn der Interrupt nicht 
freigeschaltet ist. Wenn es gesetzt ist, dann lief der Timer über. Das 
Flag muss dann natürlich auch wieder gelöscht werden, sonst würde dein 
Programm denken, dass der Timer gleich wieder übergelaufen ist.

Wie das aussieht, siehst du nachher in der Lösung.
TR0=1;
Timer 0 starten, das ist auch okay.
while(TF0==0)
  {
    leds=0xFF;
  }
  leds=0x00;
}
Hier beginnt schon das oben genannte Problem, dass dein Programm denken 
wird, dass der Timer schon wieder übergelaufen ist, weil du TF0 nicht 
löschst. Der Effekt ist jetzt einfach der, dass direkt nach dem 
Einschalten der Spannung die LEDs aus sind, und nach Ablauf des Timers 
die LEDs angehen.

Ich schlage daher vor, dass du ein Programm machst, welches die LEDs 
togglen lässt, also ein, aus, ein, aus,..., dann siehst du, ob dein 
Programm wirklich funktioniert.

Also hier die Lösung:
void main(void) {

  TR0 = 0;             //Timer 0 stoppen                        *1

  TCON &= 0xF0;        //Timer 0 konfigurieren                  *2
  TCON |= 0x01;

  TF0 = 0;             //Timer 0 Interrupt-Flag löschen         *3

  TH0 = TL0 = 0;       //Timer 0 Zählregister laden             *4

  TR0 = 1;             //Timer 0 starten

  while(1) {           //Endlosschleife
    if(TF0 == 1) {     //Abfrage Timer abgelaufen
      TF0 = 0;         //Wenn ja, dann Interrupt-Flag löschen   *5
      LED = ~LED;      //LEDs invertieren                       *6
    }
  }
}

*1: Ist nach einem Reset nicht nötig, da das Flag automatisch 0 ist. Ich 
mache es trotzdem immer, da es ja mal sein kann, dass der Timer z.B. 
erst für eine kurze Zeitgenerierung benutzt wird, danach für eine lange 
(nur als Beispiel). Deswegen schreibe ich immer nach dem gleichen 
Ablauf, Timer stoppen, konfigurieren, starten. Nötig wird das aber erst 
bei größeren Programmen.

*2: Durch das Verunden mit 0xF0 setze ich alle relevanten Flags für 
Timer 0 auf 0, ohne die Flags von Timer 1 zu beeinflussen. Durch das 
Verodern setze ich alle relevanten Flags auf 1.

*3: Siehe (*1), das Flag könnte durch vorhergehende Verwendung gesetzt 
sein.

*4: Auch siehe (*1) bzw. (*3), auf die Art bin ich sicher, dass ich 
ALLES WICHTIGE zum Timer 0 konfiguriert habe.

*5: Das sollte mittlerweile klar sein :-)

*6: So siehst du wirklich, ob dein Programm funktioniert, da bei jedem 
Überlauf des Timers die LEDs an- bzw. ausgehen.

Ich übernehm jetzt erstmal keine Garantie für die Funktion des 
Programms, ist schon spät grins

Probiers mal aus, und sag Bescheid, ich geh jetzt erstmal in die Heia 
:-)

Scotty

Autor: jack (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
@Scotty

Wenn Du Timer0 als 16-Bit Timer verwenden willst, muß TMOD auf 0x01
gesetzt werden, nicht TCON (enthält TF0 und TR0).

Außerdem ist ein Timerdurchlauf zu kurz: Bei einem 12MHz-Quarz dauert
er 16,5 ms. Man muß also doch in einer ISR eine Variable runterzählen,
um dann die LEDs zu invertieren.

Autor: jack (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Berichtigung:

16,5ms ist falsch: muß natürlich 65,5ms heißen.

Autor: Scotty (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Ah, sorry, stimmt natürlich, TMOD. Das kommt davon, wenn man morgens um 
eins noch vor der Kiste hockt :-)

Okay, die Zeit hab ich mir nicht ausgerechnet, weil ich eh nicht wusste, 
mit welcher Frequenz gearbeitet wird. Sicher ist das mit einer Variablen 
noch geschickter, aber ich wollte Oli nicht gleich überlasten.

Scotty

Autor: Oliver D. (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hallo nochmal.

Also ich werde es gleich mal ausprobieren....Allerdings nochmal zu der 
Zeit.

Da einmal Überlauf ja nur wenige ms sind, muss ich quasi mehrfach einen 
Überlauf haben um einmal ein bzw. auszuschalten.
Musst das zwingend per Interrupt Routine gemacht werden?
Beim Überlauf erhalte ich ja eine 1 in TF0. Folglich könnte ich doch 
einfach
if (TF0==1)
{ variable++
}

if(variable==10)
{
...leds ein aus...
}

würde das dann so funktionieren?

Autor: jack (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Ja, ein einziges Mal.

Autor: jack (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Es ist doch viel besser, beim Überlauf in die ISR zu springen und
dort die Zählerei zu erledigen und die Variable zurückzusetzen.

Autor: Oliver D. (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Alles klar.

Nur wie sieht die Interrupt routine aus?
Und wie bekomme ich den Rückgabe Wert zurück in die Hauptfunktion?


Und warum soll das einfacher sein?

Autor: Matthias (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
so z.B. für f=11.0592MHz:
// Testprogramm TIMER
// Beispiel: LED blinkt im o.5s Takt

#include "reg51.h"   // Register des 8051MCs
#define LED P0_7

// Timer 0 - Zeitbasis
// Timer0: Reload Wert für T0 T=1/(fosz/12)*(65536-(TH0*256+TL0))
// RELOAD (16bit) = -(0.01 * FREQ_OSC / 12 - 65536);
// RELOAD-Wert für 10ms = 0xDC00 (56320) bei FOSZ 11.059200 MHz
#define TH0Reload 0xDC
#define TL0Reload 0x00

static unsigned char int_delay = 0;

// ********************************************************************
// Timer 0 Interrupt
// Routine wird alle 10ms aufgerufen
// ********************************************************************
void TIMER0ISR (void) interrupt 1 using 1 {
  TH0  = TH0Reload; // Update MSB
  TL0  = TL0Reload; // Update LSB
  int_delay++;
  if (int_delay == 50) {
    // LED blinkt im Sekundentakt (500ms an, 500ms aus)
    // nur Bsp., anpassen!
    LED = !LED;
    int_delay = 0;
  }
}

// ********************************************************************
// Grund-Initialisierung
// ********************************************************************
void init (void) {
  // Timer 0 initialisieren
  TMOD = 0x01;          // 0000 0001B    Timer 0: Mode 1 (16-Bit Zähler)
  TH0  = TH0Reload;   // Reloadwert Timer 0
  TL0  = TL0Reload;
  TR0  = 1;             // Timer 0 starten
  // Interruptsystem
  ET0  = 1;             // Timer 0, Interruptfreigabe
  EA   = 1;             // generelle Interruptfreigabe
}

// ********************************************************************
// Hauptprogramm
// ********************************************************************
void main (void) {
  init();
  while(1);
}

Autor: Oliver D. (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Ein paar kurze fragen ;)

static unsigned char. Ist das eine Variable die Global für alle 
Funktionen gilt?


void TIMER0ISR (void) interrupt 1 using 1.
TIMER0ISR ist frei gewählt als funktionsname, nicht wahr?
Interrupt 1 using 1. Ist das ebenfalls frei gewählt oder erfüllt das 
eine Funktion.
In der Schule haben wir leider noch nicht mit Interrupt gearbeitet.

Autor: Oliver D. (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Also erstmal schonmal vielen Dank. Es funktioniert Prima und ich bin
sehr erleichtert etwas zu haben, an dem ich rumexperimentieren kann.

Autor: Matthias (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
> static unsigned char. Ist das eine Variable die Global für alle
> Funktionen gilt?
richtig, sofern diese am Anfang, also nicht innerhalb einer Funktion 
deklariert wird. Das STATIC kann man hier auch weglassen.

>void TIMER0ISR (void) interrupt 1 using 1.
>TIMER0ISR ist frei gewählt als funktionsname, nicht wahr?
richtig

>Interrupt 1
Nicht frei wählbar. Hier muss man die Syntax des jeweiligen C-Compilers 
beachten. Die Angabe funktioniert so z.B. mit Keil und SDCC 
(TIMER0-Interrupt)

>using 1. Ist das ebenfalls frei gewählt oder erfüllt das
>eine Funktion.
dto. nicht frei wählbar, auch hier die Compiler-Spezifik beachten, 
Angabe bedeutet, dass die "Registerbank 1" des 8051 für die 
Interruptroutine benutzt werden soll. Könnte auch 1,2 oder 3 sein. 
"Registerbank 0" wird üblicherweise vom Hauptprogramm verwendet und 
sollte bei Interruptroutinen gemieden werden.

Autor: Oliver D. (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Alles klar.

Noch ne frage.
"void TIMER0ISR (void) interrupt 1 using 1".
Zu Interrupt 1. Muss ich diesen bei dem software Timer deshalb wählen,
weil dieser Bit 1 ist?
Denn meinen externen Interrupt INT0 kann ich per interrupt 0 verwenden.
Folglich sollte dann ja der INT1 (externe interrupt1) über interrupt 2 
anzusprechen sein, oder?

Dabei habe ich mich an diese Tabelle gehalten, dem IE SFR

Bit  Name  Bit Address  Explanation of Function
7  EA  AFh  Global Interrupt Enable/Disable
6  -  AEh  Undefined
5  -  ADh  Undefined
4  ES  ACh  Enable Serial Interrupt
3  ET1  ABh  Enable Timer 1 Interrupt
2  EX1  AAh  Enable External 1 Interrupt
1  ET0  A9h  Enable Timer 0 Interrupt
0  EX0  A8h  Enable External 0 Interrupt

Autor: Matthias (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Zu den Interruptzuordnungen gibts ne Tabelle in der Beschreibung des 
jeweiligen C-Compileres, Bsp. für klass. 8051 bei KEIL:

Nr. Addr. Interrupt
0  0003h  EXTERNAL 0
1  000Bh  TIMER/COUNTER 0
2  0013h  EXTERNAL 1
3  001Bh  TIMER/COUNTER 1
4  0023h  SERIAL PORT

Beim KEIL-Compiler ist die Zuordnung hier beschrieben:
http://www.keil.com/appnotes/files/apnt_103.pdf

Autor: Oliver D. (highspeed-oliver)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Cool vielen dank nochmal.
Klappt echt prima und hat zum Verständnis sehr beigetragen :)

Autor: Peter Dannegger (peda)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Matthias wrote:

>>using 1. Ist das ebenfalls frei gewählt oder erfüllt das
>>eine Funktion.
> dto. nicht frei wählbar, auch hier die Compiler-Spezifik beachten,
> Angabe bedeutet, dass die "Registerbank 1" des 8051 für die
> Interruptroutine benutzt werden soll. Könnte auch 1,2 oder 3 sein.
> "Registerbank 0" wird üblicherweise vom Hauptprogramm verwendet und
> sollte bei Interruptroutinen gemieden werden.

Als Anfänger sollte man "using" garnicht benutzen!

Es klaut erstmal komplette 8Byte SRAM und bringt bei kurzen 
Interrupthandlern garnichts.

Im Gegenteil, es kann sogar gefährlich werden, wenn man im 
Interrupthandler Unterfunktionen benutzt oder Interruptprioritäten!

Man muß sich ja nicht unnötig zusätzliche Fallgruben ins Programm 
basteln.


Peter

Autor: Nils-k. Meinberg (nils3012)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hallo bin auch änfänger in Sachen MC und hätte da mal ein frage an euch, 
ich habe die Aufgabe bekommen (Ausbildung)



alle 1sec die Roten LED´s anzuschalten
alle 3 sec alle Gelben LED´s
und alle 5 sec alle grünen LeD´s

und das OHNE Interrupts ,nur mit SBits.....



Bin total am verzweifeln weil ich diese Woche nen Referat halten muss 
darüber .


Könnt ihr mir bitte helfen????


MFG


Achja ganz vergessen , es handelt sich um den MC 8051

Autor: Matthias K. (matthiask)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Wird hier nicht gern gesehen, noch dazu wenn man einen 2 Jahre alten 
Beitrag ausgräbt. Hausaufgabenlösungen gibts von uns nicht um jeden 
Preis. Du musst wenigstens vorab uns Deinen Lösungsansatz präsentieren.

Autor: Nils-k. Meinberg (nils3012)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Ok sorry tut mir leid wollte keinen neuen Thread aufmachen...

Lösungsansatz:
sbit Rot1=P0^0;
sbit Rot2=P0^3;
sbit Rot3=P0^6;
sbit Rot4=P0^7;
sbit Gruen1=P0^2;
sbit Gruen2=P0^5;
sbit Gelb1=P0^1
sbit Gelb2=P0^4;

Void main(void)
{
P0=0 //Alle Aus
char i,durchgang;

TMOD=TMOD&0xF0;
TMOD=TMOD|0x01;
TR0=0;
TF0=0;
durchgang=0;
do
{
       for(i=0;i<100;i++)  // höchste wert als schleifenende
{      durchgang=durchgang+1;
       //Timer0 wieder starten
        TL0=0xB0;
        TH0=0x03;     //maximal überlauf in 50ms
        TR0=1;        //Zeitgeber Starten

do{}while(TF0==0)   //warten bis überlauf Bit gesetzt
select(durchgang)
{
   case 20:Rot1=1;Rot2=1;Rot3=1;Rot4=1;break;
   case 60:Gelb1=1;Gelb2=1;Gelb3=1;break;
   case 100:Gruen1=1;Gruen2=1;break;
}
   TR0=0;
   TF0=0;
    }
   if (durchgang==100)
    durchgang=0;    //wieder von vorne zählen
}
  while(1)
}


-------------------------------------
das mit den Zeiten versteh ich auch noch nicht so ganz auf meinen 
unterlagen sind wiedersprüchliche Werte.
zb habe ich bei 50ms einen wert von TLi = B0 und THI = 3C errechnet,
B0 passt ja aber 0x03 passt nicht .....

Danke falls wer hilft.

Autor: Peter Dannegger (peda)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Nils-k. Meinberg schrieb:
> zb habe ich bei 50ms einen wert von TLi = B0 und THI = 3C errechnet,

Nö, da wird doch nix gerechnet.
Ein C-Programmierer ist zu faul zum Rechnen, er überläßt es dem 
Compiler.

Ein C-Compiler muß alle konstanten Ausdrücke bereits zur Compilezeit 
ausrechnen und trägt diese Werte dann in den Assemblercode ein.

#include <reg51.h>

#define u8              unsigned char
#define u16             unsigned short
#define LOW(x)          (x & 0xFF)
#define HIGH(x)         (x >> 8)


#define XTAL            11.0592e6

#define T_1MS           (u16)(XTAL / 12.0 * 1e-3)


void delay_ms( u16 val )        // delay 1 ... 65536ms
{
  do{
    TR0 = 0;
    TF0 = 0;
    TMOD = 0x01;                // T0 = 16 Bit
    TL0 = LOW(-T_1MS);
    TH0 = HIGH(-T_1MS);
    TR0 = 1;
    while( TF0 == 0 );
  }while( --val );
}


Peter

Autor: Nils-k. Meinberg (nils3012)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Erstmal Danke,

sowas ist für uns denke ich , ein bisschen zu hoch gegriffen,
wir haben zur Übung eine Tabelle bekommen wo wir die Werte ausrechnen 
sollten :

z.B. 60ms = 65536(Höchster Wert 0xFFFF) - (Minus) 60000(60ms) ergibt

       5536 ----> das in Hex ergibt THi = 15   TLi = A0

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [avrasm]AVR-Assembler-Code[/avrasm]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.
Hinweis: der ursprüngliche Beitrag ist mehr als 6 Monate alt.
Bitte hier nur auf die ursprüngliche Frage antworten,
für neue Fragen einen neuen Beitrag erstellen.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.