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:
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?
@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.
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.
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.
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.
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 :)
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! ;-)
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)"
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
unsignedintdelaycount;// Globaler Delaycounter
6
7
8
ISR(TIMER0_OVF_vect)
9
{
10
delaycount++;
11
}
12
13
voiddelay()
14
{
15
delaycount=0;
16
while(delaycount<31250)
17
}
18
19
intmain(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
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 ';'
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.
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 ';'):