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 :)
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
voidmain(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
@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.
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
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?
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?
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.
> 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.
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
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
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
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
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.
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.
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.
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