www.mikrocontroller.net

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

Important announcement: there is an English version of this forum on EmbDev.net. Posts you create there will be displayed on Mikrocontroller.net and EmbDev.net.
Autor: Oliver D. (Gast)
Datum:

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:

Ich mein natürlich auf TF0 eine Null ausgeben
Autor: Ralf Altmann (warpnine)
Datum:

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:

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

Berichtigung:

16,5ms ist falsch: muß natürlich 65,5ms heißen.
Autor: Scotty (Gast)
Datum:

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:

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:

Ja, ein einziges Mal.
Autor: jack (Gast)
Datum:

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:

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:

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:

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:

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:

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

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:

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:

Cool vielen dank nochmal.
Klappt echt prima und hat zum Verständnis sehr beigetragen :)
Autor: Peter Dannegger (peda)
Datum:

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:

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:

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:

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:

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:

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 Email-Adresse ist freiwillig. Wenn Sie automatisch per Email ü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




Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder GIF-Format hochladen.
Siehe Bildformate

Mit dem Abschicken erkennst du die Nutzungsbedingungen an.

webmaster@mikrocontroller.netImpressumNutzungsbedingungenWerbung auf Mikrocontroller.net