Forum: Mikrocontroller und Digitale Elektronik 8051 Delay Funktion mit Timer (mal wieder)


von Mathias Z. (matziz198)


Lesenswert?

Hallo,

ich möchte eine variable Delay Funktion für einen LED-Fade-Effekt 
programmieren. Ist also im Timing relativ unkritisch. Ich habe auch 
schon einen Ansatz. Dieser Funktioniert jedoch noch nicht. Kompiler ist 
Keil, µC ist ein AT89C51ED2. Timer 0 läuft im 8 bit Autoreload und 
erzeugt mit dem PCA bereits eine 200 Hz PWM. Diesen Timer (Überlauf alle 
5 ms) möchte ich ebenfalls für die Erzeugung des Delays benutzen.
Egal ob ich den Wert 1 oder 100 der Delay Funktion übergebe, scheint es 
so als ob sie nicht ausgeführt wird.
Über einen Denkanstoß wäre ich dankbar.

Hier die relevanten Stellen des Codes:
1
unsigned int delaycount;  // Globaler Delaycounter
Timer 0 ISR:
1
void Timer_0_ISR(void) interrupt 1 // Interruptvektor = 0x0B
2
{ 
3
  TF0=0;       // Überlaufflag löschen
4
  delaycount++;  // Delay Variable incrementieren
5
}
Delay Funktion:
1
void delay5ms(unsigned int dtime)
2
{
3
  delaycount = 0;
4
  while (delaycount < dtime);
5
}
Funktionsaufruf in der Main zw. den einzelnen Helligkeitswerten
1
delay5ms(10);

von Wilhelm F. (Gast)


Lesenswert?

Schau mal ins Assembler- oder Listing-File, ob die Zählvariable im 
Interrupt nicht wegoptimiert wurde. Das Stichwort dazu heißt volatile 
zur Variablendeklaration.

Nebenbei am Rande: Muß bei Timer0 eigentlich TF0 glöscht werden, macht 
der das nicht automatisch?

von Mathias Z. (matziz198)


Lesenswert?

volatile hilft leider nicht.

TF0 muss im Timer Mode 2 von Hand gelöscht werden. Das mache ich also in 
der T0 ISR.

Matze

von Wilhelm F. (Gast)


Lesenswert?

@Matze:

Es wäre noch möglich, daß die beiden Stellen sich stören, wo die 
Variable delaycount geändert wird. Was passiert, wenn in der Funktion 
delay bei Nullsetzung gerade ein Timerinterrupt kommt, und da in die 
selbe Variable einen anderen Wert hinein schreibt?

Es könnte sein, muß aber nicht. Du könntest also mal testweise dort, wo 
delaycount auf Null gesetzt wird, vorher den Timerinterrupt sperren, und 
nach dem Befehl wieder frei geben.

von Mathias Z. (matziz198)


Lesenswert?

Danke erstmal für die zügige Antwort,

die Interrupts bei 0 setzen zu deaktivieren hatte ich vorher schon 
probiert. Leider mit negativem Ergebnis. Ich habe auch schon probiert 
die int (delaycount und dtime) durch char zu ersetzen. Aber dann 
funktioniert fast gar nichts mehr.

Die Idee der Delay Funktion habe ich übrigens von dieser Seite:
http://www.edaboard.com/thread49968.html

dort scheint es zu Funktionieren.

von Wilhelm F. (Gast)


Lesenswert?

Mathias Z. schrieb:

> dort scheint es zu Funktionieren.

Das ganze sieht ja auch ordnungsgemäß aus.

Jetzt kenne ich allerdings den AT89C51ED2 nicht so gut, sondern eher den 
Standard-8051, und ein paar Derivate mit exakt diesem Core. Habe mir 
aber mal das Datenblatt angeschaut, und zumindest haben die Timer die 
selbe Clockbasis wie die CPU. Das bedeutet, daß im Autoreload-Mode 
zwischen Timerinterrupts nur maximal 256 Maschinenzyklen vergehen. Oder 
noch weniger, je nachdem, welcher Wert im Reloadregister steht. Aber du 
schreibst, das sei ja in Ordnung.

Die Int-Variable beansprucht davon schon einen guten Teil, und manche 
Befehle brauchen 2 Maschinenzyklen. Die Erhöhung der Zählvariable ist ja 
eine Addition, in der die Werte aus dem RAM gelesen, mit Übertrag 
addiert, und wieder zurückgeschrieben werden. Alles in Assembler- bzw. 
Maschinencode, und das ist weit mehr als die eine Zeile in C. Das können 
eine Menge Befehle mit entsprechender Laufzeit sein, so daß dein 
Programm hauptsächlich sich im Interrupt aufhält, und das Hauptprogramm 
kaum noch oder sehr langsam ausführt. Noch schlimmer als bei internem 
RAM wird es mit Zugriffen auf externes RAM.

Da ich leider nur den kleinen Codeschnipsel kenne, und weder die 
Initialisierung noch das Restprogramm noch dein Equipment an 
Testmöglichkeiten:

Auf einfache Weise hilft es vielleicht, an den kritischen 
Programmstellen mal einen Pin zu toggeln, und mit dem Oszi die Zeiten 
anzuschauen. Z.B. an Anfang und Ende des Interrupts, und in der 
Delayschleife. Oder auch mal einen Blick in die Assemblerfiles zu 
werfen.

von Kernel (Gast)


Lesenswert?

Mathias Z. schrieb:
> Egal ob ich den Wert 1 oder 100 der Delay Funktion übergebe, scheint es
> so als ob sie nicht ausgeführt wird.

Was heißt das genau? Die Funktion delay() wird aufgerufen und kehrt 
sofort zurück?

Laß den Keil ein LIST File erstellen und poste es. Wichtig sind die 
delay(), die ISR und der Teil um den Aufruf aus der main().

Zwei Dinge sind prägnant:
1. volatile für die Zählvariable muss sein!
2. der TIMER könnte schneller laufen als du willst, denn

Mathias Z. schrieb:
Ich habe auch schon probiert
die int (delaycount und dtime) durch char zu ersetzen.

Sollte bei Zahlen < 127 keinen Einfluss haben.

von Mathias Z. (matziz198)


Lesenswert?

Hab den Fehler gefunden. Lag an mir. Arbeite nur ab und zu an dem Code 
und habe zu wenig kommentiert.
Die Delay Funktion funktioniert sehr gut, ich muss nur größere Werte 
eintragen damit man etwas mit dem Auge sehen kann.
Der Timer liefert alle 19,5µs einen Überlauf zur Ansteuerung der 200Hz 
PWM. Übergibt man der Funktion 100 ist es klar das man 1,95ms nicht 
sehen kann.

Danke für Eure Zeit und Mühe, ich gelobe Besserung :)

von Wilhelm F. (Gast)


Lesenswert?

Mathias Z. schrieb:

> Arbeite nur ab und zu an dem Code
> und habe zu wenig kommentiert.

Wenigstens am Rand rechts neben den Code, oder in den Kopf der Funktion 
sollte man ein paar Worte hinein schreiben, was da passiert. Nicht mal 
unbedingt für fremde Personen, sondern für einen selbst. Mir half sowas 
bisher ungemein, weil ich nach 2 Monaten auch mal was vergesse. Mir 
graust es dann vor den eigenen unkommentierten Dingen. Und dann kostet 
es im Nachhinein viel mehr Zeit und Ärger.

Daß die Interruptfrequenzen so hoch sind, dachte ich mir schon fast. Die 
5ms erschienen mir gleich merkwürdig, es sei denn, die Taktfrequenz wäre 
extrem niedrig. Aber schau noch mal nach, ob die Löschung des Flags im 
Interrupt nicht doch unnötig ist. Sie macht den Interrupt kürzer, und 
man hat diese Zeit dann woanders im Programm mehr zur Verfügung. Zwei 
Fliegen mit einer Klappe. Die Hersteller gaben sich ja dort auch schon 
Mühe, um noch etwas mehr Leistung aus der Maschine heraus zu holen.

Denn in meiner Doku zum 8051 steht, daß das Interruptflag nur im 
Timermodus 3 gelöscht werden muß. In den anderen Modes wird es von der 
Hardware gelöscht, also automatisch. Und die 8051-Cores in den 
verschiedenen Derivaten sind ja meist kompatibel zum Standard-8051.

> ich gelobe Besserung :)

Gute Besserung! ;-)

von Kernel (Gast)


Lesenswert?

Mathias Z. schrieb:
> Hab den Fehler gefunden.

Schön, dann hast du jetzt Zeit und kannst ans Optimieren gehen. Aktives 
Warten ist nicht wirklich guter Stil. Wenn du parallel noch Eingaben 
verarbeiten willst, verliert der Nutzer schnell die Freude.

Besser geht es so:
Beitrag "Wartezeiten effektiv (Scheduler)"

von Julian T. (Firma: Stuttgart) (livingevil)


Lesenswert?

Hallo Mathias Z.,

ich habe nach deinem Vorbild versucht eine Warteschleife einzurichten, 
nach dem Motto "warten ist tödlich", allerdings klappt das nicht.

Als Test soll die LED im 1s Takt blinken. Mit der _delay_ms(1000); 
Funktion in der void delay klappt das (mit der avr/delay bib) als Test.

Woran kann es liegen, das mit diesem Code die LED dauerhaft leuchtet? 
Bedeutet das, dass die delay() den Ablauf nicht verzögert?
Verstehe ich das richtig, dass die delay() einmal pro Zyklus aufgerufen 
wird? Dann würde "delaycount=0;" die while Schleife ja nie zum Ende 
kommen lassen - oder?

1
#include <avr/io.h>
2
#include <avr/interrupt.h>
3
#include <avr/pgmspace.h>
4
5
unsigned int delaycount;  // Globaler Delaycounter
6
7
8
ISR(TIMER0_OVF_vect)
9
{
10
  delaycount++;  
11
}
12
13
void delay()
14
{    
15
    delaycount=0;
16
    while (delaycount<31250)
17
}
18
19
int main(void)
20
{
21
  
22
  //  DDRD = 0b10000000;                      // Setup PB0 as output
23
24
  DDRD=0b10000000;  // PWM output on PD6/
25
  
26
  
27
  TCCR0= (1<<CS00);          // Start Timer 0 with no prescaler
28
  TIMSK= (1<<TOIE0);                // Enable Timer 0 overflow interrupt
29
30
  sei();                            // Set the I-bit in SREG
31
32
  for(;;){PORTD^=_BV(PD7);delay();}                         // Endless loop;
33
  // main() will never be left
34
35
  return 0;                         // This line will never be executed
36
37
}

von Karl H. (kbuchegg)


Lesenswert?

Julian T. schrieb:

> unsigned int delaycount;  // Globaler Delaycounter

volatile

FAQ: Was hat es mit volatile auf sich


> void delay()
> {
>     delaycount=0;
>     while (delaycount<31250)
> }

jeder von einem nicht Anfänger programmierte Compiler der letztehn 15 
Jahre optimiert dir diese Warteschleife gnadenlos weg. Im verlinkten 
Artikel steht auch wieso.

Edit: ausserdem compiliert das nicht. Da fehlt ein ';'

: Bearbeitet durch User
von Peter D. (peda)


Lesenswert?

Mathias Z. schrieb:
> Der Timer liefert alle 19,5µs einen Überlauf zur Ansteuerung der 200Hz
> PWM.

Das ist sportlich, der MC ist da schon sehr ausgelastet.
Bei einem 12MHz/12 Takt wären das nur 19,5 CPU-Zyklen.
Welchen Quarz und welchen CPU-Teiler benutzt Du denn?

Den 16Bit delaycount mußt Du im Main auch noch atomar zugreifen.

von Julian T. (Firma: Stuttgart) (livingevil)


Lesenswert?

Karl H. schrieb:
> volatile
>
> FAQ: Was hat es mit volatile auf sich
>
>> void delay()
>> {
>>     delaycount=0;
>>     while (delaycount<31250)
>> }
>
> jeder von einem nicht Anfänger programmierte Compiler der letztehn 15
> Jahre optimiert dir diese Warteschleife gnadenlos weg. Im verlinkten
> Artikel steht auch wieso.

Vielen Dank für den Hinweis auf den Artikel - da wäre ich nie drauf 
gekommen - mir war nicht bewusst, dass der Compiler so "stark" 
eingreift.

Für andere Interessenten, die eine LED im Sekunden Takt blinken lassen 
wollen, ohne _delay_ms(1000), hier der Code (mit fehlendem ';'):
1
#include <avr/io.h>
2
#include <avr/interrupt.h>
3
#include <avr/pgmspace.h>
4
5
volatile uint16_t delaycount;  // Globaler Delaycounter
6
7
8
ISR(TIMER0_OVF_vect)
9
{
10
  delaycount++;  
11
}
12
13
void delay()
14
{    
15
    delaycount=0;
16
    while (delaycount<=31250);
17
}
18
19
int main(void)
20
{
21
  DDRD=0b10000000;  // PWM output on PD7/
22
  
23
  
24
  TCCR0= (1<<CS00);          // Start Timer 0 with no prescaler
25
  TIMSK= (1<<TOIE0);                // Enable Timer 0 overflow interrupt
26
27
  sei();                            // Set the I-bit in SREG
28
29
  for(;;){PORTD^=_BV(PD7);delay();}                         // Endless loop;
30
  // main() will never be left
31
32
  return 0;                         // This line will never be executed
33
34
}

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.