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 :)
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
@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.
Berichtigung: 16,5ms ist falsch: muß natürlich 65,5ms heißen.
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?
Es ist doch viel besser, beim Überlauf in die ISR zu springen und dort die Zählerei zu erledigen und die Variable zurückzusetzen.
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?
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 | }
|
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.
Also erstmal schonmal vielen Dank. Es funktioniert Prima und ich bin sehr erleichtert etwas zu haben, an dem ich rumexperimentieren kann.
> 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
Cool vielen dank nochmal. Klappt echt prima und hat zum Verständnis sehr beigetragen :)
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.
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
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
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.