Hallo,
ich habe mir die SoftPWM v3 von
http://www.mikrocontroller.net/articles/Soft-PWM gerade etwas angepasst
und sie läuft soweit auch bestens.
Könnte mir aber eventuell jemand erklären, warum die Funktion
pwm_update(), in der main() aufgerufen, 100mal langsamer abgearbeitet
wird, als wenn ich sie z.B. mittels eines zweiten Timers alle 10ms in
dessen ISR aufrufe?
Die main() müßte doch schneller sein?
Schleife für die main():
1
xxx++;
2
SET_PWM(pgm_read_word(lookupTable64+xxx),0,0,0);
3
if(xxx>=63)xxx=0;
Zähler über die ISR: (das Inkrement in der main() fällt dann natürlich
weg!)
1
// Konfiguration von 8-bit Timer/Counter0
2
TCCR2B=(1<<CS22)|(1<<CS20);// Vorteiler:1024
3
TIMSK2|=(1<<TOIE2);// Aktivierung des Timer2-Overflow-Interrupts
Peter K. schrieb:> Könnte mir aber eventuell jemand erklären, warum die Funktion> pwm_update(), in der main() aufgerufen, 100mal langsamer abgearbeitet> wird, als wenn ich sie z.B. mittels eines zweiten Timers alle 10ms in> dessen ISR aufrufe?
Das einfachste ist, wenn du deinem Programm im Simulator über die
Schulter schaust, und guckst, wo er womit die Zeit verbringt.
@ Karl Heinz Buchegger (kbuchegg) (Moderator)
>weil sie, von main aus aufgerufen, dauernd durch die Interrupts>unterbrochen wird, während sie von einer ISR aus aufgerufen durchläuft?
Theoretisch ja, praktisch nein. Denn gerade die 3. Version macht SEHR
wenig Interrupts, das ist ja der Witz. Die Aussage, 100 mal langsamer
ist nicht haltbar, es sei denn, der OP hat sie verschlimmbessert.
Mit VOLLSTÄNDIGEM Code könnte man da was sehen . . .
MFG
Falk
Peter K. schrieb:> Könnte mir aber eventuell jemand erklären, warum die Funktion> pwm_update(), in der main() aufgerufen, 100mal langsamer abgearbeitet> wird
Wie kommst du darauf?
> Die main() müßte doch schneller sein?
Und wie kommst du darauf?
Was auch immer du gemessen hast, scheint der typische "wer mißt, mißt
Mist" gewesen zu sein. Und syntaktisch kaputten Code zu posten, macht
die Sache kein bisschen besser.
XL
Peter K. schrieb:> Also folgendes habe ich in der ISR(in der SoftPWM) geändert:
<schnipp>
Das ist nicht clever. Die ISR wird dadurch deutlich länger und die
Makros zu Beginn des Codes stimmen nicht mehr. Wenn ich das richtig
überschaue, ist die magische Konstante 111 die Anzahl Takte, die die ISR
lang ist. Wenn du den Code der ISR änderst, mußt du das anpassen.
> Und dies ist die Test-Funktion, einmal in ISR(TIMER2_OVF_vect) und in> der main() aufgerufen:
Entschuldige bitte die harten Worte, aber das ist Bullsh*t. Der
Parameter nX wird nicht verwendet, aber dafür eine unbekannte Variable
xxx. Der else-Zweig kann beliebig außerhalb der Tabelle lesen (schau
einfach mal, was für xxx=100 passiert) etc. pp. Wenn dein Compiler eine
Faust machen könnte, dann hättest du jetzt eine blutige Nase ;)
Wir kennen auch SET_PWM() nicht. Und wir wissen auch nicht, wie du
diese Funktion aus main() aufrufst (sicher mit irgendeiner Art von
delay) oder wie du Timer2 eingestellt hast.
Nicht zu vergessen, daß Variablen, die innerhalb einer ISR verändert
werden, als volatile deklariert sein müssen.
> @Axel:>> Ich habe nichts gemessen, sondern einfach die Augen aufgemacht und eine> angeschlossene LED beobachtet.
Und daraus schließt du auf die Ausführungsgeschwindigkeit einer
Funktion? Das einzige was du beobachtest, ist wie oft pro Sekunde die
Funktion zum Ändern der PWM-Werte ausgeführt wird. Und nix anderes.
XL
Hi,
hab's auch grad erst gesehen. Ich hatte die Test-Funktion noch ein paar
mal danach geändert und hab wohl einen älteren Speicherpunkt erwischt.
Vergiss dann einfach diese Funktion - alles andere siehst du, wenn du
fit bist, etwas weiter oben. Dort ist eine dreizeilige Schleifenfunktion
beschrieben, die auch mit xxx arbeitet.
Was SET_PWM() macht, steht auch schon weiter oben:
void SET_PWM(uint16_t data1,uint16_t data2,uint16_t data3,uint16_t
data4)
{
pwm_setting[0] = data1;
pwm_setting[1] = data2;
pwm_setting[2] = data3;
pwm_setting[3] = data4;
pwm_update();
}
Desweiteren deklariere ich in meinem Projekt keine Variablen in einer
ISR, dass macht ausschließlich IHR.^^
LG
@ Peter K. (Gast)
>Die besagte ISR braucht etwas über 70 Takte. Umgeschrieben 2-6 weniger.>Warum ist dies denn so schlimm?
Schlimm ist deine Ignoranz. Poste VOLLSTÄNDIGEN Code.
MfG
Falk
Peter K. schrieb:>Die besagte ISR braucht etwas über 70 Takte. Umgeschrieben 2-6 weniger.
Wohl kaum. Wenn du aus einem simplen
1
PWM_PORT=tmp;
ein
1
if(tmp&0b00000001)PWM1_PORT|=(1<<PWM1);
2
if(tmp&0b00000010)PWM2_PORT|=(1<<PWM2);
3
if(tmp&0b00000100)PWM3_PORT|=(1<<PWM3);
4
if(tmp&0b00001000)PWM4_PORT|=(1<<PWM4);
machst, dann wird der Code nicht kürzer, sondern länger.
>Warum ist dies denn so schlimm?
Weil - wenn die ISR zu lange dauert - der nächste Interrupt verpaßt
wird. Bei genauerer Betrachtung des Codes ist die magische Grenze von
111 nicht die Laufzeit der ISR alleine, sondern die Summe der Laufzeiten
der ISR und der Zeiger-Tausch-Funktion. Bzw. ganz genau die Zeit die
zwischen dem Timer1-Interrupt für die letzte PWM-Phase bis zum sei() in
pwm_update() vergeht, wenn wir annehmen, daß pwm_update() in der
while(pwm_sync == 0); Schleife hängt.
> der Code war schon vollständig gepostet.
Nein, war er nicht. main() fehlte z.B.
Kannst du uns diese Zeile mal erläutern? Und wie hast du eigentlich
F_CPU definiert? Ich komm da im Leben nicht auf 10ms.
[c]
> while(1)> {> xxx++; // für den Vergleich hab ich dieses Inkrement in TIMER2_OVF_vect
verschoben
> SET_PWM(pgm_read_word(lookupTable64+xxx),0,0,0);> if(xxx>=63) xxx=0;> }}
[c]
Dieser Teil fehlte bisher.
Ein wesentlicher Punkt bei der ganzen Sache ist, daß PWM_UPDATE() nur
einmal je PWM-Zyklus laufen kann, weil die Funktion darauf wartet, daß
die PWM-ISR pwm_sync auf 1 setzt. Das passiert genau einmal im
PWM-Zyklus.
Die Endlos-Schleife in main() läuft also ziemlich genau 150 mal pro
Sekunde durch. In der Originalversion wird die LED dabei jedesmal eine
Stufe heller gedimmt, was zu einem Blinken mit 150Hz/63 ~= 2.4Hz führt.
Die Timer2 ISR verändert xxx nun asynchron. Mit welcher Frequenz, hängt
von F_CPU ab (was du uns nicht sagst). Auf jeden Fall kann sich die
Variable zwischen aufeinanderfolgenen Aufrufen von PWM_UPDATE() gleich
mehrfach ändern, was vermutlich nicht ist, was du willst. Dafür kannst
du dir aber häßliche Aliasingeffekte einhandeln.
Ach ja, die Deklaration von xxx fehlt uns auch immer noch. Willst du uns
veralbern? Wenn ich nicht schon so viel getippt hätte, würde ich jetzt
glatt abbrechen...
Schließlich noch:
(du schriebst)
>> Könnte mir aber eventuell jemand erklären, warum die Funktion>> pwm_update(), in der main() aufgerufen, 100mal langsamer abgearbeitet>> wird, als wenn ich sie z.B. mittels eines zweiten Timers alle 10ms>> in dessen ISR aufrufe?
Das geht schon mal gar nicht. Innerhalb einer ISR sind Interrupts
gesperrt (es sei denn, man enabled sie explizit). Da pwm_update() aber
auf eine volatile Variable wartet, die von einer anderen ISR gesetzt
wird, wird das effektiv eine Endlosschleife.
XL
Hallo Axel,
danke und SORRY. Ich bin noch Anfänger und scheine das mit den
Interrupts irgendwie nur teilweise zu verstehen.
Ich hab' bislang knapp 10 Dateien, über die meine Deklarationen und
Funktionen verstreut sind und wollte deshalb (in meiner Ansicht)
unnötiges Kopieren vermeiden. Ich dachte aber zudem auch, dass meine
obigen Postings verständlich waren. Das es eine main() gibt, ist ja,
dachte ich, klar und wie die obigen Funktionen dort eingebunden sind,
hatte ich kurz aber deutlich erklärt. Nochmal sorry.
Diese Zeile:
TCNT2 = (uint8_t)(int16_t)-((F_CPU/(F_CPU/16000000)) / 1024 * 10e-3 +
0.5); ist aus Peter's Tasterentprellung und die mittleren Klammern
ergaben 16MHz. Ich hatte das irgendwann mal auf 20MHz umgebaut. In
seiner kurzen Kommentierung wurden 10ms erwähnt, genaueres ist mir aber
bislang unbekannt.
Diese Interrupts scheinen mir wirklich zuzusetzen.:(
Hab' ich das dann richtig verstanden, dass die main() allgemein nur
150mal pro Sekunde abgearbeitet wird, weil die Interruptroutine so lange
braucht bzw. wartet?
Beeinflussen sich dann eigentlich auch die verschiedenen Timer-ISRs
untereinander?
Das einzige, was ich bräuchte, ist irgendein Signal mit 500 bzw. 1000Hz,
um die PWM auch zügig zu aktualisieren. Aber seit diesen Interrupts
versteh ich irgendwie gar nix mehr.
Achso,... und sorry, ich hatte aus dem Originalcode versehentlich ein
volatile gelöscht - hab' ich leider gerad erst bemerkt.
LG
Hallo Peter,
Peter K. schrieb:> danke und SORRY. Ich bin noch Anfänger und scheine das mit den> Interrupts irgendwie nur teilweise zu verstehen.
Dann lies. Und übe.
> Diese Zeile:> TCNT2 = (uint8_t)(int16_t)-((F_CPU/(F_CPU/16000000)) / 1024 * 10e-3 +> 0.5); ist aus Peter's Tasterentprellung und die mittleren Klammern> ergaben 16MHz.
Dann hast du irgendwas falsch kopiert. Schon bei
1
(F_CPU/(F_CPU/16000000))
rollen sich einem die Fußnägel hoch. Bei F_CPU < 16MHz kriegt man gar
"division by zero", weil der Präprozessor das als INT behandelt.
> Hab' ich das dann richtig verstanden, dass die main() allgemein nur> 150mal pro Sekunde abgearbeitet wird, weil die Interruptroutine so lange> braucht bzw. wartet?
Nicht die Interruptroutine. In pwm_setup() findest du diese 2 Zeilen:
1
// Warten auf Sync
2
pwm_sync=0;// Sync wird im Interrupt gesetzt
3
while(pwm_sync==0);
Hier wird die Variable also auf 0 gesetzt und dann in einer (leeren)
Schleife so lange gewartet, bis sie != 0 ist. Auf einen Wert != 0 wird
diese Variable nur an einer Stelle gesetzt:
1
ISR(TIMER1_COMPA_vect)
2
{
3
...
4
if(pwm_cnt==pwm_cnt_max)
5
{
6
pwm_sync=1;// 'Update jetzt möglich'
7
pwm_cnt=0;
8
}
9
...
10
}
Diese ISR wird in jeder PWM-Periode (die du auf (1/150)s definiert hast)
mindestens 1 mal (alle LEDs aus) und maximal 5 mal (alle 4 LEDs mit
verschiedener Helligkeit an) aufgerufen. Im letzten Aufruf pro
komplettem Zyklus wird die Variable auf 1 gesetzt.
Im Rahmen der Rechengenauigkeit und des glatten Aufgehens von 150Hz,
1024 PWM-Stufen und Vorteiler 8 in F_CPU passiert das also ca. 150 mal
in der Sekunde. Wenn du pwm_setup() irgendwo aufrufst, kommt das optimal
sofort (na gut, fast sofort) und pessimal nach (1/150)s zurück.
> Beeinflussen sich dann eigentlich auch die verschiedenen Timer-ISRs> untereinander?
Klar doch. Es kann immer nur eine ISR zu einer Zeit laufen. Nur wenn du
in einer ISR
1
sei()
machst, kann ein anderer Interrupt diese ISR unterbrechen. Andererseits
ist es eine goldene Regel, eine ISR immer so kurz wie möglich zu halten,
so daß das nur selten nötig ist. Insbesondere darf eine ISR sich
niemals selbst unterbrechen. Sonst hast du ganz schnell einen
Stacküberlauf.
> Das einzige, was ich bräuchte, ist irgendein Signal mit 500 bzw. 1000Hz,> um die PWM auch zügig zu aktualisieren. Aber seit diesen Interrupts> versteh ich irgendwie gar nix mehr.
Aktualisiert wird die PWM bestenfalls einmal alle (1/150) Sekunden.
Durch pwm_setup(). Öfter geht auch nicht. Wie willst du denn die
Helligkeit einer LED schneller ändern als nach einem PWM-Zyklus?
Du kannst zur Synchronisation die pwm_setup() Funktion verwenden, so wie
das in dem Wiki-Artikel vorgesehen ist.
In meiner Soft-PWM Implementierung mache ich das anders: mit einer
Callback-Funktion und einer Zählervariabe (ich habe sie "tick" genannt).
Immer wenn tick==0 ruft main() die Aktualisierungsfunktion auf, die
einen neuen Helligkeitswert setzen kann. Und auch tick entsprechend
setzt. Denn normalerweise muß man die Helligkeit nicht nach jedem
PWM-Zyklus verändern.
Meinen Soft-PWM Code findest du hier:
Beitrag "noch ein AVR Moodlight"
XL
Hallo Axel,
vielen Dank für die vielen Infos und jepp, werd' ich auf jeden Fall
machen.^^
Hmmmmm... deine SoftPWM gefällt mir wirklich sehr gut!!!(und
funktionierte sogar auf Anhieb);-)
Der Hauptgrund dafür sind die 6 zusätzlichen Bits, da die 10Bit-Stufen
bei den untersten 4 Werten nicht mehr so ganz mitkommen.
Könnte man deine PWM denn auch auf 4 LEDs umbauen? Ich bräuchte eine
4.LED-Farbe auf Port.B1(also 4 LEDs, verteilt auf 2 Ports)?
Könnte man das Setzen des Ausgangsports dann event. auch wie in meinem
Umbau der 10bit-PWM machen, um nicht benutzte Portbits so zu belassen
wie sie sind?
Liebe Grüße
Hallo Peter,
Peter K. schrieb:> Hmmmmm... deine SoftPWM gefällt mir wirklich sehr gut!!!(und> funktionierte sogar auf Anhieb);-)
Schön.
> Der Hauptgrund dafür sind die 6 zusätzlichen Bits, da die 10Bit-Stufen> bei den untersten 4 Werten nicht mehr so ganz mitkommen.
Naja, eigentlich sind es gar nicht so viel mehr reale Bits. Aber in der
Tat habe ich recht lange mit verschiedenen Faktoren für die Kennlinie
experimentiert. Und ein bisschen Luft ist auch noch; ich wollte mich
nicht davon abhängig machen, daß der Compiler die ISR immer so kurz wie
möglich codiert.
> Könnte man deine PWM denn auch auf 4 LEDs umbauen? Ich bräuchte eine> 4.LED-Farbe auf Port.B1(also 4 LEDs, verteilt auf 2 Ports)?
Es wäre viel einfacher, wenn das 4 Bits an einem Port wären. Daß die ISR
so knackig kurz ist, beruht auf 2 Punkten:
1. alle LEDs an einem Port
2. die anderen Portleitungen haben einen festen Pegel
> Könnte man das Setzen des Ausgangsports dann event. auch wie in meinem> Umbau der 10bit-PWM machen, um nicht benutzte Portbits so zu belassen> wie sie sind?
Dafür würde ich mal mit spitzem Stift die Länge der ISR nachrechnen.
Weit besser wäre es aber, die Bitmasken vorzuberechnen und mehrere
pwm_data[] Arrays zu haben (1 je genutztem Port). Dann muß die ISR nur
noch pro Port die passenden Bits rausmaskieren und setzen.
An sich hast du viel Luft, wenn dein µC mit 20MHz läuft. Leider hat
Timer 1 aber keinen Prescaler 2:1 oder 4:1, sondern nur 1:1 oder 8:1.
Ersteres gibt 300Hz PWM-Frequenz, letzteres nur magere 38Hz. Vermutlich
würde ich das unter diesen Bedingungen anders schreiben. Z.B. mit
Prescaler 8:1 und entweder 14 oder 15 Bit nominaler Auflösung
(entsprechend 75 oder 150Hz). Die Timing-Anforderungen sind bei dem
höheren Takt ja entspannter, da kann man die zusätzliche 16-Bit Addition
in der ISR verschmerzen.
XL