PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : Anfänger bittet um Hilfe zur Timerprogrammierung in C



muetzi
03.11.2005, 14:36
Hallo Leute!

Bin noch Neuling bei der µC Programmierung, gehe zwar in die
Abendschule, aber dort wird uns aus Zeitmangel einfach zu wenig
erklärt.

Mein Problem:

Das Prog ist für einen 50c517a geschrieben, typisches Anfängerprog
halt.

Ich glaub, ich kapier grundsätzlich nicht, wie ein Timer arbeitet.
Das mit den Initialisierungen ist mir klar, die Erzeugung der Zeitbasis von 1s auch, aber warum blinken die Leds an Port1 1s lang, 1s nicht, 1s wieder,...

Wäre nett, wenn ihr für einen Anfänger mal wieder etwas Gedult
aufbringen könntet!

Danke im voraus,

Gruß Jörn

SprinterSB
03.11.2005, 16:12
Die Interrupt Service Routine isr_**** schlägt alle 250µs zu.

teiler zählt immer von 0 bis 3 und bewirkt dadurch im Endeffekt eine softwaremässige Verviefachung dieser Zeit auf 4*250µs = 1ms.
Das gleiche für tsek mit einem Faktor 1000.
Dadurch wird P1 = ~P1 ; nur 1x pro Sekunde aufgerufen.
~ steht für 'not', d.h. alle Bits an dem Port werden umgedreht, wodurch die LED blinkt im 1s - Takt, also mit 1/2 Hz.

tsek und teiler sind übrigens keine lokalen Variablen, sondern mindestens im ganzen Modul bekannt.

Initialisieren kann man die auch so:

char teiler = 0;
int tsek = 0;

dadurch spart man etwas Programmcode. Dann müssen auf der rechten Seite der Initialisierung allerdings Konstanten stehen.

muetzi
03.11.2005, 16:57
Hallo Georg.

Danke erst mal für die Antwort!

Also, wenn ich das richtig verstanden habe, läuft das Ganze so ab.

Der Timer löst alle 250µs die ISR aus. (wegen Auto-Reload).
Die ISR-Funktion zählt bis eine 1s hoch, dann wird P1 invertiert. (Wert 0xFF, nach 1s 0x00, usw.).
Der Timer schickt zwar seinen Interrupt alle 250µs, aber so lange die Bedingungen für teiler und tsek nicht erfüllt sind, wird die Funktion nicht verlassen.
Die 250µs werden in der Variable teiler gespeichert, und vervierfacht.

Oder???????

Vielleicht könntest du mir ja noch mal bei meinem Gedankensprung helfen!
Der Lehrer hatte erwähnt, dass der Timer so programmiert wird, das er alle 1s ein Interrupt erzeugt und die ISR alle Sek. aufgerufen wird.
Aber das geht ja gar nicht, nicht einmal mit einem 16 Bit Timer.

Danke nochmal im voraus,

Gruß Jörn

SprinterSB
03.11.2005, 17:18
[...] aber so lange die Bedingungen für teiler und tsek nicht erfüllt sind, wird die Funktion nicht verlassen.
Die 250µs werden in der Variable teiler gespeichert, und vervierfacht.

Oder???????

Fast. Verlassen wird die ISR schon, aber das äussere if wird nur jedes 4. mal ausgeführt und das innere if nur 1 mal pro 4000=4*1000 IRQs. Wird vielleicht klarer, wenn du anders einrückst:

void isr_t0(void) interrupt 1
{
teiler++;

if (teiler == 4) // 4 x 250µsek = 1mS
{
teiler = 0;

tsek++;

if (tsek == 1000)
{
tsek = 0;

P1 = ~P1;
}
}
}



Der Lehrer hatte erwähnt, dass der Timer so programmiert wird, das er alle 1s ein Interrupt erzeugt und die ISR alle Sek. aufgerufen wird.
Aber das geht ja gar nicht, nicht einmal mit einem 16 Bit Timer.

Da hast du recht. Unter Umständen bekommt man 1 Interrupt pro Sekunde hin, aber das ist abhängig vom µC und dessen Takt.

Mit Verlaub, was der Herr Lehrer da erzähl ist Käse. Jedenfalls bei der Quelle. Tritt ihm nächstens ans Schienbein, wenn er so'n Schotter erzählt.

Sandro
03.11.2005, 17:26
Zur grundsätzlichen Funktion des Timers:

Ist TR0 gestzt wird das Timerregister bei jedem Maschinentakt um eins
erhöht. Es wird also der Maschinentakt gezählt.
Im Modus2 wird das 16-Bit Timerregister in zwei 8-Bit Register geteilt.
Das Lowbyte wird dann als Zählregister benutzt. Der Wert im Highbyte
wird bei einem Überlauf des Zählregisters in dieses geladen.
Mehr macht der Timer eigentlich nicht.

In deinem Program wird ein Interupt benutzt.
Ein Interupt ist eine Programunterbrechung. Ist die entsprechende
Bedingung erfüllt wird eine bestimmte Stelle im Programspeicher
angesprungen. Diese Stellen nennen sich Interuptvectoren und befinden
sich am Anfang des Speichers. Um Interupts zuzulassen ist es nötig
das Bit EAL zu setzen. Mit setzen des ET0 Bit wird der Interupt des
Timer0 aktiviert.
In deinem Fall wird bei jedem Unterlauf des Timer0 der entsprechende
Vector angesprungen und der Code zum erzeugen der Zeitbasis
ausgeführt.

Eine gute Anlaufstelle ist die Seite von Erik Buchmann (http://wwwiti.cs.uni-magdeburg.de/~buchmann/privat/index.htm).
Sie behandelt zwar den AT89c2051, das kann dir aber egal sein. Alle
8051 Controller haben den selben Befehlssatz.

Sandro
03.11.2005, 17:32
Stammt das Program von deinem Lehrer?
Die Wahl des Reloadwerts ist nämlich äuserst ungünstig.
Alle 6 Maschinentakte wird ein Interupt ausgelöst. Wenn man jetzt bedenkt
das viele Befehle des 8051 mehr als einen Takt benötigen werden
unweigerlich Interuptanforderungen verloren gehen.
Auserdem hat der Controller dadurch keine Zeit um überhaupt noch etwas
anderes zu machen.

SprinterSB
03.11.2005, 17:51
Ich kenn den µC nicht, aber im Kommentar steht was von 'Reload durch Überlauf', nicht durch 'Unterlauf'. Demnach sollte das Ding hochzählen? Ist aber womöglich auch nicht korrekt kommentiert.

Sandro
03.11.2005, 18:29
Da war ich jetzt etwas vorschnell. Du hast recht. Demnach stimmt meine
Aussage nicht. Wie kann mir den sowas passieren? Werd das gleich ändern.
schäm

03.11.2005, 22:27
Hallo an alle!
Danke nochmal für die zahlreichen Antworten, is halt nicht so einfach als Anfänger und für euch dieses Thema sicherlich schon ein alter Hut!

Die CPU hat (fast) den gleichen Befehlssatz wie die anderen 8051,
wenn man sie mit 12Mhz taktet, hat man einen Maschinenzyklus von 1µs.
Der Timer zählt hoch und geht dann in den ÜBERLAUF.

Wegen meinem Lehrer:
Ich weiß, es gibt auch Lehrer die Schrott erzählen, aber das ganze stand nicht als Lehrstz im Manuskript, sondern Aufgabe war, P1 mit einer Blinkfrequenz von 1Hz mit Hilfe von Timer0 zu realisieren.
Es war als Ansatz geschrieben, und wir sollen anhand der Manuals rausfinden, wie es geht, um selber draufzukommen, das der Timer nur bis 256µs hochzählen kann.

Jetz hab ich auf jeden Fall den Programmablauf verstanden, deswegen braucht man bei Timer2 (16-Bit-Timer) auch keine Variable teiler, weil dieser eine Zeitbasis von 65536µs besitzt und man gleich eine zeitl. Verzögerung von 1ms erzeugen kann. (nur 1x if-Bed. bis 1000).

Aber mit einem 80x86 geht das mit Interrupt/ sek. sicher!!!

Eine Frage hätt noch bitte: welchen Zweck erfüllt eigentlich die Endlosschleife while(1); im main().
Als ich Sie weggelassen hatte, funktionierte nichts mehr.
Meine Vermutung ist, dass die Timer-Initalisierungen damit unendlich oft ausgeführt werden können??? Aber ob das stimmt?

Danke nochmal im voraus,

Gruß, Jörn

muetzi
03.11.2005, 22:38
Nachtrag:
Der letzte Beitrag stammt auch von mir, war nur nicht eingeloggt!

Jörn

muetzi
03.11.2005, 23:46
Aja, noch eins bitte.

@sprinter:

wegen der if-Bedingung:

Könnte man nicht gleich schreiben:

void ist_t0(void) interrupt 1
{

tsek++;

if(tsek==4000)
{
tsek=0;
P1=~P1;
}

}

Danke nochmal,

Gruß Jörn

04.11.2005, 05:02
Ich kapier einfach nicht, warum derjenig die Schleifen ineinander verschachtelt.

Jörn

SprinterSB
04.11.2005, 07:47
[...] sondern Aufgabe war, P1 mit einer Blinkfrequenz von 1Hz mit Hilfe von Timer0 zu realisieren.

In diesem Fall, muss P1 ne halbe Sekunke LOW sein, und ne halbe Sekunde HIGH. Das gibt ne Frequenz von 1Hz. Nach 1 Sekunde wiederholt sich dann alles. Wenn P1 1 Sek LOW ist etc, dann hast du ne Frequenz von 1/2 Hz. *haarespalt*


welchen Zweck erfüllt eigentlich die Endlosschleife while(1); im main().
Als ich Sie weggelassen hatte, funktionierte nichts mehr.
Meine Vermutung ist, dass die Timer-Initalisierungen damit unendlich oft ausgeführt werden können??? Aber ob das stimmt?
Zweck davon ist, daß man main() nicht verlässt. Wo würde man dann landen?

Das mit if (tsek==4000) stimmt auch.

04.11.2005, 08:15
Hallo Sprinter,

danke erst mal für deine Geduld!

Stimmt, das mit der Frequenz 1 Hz stimmt, weil in diesem Fall ja 1 Periode ne Sek. dauert!!!
Ja, ja , die lieben Grundlagen der Elektrotechnik/ Elektronik!

Mir rotieren schon mittlerweile die Leds mit ein paar MHz im Hirn!

wegen while(1); dann war meine Theorie richtig. Ohne while machts gleich einen RET und landet keine Ahnung wo.

Aber könnt ich dich trotzdem noch mit einer Frage quälen?!

Kannst du mir einen Grund nennen, warum der Programmierer eine verschachtelte if-Bedingung nimmt, man könnte die Zeitverzögerung ja auch anders programmieren.
Hat das vielleicht den Sinn, das nicht unnötig Prozessorzeit vernichtet wird, oder so???

Falls du mir dies noch beantworten könntest, DANKE!

Gruß, Jörn

PS. Dann wäre mein Problemchen vollständig gelöst!!

muetzi
04.11.2005, 08:18
Sorry, schon wieder nicht eingeloggt, das Forum haut mich immer nach einer gewissen Zeit raus.

Jörn

SprinterSB
04.11.2005, 09:10
Als mit vernichten von Prozessorzeit hat das nix zu tun. In einer ISR ist das eher ungünstig, und was die Codegröße angeht ist es auch schlechter als mit nur einem if.
Hat wohl didaktische Gründe. Erst mal auf eine ms und dann auf eine Sekunde. So kann man auch 1000s lang warten, ohne gruß was zu ändern. Mit nur 1 Variablen würd die überlaufen. Ich vermute mal, der Compiler arbeitet mit 16-bit-ints. Möglich wären auch 32 bit, ist aber eher unwahrscheinlich. Wie groß ein int werden kann siehst du in der Doku oder mit sizeof(int), das die die Anzahl der bytes gibt, die der Typ belegt.
Bit 16 bit ist
0x8000 = -32768 <= int <= 32767 = 0x7fff
, damit kann man nicht so weit zählen, und mit char schon gar nicht
0x80 = -128 <= char <= 127 = 0x7f

Als Zählvariablen nummt man übrigens besser unsigned, wegen

0 <= unsigned int <= 65535 = 0xffff
0 <= unsigned char <= 255 = 0xff

Wenn man mit einem Zähler eine Zeit von 10s warten wollte, würde man auf 40000 vergleichen. Ein guter Compiler meckert so was an, mit 'comparison always false/true due to limited range of data type' oder so. Mit unsigned short würd es noch passen.

Bluehorn
23.02.2006, 09:12
Als mit vernichten von Prozessorzeit hat das nix zu tun. In einer ISR ist das eher ungünstig, und was die Codegröße angeht ist es auch schlechter als mit nur einem if.

Hat wohl didaktische Gründe. Erst mal auf eine ms und dann auf eine Sekunde. So kann man auch 1000s lang warten, ohne gruß was zu ändern.

Ich schliesse mich der Vermutung an, dass es didaktische Gründe hat. Allerdings macht es auch durchaus Sinn, möglichst mit einer 8-Bit Variable (char) abzublocken, schliesslich handelt es sich hier um eine 8-Bit CPU.

Das halbiert dann in etwa die Zeit in der ISR gegenüber einem 16-Bit Zähler. Allerdings wird hier jedes 4.te Mal schon auf den 16-Bit Zähler "durchgeschaltet", insofern bringt diese Optimierung hier recht wenig.

Währe sinniger, eine 8-Bit Variable bis 250 laufen zu lassen und in der inneren Schleife dann bis 16 zu zählen. Dann würde man sogar ohne 16 Bit auskommen und die schnelle 8-Bit Operation wird zu 99,6% ausgeführt statt wie jetzt nur zu 75% der ISR-Aufrufe.