Hallo Leute, ich möchte gern in C für eine gewisse Zeit einen Port über I2C ansteuern. Die Ansteuerung an sich ist kein Problem, verstehe mich nur nicht mit Interrupts, Timern u.ä. Wie kann ich z.B. für 40s über "i2C_setbyte" einen Zustand setzen und ihn danach wieder auf "LOW" setzen? Vielen Dank im Voraus. MfG, Bastian
Bastian schrieb: > Hallo Leute, > > ich möchte gern in C für eine gewisse Zeit einen Port über I2C > ansteuern. Die Ansteuerung an sich ist kein Problem, verstehe mich nur > nicht mit Interrupts, Timern u.ä. > > Wie kann ich z.B. für 40s über "i2C_setbyte" einen Zustand setzen und > ihn danach wieder auf "LOW" setzen? > > Vielen Dank im Voraus. > > MfG, > Bastian 1. i2C_setbyte(1); 2. 40s "warten" oder was anderes tun 3. i2c-setbyte(0); Um so eine Wartezeit zu generieren nimmt man üblicherweise einen Timer. Wenn du nicht weißt wie der funktioniert solltest du das hier im Tutorial durchlesen. Andererseits kann man auch eine Warteschleife erstellen, nur wäre dann der Controller für 40 s in dieser gefangen.
Vielen Dank erst einmal für Deine Antwort. Das Problem ist, dass der Controller kontinuierlich Werte vom A/D-Umsetzer aufnimmt (natürlich auch innerhalb dieser 40s). Es muss also unabhängig von der Messwertaufnahme funktionieren. Nach 40s soll der Baustein, den ich über I2C anspreche, für eine gewisse Zeit deaktiviert werden, um dann nach einer bestimmten Zeit wieder eingeschaltet zu werden. Im Vordergrund läuft grundsätzlich die Messwertaufnahme.
Du setzt einen Zähler auf 40, dekrementierst diesen Wert jede Sekunde und schaltest bei Erreichen von 0 ab. Otto
Dann wäre die einfachste Lösung vermutlich einen Timer so einzustellen, dass dieser nach 40 Sekunden einen Interrupt auslöst, während derer du dann immer noch deine Messwerte zwischenspeichern könntest. Die ganzen Grundlagen dazu stehen im Tutorial. Also zumindest die Prinzipien, da es ja eigentlich für AVRs gedacht ist.
Bitte nicht das AVR-Tutorial lesen, wenn Du einen 8051 programmieren musst. Da passen die ganzen Register und Einstellungen überhaupt nicht. Timer beim 8051 nimmst Du am Anfang nur Timer0/1 im 16-Bit Mode. TMOD = 0x11; setzt beide Timer in den Mode. Nur wenn du serielle Schnittstelle verwendest, dann brauchst Du evtl. den Timer 1 für die Baudrate. Wenn Du keinen Startwert und keinen Reload-Wert setzt, dann läuft der Timer 65536 Maschinenzyklen (also bei 12MHz ohne X2-Mode 65,5ms). Bei jedem Timerüberlauf musst Du das Timer-Flag wieder löschen (entfällt im Interrupt-Betrieb, aber erstmal ohne) und zählst eine Variable hoch. Nimm mal unsigned int Ticks; Bei jedem Überlauf wird Ticks erhöht und wenn die benötigte Anzahl (=Zeit in s / 65,5ms) erreicht ist, machst Du was und setzt Ticks wieder auf 0.
1 | TMOD = 0x11; |
2 | //TH0 = (65536 - Benoetigte_Zeit)/256; // für Startwert oder Reload
|
3 | //TL0 = (65536 - Benoetigte_Zeit)%256; // für Startwert oder Reload
|
4 | TR0 = 1; // Timer 0 starten - TR1 für Timer 1 |
5 | |
6 | while(1) // Endlosschleife |
7 | {
|
8 | if (TF0 == 1) // Timer 0 ist überglaufen |
9 | { Ticks++; // sonst braucht man erstmal nix. |
10 | TF0 = 0; // Flag löschen |
11 | }
|
12 | if (Ticks == 0) |
13 | { i2c_setbyte(1); //einschalten -> wird mehrfach ausgeführt |
14 | // oder du fügst ein Merker-Flag ein
|
15 | }
|
16 | if (Ticks == Ausschaltzeit) |
17 | { i2c_setbyte(0); // ausschalten -> wird nur einmal ausgeführt |
18 | Ticks = 0; // wenn Ticks gelöscht wird |
19 | }
|
20 | }
|
Damit kannst Du zunächst einen Vorgang realisieren. Wenn Du die Ticks einfach weiterlaufen lässt, dann kann man auch sowas machen:
1 | if (Taster1 == 1) |
2 | { Ausschaltzeit1 = Ticks + Dauer1; //zur aktuellen Zeit die Dauer addieren |
3 | Einschalten(1); |
4 | }
|
5 | if (Ticks == Ausschaltzeit1) |
6 | { Ausschalten(1); |
7 | }
|
8 | if (Taster2 == 1) |
9 | { Ausschaltzeit2 = Ticks + Dauer2; //zur aktuellen Zeit die Dauer addieren |
10 | Einschalten(2); |
11 | }
|
12 | if (Ticks == Ausschaltzeit2) |
13 | { Ausschalten(2); |
14 | }
|
Das funktioniert mit beliebig vielen Signalen und es ist auch kein Problem, wenn es einen Überlauf gibt. Wenn Ticks z.B. auf 65000 steht und du willst eine Dauer von 60 Sekunden (= 60000ms/65,536ms = 915 Ticks), dann ergibt die Addition für die Ausschaltzeit 379 (gleicher Datentyp für alle Schaltzeiten!). tschuessle Bernhard
Teil 2: jetzt mit Reload für kurze Zeiten ========================================= Die Timer zählen nach dem Überlauf sofort ab 0x0000 weiter. Also muss man wieder einen Startwert (Reload) einstellen, so dass der Timer nach einer kürzeren Zeit überläuft. Die Formeln im Quelltext kann man problemlos mit Konstanten füllen, die Division und Modulo wird dabei nicht wirklich berechnet, sondern der Compiler fügt die Ergebniswerte in den Code ein. (anders sieht es für variable Zeiten aus, dazu später mehr)
1 | TH0 = (65536 - Benoetigte_Zeit)/256; // für Startwert oder Reload |
2 | TL0 = (65536 - Benoetigte_Zeit)%256; // für Startwert oder Reload |
Die Benoetigte_Zeit ist ein Wert in Maschinenzyklen (je nach System, oft aber 1µs). Für eine gewünschte Timer-Zeit von 1ms schreibt man dann also einfach
1 | TH0 = (65536 - 1000)/256; // 1ms = 1000MZ, davon das High-Byte in TH0 |
2 | TL0 = (65536 - 1000)%256; // das Low-Byte in TL0 |
Da der 8051 bei den Timern 0 und 1 keine getrennten Zähl- und Reload-Register hat, muss man diesen Wert nach jedem Überlauf möglichst schnell wieder in die Register schreiben (daher nimmt man da dann meist Interrupts).
1 | while(1) // Endlosschleife |
2 | {
|
3 | if (TF0 == 1) // Timer 0 ist überglaufen |
4 | { TH0 = (65536 - 1000)/256; // 1ms = 1000MZ, Hih-Byte |
5 | TL0 = (65536 - 1000)%256; // Low-Byte in TL0 |
6 | TF0 = 0; // Flag löschen |
7 | Ticks++; // sonst braucht man erstmal nix. |
8 | }
|
9 | // Rest des Programms
|
Bei einer Millisekunde läuft die Variable Ticks bereits nach 65536ms, also etwa einer Minute über. Wenn man längere Zeiten benötigt, dann entweder die Ticks alle 10, 20 oder 50ms einstellen oder (in C ja kein echtes Problem) unsigned long Ticks; verwenden. Damit kannst Du in ms-Auflösung Zeiten bis 1193 Stunden erzeugen, bevor der Zähler überläuft... Braucht aber mehr Speicher (auch für die Schaltzeiten!). Ohne Interrupts kann es aber passieren, dass die Bearbeitung des restlichen Programms länger als die Timer-Zeit dauert. Dann werden die Zeiten sehr ungenau. Also erstmal nicht wundern... Teil 3: Reload mit variablen Zeiten =================================== Für variable Zeiten (z.B. PWM für Modellbau-Servo mit 1-2ms Impuls und 20ms Periodendauer) darf man die (16Bit) Variablen nicht einfach in die obige Zeile einsetzen. Dabei wird sonst die Division und die Modulo-Operation wirklich ausgeführt (die sich ergebenden Zeiten sind dann für die Tonne...). Hier muss man (am besten eine Phase vorher, wenn der Timer gerade übergelaufen war) die Variable berechnen.
1 | while(1) // Endlosschleife |
2 | {
|
3 | if (TF0 == 1) // Timer 0 ist überglaufen |
4 | { TH0 = (65536 - Pulszeit)/256; // Hih-Byte |
5 | TL0 = (65536 - Pulszeit)%256; // Low-Byte in TL0 |
6 | TF0 = 0; // Flag löschen |
7 | Ticks++; // sonst braucht man erstmal nix. |
8 | Pulszeit = 1000 + 4 * Dip_Schalter; // ergibt 1-2ms |
9 | }
|
10 | // Rest des Programms
|
tschuessle Bernhard
Teil 4: Timer-Interrupt ohne Schrecken ====================================== Mit Interrupt vermeidet man die Ungenauigkeiten beim Timer-Reload. Sobald der Timer bei aktiviertem Interrupt überläuft, springt der Prozessor in die Interrupt-Service-Routine und führt den dortigen Code aus. Danach geht es an der alten Stelle im Hauptprogramm weiter. (der sollte daher möglichst kurz sein) Interrupts aktiviert man erstmal einzeln für jede Quelle, danach noch den Hauptschalter EA.
1 | TMOD = 0x11; |
2 | TH0 = (65536 - 1000)/256; // für Startwert |
3 | TL0 = (65536 - 1000)%256; // für Startwert |
4 | TR0 = 1; // Timer 0 starten - TR1 für Timer 1 |
5 | ET0 = 1; // Interrupt für Timer 0 aktivieren |
6 | EA = 1; // Globalen Interrupt aktivieren -- ab jetzt geht's rund |
die Interrupt-Service-Routine ersetzt die if(TF0)-Abfrage aus dem Hauptprogramm. Ist kein Hexenwerk, es kommt nur ein Schlüsselwort und eine Nummer, die die Quelle angibt.
1 | void ISR_Timer0 (void) interrupt 1 |
2 | {
|
3 | TH0 = (65536 - 1000)/256; // Reload |
4 | TL0 = (65536 - 1000)%256; // Reload |
5 | // TF0 = 0; // Flag löschen geht jetzt automatisch! Der reine Luxus!
|
6 | Ticks++; // sonst braucht man erstmal nix. |
7 | }
|
Ticks muss jetzt als globale Variable deklariert werden, damit sowohl die ISR als auch main() darauf zugreifen können. Erbsenzähler rechnen natürlich noch aus, wieviele µs es bis zum Relaod dauert und korrigieren damit den Reload-Wert:
1 | // wenn es z.B. 6us bis zum Sprung in die ISR dauert:
|
2 | TH0 = (65536 - 1000 + 6)/256; // korrigiert um 6us |
3 | TL0 = (65536 - 1000 + 6)%256; // korrigiert |
Es ist aber nicht immer möglich, exakt auf 1µs genau zu arbeiten (ein laufender ASM-Befehl kann nicht unterbrochen werden und dauert 1, 2 oder 4 MZ). Aber zuviel überlegungen nutzen eh nichts, weil der Quarztakt ja auch nicht exakt ist. (eine Uhr mit Timer-Interrupt läuft aber trotzdem mit einer Abweichung von unter 1s/Woche) tschuessle Bernhard
Zum Timer-Interrupt eines 8051 helfen vielleicht auch diese Videos: http://et-tutorials.de/4458/timer-interrupt-des-8051-mikrocontrollers/
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.