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


von Oliver D. (Gast)


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 :)

von Oliver D. (Gast)


Lesenswert?

Ich mein natürlich auf TF0 eine Null ausgeben

von Ralf A. (warpnine)


Lesenswert?

Hallo,

das geht so nicht. Erstmal die Begründung:
1
for(;;)
2
{...}
Das ist okay so, kannst auch while(1) {...} schreiben (nur als Info, ist 
vielleicht besser lesbar)
1
TMOD=0x01;
Ist auch okay, Timer 0 als 16-Bit Timer, kein Gate-Betrieb, Timer 1 
gestoppt.
1
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.
1
TR0=1;
Timer 0 starten, das ist auch okay.
1
while(TF0==0)
2
  {
3
    leds=0xFF;
4
  }
5
  leds=0x00;
6
}
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:
1
void main(void) {
2
3
  TR0 = 0;             //Timer 0 stoppen                        *1
4
5
  TCON &= 0xF0;        //Timer 0 konfigurieren                  *2
6
  TCON |= 0x01;
7
8
  TF0 = 0;             //Timer 0 Interrupt-Flag löschen         *3
9
10
  TH0 = TL0 = 0;       //Timer 0 Zählregister laden             *4
11
12
  TR0 = 1;             //Timer 0 starten
13
14
  while(1) {           //Endlosschleife
15
    if(TF0 == 1) {     //Abfrage Timer abgelaufen
16
      TF0 = 0;         //Wenn ja, dann Interrupt-Flag löschen   *5
17
      LED = ~LED;      //LEDs invertieren                       *6
18
    }
19
  }
20
}

*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

von jack (Gast)


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.

von jack (Gast)


Lesenswert?

Berichtigung:

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

von Scotty (Gast)


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

von Oliver D. (Gast)


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?

von jack (Gast)


Lesenswert?

Ja, ein einziges Mal.

von jack (Gast)


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.

von Oliver D. (Gast)


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?

von Matthias (Gast)


Lesenswert?

so z.B. für f=11.0592MHz:
1
// Testprogramm TIMER
2
// Beispiel: LED blinkt im o.5s Takt
3
4
#include "reg51.h"   // Register des 8051MCs
5
#define LED P0_7
6
7
// Timer 0 - Zeitbasis
8
// Timer0: Reload Wert für T0 T=1/(fosz/12)*(65536-(TH0*256+TL0))
9
// RELOAD (16bit) = -(0.01 * FREQ_OSC / 12 - 65536);
10
// RELOAD-Wert für 10ms = 0xDC00 (56320) bei FOSZ 11.059200 MHz
11
#define TH0Reload 0xDC
12
#define TL0Reload 0x00
13
14
static unsigned char int_delay = 0;
15
16
// ********************************************************************
17
// Timer 0 Interrupt
18
// Routine wird alle 10ms aufgerufen
19
// ********************************************************************
20
void TIMER0ISR (void) interrupt 1 using 1 {
21
  TH0  = TH0Reload; // Update MSB
22
  TL0  = TL0Reload; // Update LSB
23
  int_delay++;
24
  if (int_delay == 50) {
25
    // LED blinkt im Sekundentakt (500ms an, 500ms aus)
26
    // nur Bsp., anpassen!
27
    LED = !LED;
28
    int_delay = 0;
29
  }
30
}
31
32
// ********************************************************************
33
// Grund-Initialisierung
34
// ********************************************************************
35
void init (void) {
36
  // Timer 0 initialisieren
37
  TMOD = 0x01;          // 0000 0001B    Timer 0: Mode 1 (16-Bit Zähler)
38
  TH0  = TH0Reload;   // Reloadwert Timer 0
39
  TL0  = TL0Reload;
40
  TR0  = 1;             // Timer 0 starten
41
  // Interruptsystem
42
  ET0  = 1;             // Timer 0, Interruptfreigabe
43
  EA   = 1;             // generelle Interruptfreigabe
44
}
45
46
// ********************************************************************
47
// Hauptprogramm
48
// ********************************************************************
49
void main (void) {
50
  init();
51
  while(1);
52
}

von Oliver D. (Gast)


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.

von Oliver D. (Gast)


Lesenswert?

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

von Matthias (Gast)


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.

von Oliver D. (Gast)


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

von Matthias (Gast)


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

von Oliver D. (highspeed-oliver)


Lesenswert?

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

von Peter D. (peda)


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

von Nils-k. M. (nils3012)


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

von Matthias K. (matthiask)


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.

von Nils-k. M. (nils3012)


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.

von Peter D. (peda)


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.
1
#include <reg51.h>
2
3
#define u8              unsigned char
4
#define u16             unsigned short
5
#define LOW(x)          (x & 0xFF)
6
#define HIGH(x)         (x >> 8)
7
8
9
#define XTAL            11.0592e6
10
11
#define T_1MS           (u16)(XTAL / 12.0 * 1e-3)
12
13
14
void delay_ms( u16 val )        // delay 1 ... 65536ms
15
{
16
  do{
17
    TR0 = 0;
18
    TF0 = 0;
19
    TMOD = 0x01;                // T0 = 16 Bit
20
    TL0 = LOW(-T_1MS);
21
    TH0 = HIGH(-T_1MS);
22
    TR0 = 1;
23
    while( TF0 == 0 );
24
  }while( --val );
25
}


Peter

von Nils-k. M. (nils3012)


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

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.