Forum: Mikrocontroller und Digitale Elektronik Signal für eine gewisse Zeit setzen in C (8051)


von Bastian (Gast)


Lesenswert?

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

von Papa (Gast)


Lesenswert?

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.

von Bastian (Gast)


Lesenswert?

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.

von Otto (Gast)


Lesenswert?

Du setzt einen Zähler auf 40,
dekrementierst diesen Wert jede Sekunde
und schaltest bei Erreichen von 0 ab.

Otto

von Papa (Gast)


Lesenswert?

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.

von Bernhard S. (b_spitzer)


Lesenswert?

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

von Bernhard S. (b_spitzer)


Lesenswert?

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

von Bernhard S. (b_spitzer)


Lesenswert?

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

von Papa (Gast)


Lesenswert?

Hehe, damit sollts ja wohl klappen.

von Kai (Gast)


Lesenswert?

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
Noch kein Account? Hier anmelden.