Guten Tag,
als Neuling in diesem Gebiet möchte ich ein Software PWM programmieren.
Dazu habe ich neben anderen auch folgenden Code hier im Tutorial
gefunden (siehe unten).
Bei uint8_t könnte man doch auch den "Rückfallwert" von 100 auf 255
stellen, um das Tastverhältnis noch genauer zu stellen, oder?
Ist der Code gut zu gebrauchen oder, so wie er ist, nur als
Anschauungsbeispiel gedacht, der aus Performancegründen verändert werden
muss? (andere Beispiele waren viel größer)
Wieviel Leistung erfordert solch ein Code, wenn etwa 15 LEDs einzeln
gedimmt werden sollen?
(Mikrocontroller: Atmega 644 PU 20)
Code in C:
int main( void )
{
uint8_t pwm_soll = 30; // gewünschter Dimmerwert 0..100
uint8_t pwm_phase = 0; // Laufwert der Schleife 0..100
DDRB |= (1<<PB0);
while( 1 )
{
if( pwm_soll == pwm_phase )
{
PORTB |= (1<<PB0); // active low LED aus
}
pwm_phase++;
if( pwm_phase == 100 )
{
pwm_phase = 0;
PORTB &= ~(1<<PB0); // active low LED an
}
}
return 0;
}
Heiko L. schrieb:> Bei uint8_t könnte man doch auch den "Rückfallwert" von 100 auf 255> stellen, um das Tastverhältnis noch genauer zu stellen, oder?
Hat aber den Nachteil, dass dadurch die PWM Frequenz in Summe kleiner
wird.
> Ist der Code gut zu gebrauchen oder, so wie er ist, nur als> Anschauungsbeispiel gedacht, der aus Performancegründen verändert werden> muss? (andere Beispiele waren viel größer)
Der Code hat den Nachteil, dass der µC ausser der PWM nichts anderes
mehr machen kann.
PWM ohne Timer ist in der Praxis sinnlos.
Sieh diesen speziellen Code eher als Anschauung an, wie eine PWM im
Prinzip funktioniert.
> Wieviel Leistung erfordert solch ein Code, wenn etwa 15 LEDs einzeln> gedimmt werden sollen?
Leistung hast du genug, dass ist nicht das Problem.
Sowohl deine Frequenz als auch deine Tastverhältnisse sind hier Abhängig
von der Frequenz mit der dein Kontroller läuft, den Taktzyklen die ein
Befehl braucht, und der Umsetzung des Complilers (also wie viele
Assembler-Befehle er daraus macht)
Denke nicht das jemals das raus kommt was du erwartest.
Karl Heinz Buchegger schrieb:> Hat aber den Nachteil, dass dadurch die PWM Frequenz in Summe kleiner> wird.
Stimmt, daran hab ich ja gar nicht gedacht.
Sind Gleitkommazahlen als Dimmwert in diesem Bereich eine Alternative?
> Der Code hat den Nachteil, dass der µC ausser der PWM nichts anderes> mehr machen kann.> PWM ohne Timer ist in der Praxis sinnlos.> Sieh diesen speziellen Code eher als Anschauung an, wie eine PWM im> Prinzip funktioniert.
Kann ich mir das so vorstellen, dass im Hauptprogramm ein Timer
aktiviert und Interrupt eingeschaltet wird, dann regelmäßig in ein
Unterprogramm gesprungen wird, welches einen Zähler erhöht und alle
Pins, denen ein Wert unter dem Zähler zugewiesen bekommen haben, gehen
an, bis sich der Zähler ab einem gewissen Wert wieder zurück setzt?
Weitere Unterprogramme ließen sich ja dann einfach verwirklichen, oder?
Ach ja, der Atmega644 hat auf jedem I/O Pin auch die Pin-Change
Interrupt Funktion. Also ließe sich doch jeder Pin als externer
Interrupt benutzen, oder? (nicht für die LED Steuerung, sondern für eine
Erweiterung)
Heiko L. schrieb:> Karl Heinz Buchegger schrieb:>> Hat aber den Nachteil, dass dadurch die PWM Frequenz in Summe kleiner>> wird.>> Stimmt, daran hab ich ja gar nicht gedacht.> Sind Gleitkommazahlen als Dimmwert in diesem Bereich eine Alternative?
Gleitkommazahlen sind (auf einem µC in der Leitungsklasse um die es hier
geht) fast nie eine Alternative.
Abgesheen davon: Das würde ja auch nichts ändern.
Das Grundprinzip ist ja immer noch:
Du hast n logische Zustände und entscheidest abhängig von einem Paramter
m, ob ein Ausgang auf 0 oder auf 1 sein muss.
Das Verhältnis n/m entscheidet darüber, wie lange im Mittel der Ausgang
auf 1 ist und n gibt vor, in wievielen Stufen dieses Verhältnis variiert
werden kann.
Ob man das jetzt mit ganzen Zahlen, Fixkomma oder Gleitkomma realisiert,
ändert ja am Prinzip nichts.
> Kann ich mir das so vorstellen, dass im Hauptprogramm ein Timer> aktiviert und Interrupt eingeschaltet wird, dann regelmäßig in ein> Unterprogramm gesprungen wird, welches einen Zähler erhöht und alle> Pins, denen ein Wert unter dem Zähler zugewiesen bekommen haben, gehen> an, bis sich der Zähler ab einem gewissen Wert wieder zurück setzt?
Das ist eine Möglichkeit, ja.
> Ach ja, der Atmega644 hat auf jedem I/O Pin auch die Pin-Change> Interrupt Funktion. Also ließe sich doch jeder Pin als externer> Interrupt benutzen, oder? (nicht für die LED Steuerung, sondern für eine> Erweiterung)
Das 'Problem' besteht darin, dass du den PWM-Interrupt möglichst
gleichmässig ausgelöst haben willst. PWM ist ja etwas, das über einen
zeitlichen Mittelwert funktioniert. D.h. aber auch, dass jede
Verzögerung kurzzeitig diesen zeitlichen Mittelwert durcheinander
bringt.
Heiko L. schrieb:> Karl Heinz Buchegger schrieb:>> Hat aber den Nachteil, dass dadurch die PWM Frequenz in Summe kleiner>> wird.>> Stimmt, daran hab ich ja gar nicht gedacht.> Sind Gleitkommazahlen als Dimmwert in diesem Bereich eine Alternative?
Wenn du nichts dagegen hast, daß deine PWM-Frequenz extrem kleiner wird
und gleichzeitig der Speicherverbrauch explodiert...
(Mit anderen Worten: Nein)
>> Der Code hat den Nachteil, dass der µC ausser der PWM nichts anderes>> mehr machen kann.>> PWM ohne Timer ist in der Praxis sinnlos.>> Sieh diesen speziellen Code eher als Anschauung an, wie eine PWM im>> Prinzip funktioniert.>> Kann ich mir das so vorstellen, dass im Hauptprogramm ein Timer> aktiviert und Interrupt eingeschaltet wird, dann regelmäßig in ein> Unterprogramm gesprungen wird, welches einen Zähler erhöht und alle> Pins, denen ein Wert unter dem Zähler zugewiesen bekommen haben, gehen> an, bis sich der Zähler ab einem gewissen Wert wieder zurück setzt?
Kann man z.B. so machen. Teilweise können die µCs eine begrenzte Zahl an
PWM-Kanälen auch komplett in Hardware machen, indem der Timer ein
Register für den PWM-Wert und einen Vergleicher hat, der dann direkt den
Pin ansteuert.
> Weitere Unterprogramme ließen sich ja dann einfach verwirklichen, oder?
Ja.
> Ach ja, der Atmega644 hat auf jedem I/O Pin auch die Pin-Change> Interrupt Funktion. Also ließe sich doch jeder Pin als externer> Interrupt benutzen, oder? (nicht für die LED Steuerung, sondern für eine> Erweiterung)
Ja.
Karl Heinz Buchegger schrieb:> Gleitkommazahlen sind (auf einem µC in der Leitungsklasse um die es hier> geht) fast nie eine Alternative.>> Abgesheen davon: Das würde ja auch nichts ändern.>> Das Grundprinzip ist ja immer noch:>> Du hast n logische Zustände und entscheidest abhängig von einem Paramter> m, ob ein Ausgang auf 0 oder auf 1 sein muss.> Das Verhältnis n/m entscheidet darüber, wie lange im Mittel der Ausgang> auf 1 ist und n gibt vor, in wievielen Stufen dieses Verhältnis variiert> werden kann.>> Ob man das jetzt mit ganzen Zahlen, Fixkomma oder Gleitkomma realisiert,> ändert ja am Prinzip nichts.
Ja ok, da muss ich dir recht geben. Da hatte ich wohl einen Denkfehler
drinne.
> Das ist eine Möglichkeit, ja.
Sehr gut. :)
Ich werde dann mal einen Code zusammen schreiben und euch mal drüber
schauen lassen, was ihr davon so haltet.
> Das 'Problem' besteht darin, dass du den PWM-Interrupt möglichst> gleichmässig ausgelöst haben willst. PWM ist ja etwas, das über einen> zeitlichen Mittelwert funktioniert. D.h. aber auch, dass jede> Verzögerung kurzzeitig diesen zeitlichen Mittelwert durcheinander> bringt.
Es soll parallel ein IR-Empfänger Singale auswerten und damit die
Dimmung der LEDs beeinträchtigen und ein kleines Display (zweizeilig)
angesprochen werden.
Ich denke, das sollte noch möglich sein, ohne den Rest allzu sehr zu
beeinträchtigen, oder?
Hier mal ein Entwurf des eines Codes.
Ich hab bisher noch keine große Erfahrung mit Microcontrollern, also
kann noch einiges falsch sein.
Er soll ein SoftPWM erzeugen und bei einem Signal auf Pin 6 im Register
C soll die Led dunkler werden (nur als Beispiel).
Was haltet ihr davon?
#include <avr/io.h>
#include <avr/interrupt.h>
volatile uint_t pwm_soll = 30;
ISR(TIMER0_COMPA_vect)
{
zähler++;
if( zähler >= pwm_soll)
{
PORTA = 0x01; // LED ein
}
if( zähler == 100)
{
zähler = 0;
PORTA = 0x00; // LED aus
}
}
ISR(PCINT2_vect)
{
pwm_soll = 60;
}
int main( void )
{
DDRA = 0xFF;
DDRC = 0xBF; // Alle auf Ausgang, außer PIN6
uint8_t zähler=0;
PCICR |= (1 << PCIE2);
PCMSK2 |= (1<<PCINT22);
TCCR0A = 1;
TIMSK0 |= (1<<OCIE0A);
;
sei();
while(1){};
return 0;
}
Schick es durch den Compiler und korriger erst mal die C-Fehler :-)
(Aber vom Prinzip her ist das für eine PWM ok. Ich werde zwar meiner
Lebtag nicht verstehen, warum alle Welt immer als erstes einen Taster an
einen Interrupt hängen muss, aber ok, das ist eine andere Baustelle)
Karl Heinz Buchegger schrieb:> Schick es durch den Compiler und korriger erst mal die C-Fehler :-)>> (Aber vom Prinzip her ist das für eine PWM ok. Ich werde zwar meiner> Lebtag nicht verstehen, warum alle Welt immer als erstes einen Taster an> einen Interrupt hängen muss, aber ok, das ist eine andere Baustelle)
Auch die Ansteuerung der Register ist so korrekt?
Der Taster soll hier nur als Platzhalter für ein beliebiges Input-Signal
gelten.
Nachher soll das ganze über IR gesteuert werden (wenn ich es ohne
Probleme hinbekomme).
Heute Abend wir das ganze auch erstmal mit der Hardware getestet.
Ich danke dir schon mal für deine Hilfe! :)
Heiko L. schrieb:> Auch die Ansteuerung der Register ist so korrekt?
Nein. Die Timer-Einstellung stimmt nicht. Ich vermute, du wolltest
eigentlich TCCR0B statt TCCR0A beschreiben.
Und warum benutzt du den Compare-Match-Interrupt? Du hast das
Compare-Register nirgends beschrieben, außerdem wäre eh der
Overflow-Interrupt passender.
Ich hab mir die Teile aus dem Code, die ich noch nicht kannte, aus
anderen Programmschnipseln abgeschaut.
Daher hätte es mich auch gewundert, wenn so alles korrekt wäre. ;)
Was ist denn der Unterschied zwischen TCCR0A und TCCR0B?
Ich dachte mir einfach, sie wären beide gleich zu benutzen und ich fange
bei A an, was wohl falsch war.
Wie müsste es ingesamt denn richtig heißen?
Levi schrieb:> Ich hab mir die Teile aus dem Code, die ich noch nicht kannte, aus> anderen Programmschnipseln abgeschaut.> Daher hätte es mich auch gewundert, wenn so alles korrekt wäre. ;)>> Was ist denn der Unterschied zwischen TCCR0A und TCCR0B?
Mach dir selbst einen Gefallen und hol dir von Atmel das Datenblatt zu
deinem Chip. Da ist das alles genauestens beschrieben.
Ohne das Datenblatt kannst du nicht programmieren.
Ich hab den Code mal ein wenig angepasst und Fehler in der Syntax
bereinigt.
Er funktioniert prinzipiell auch, nur ist das Zeitfenster viel zu groß.
Wenn ich das richtig verstanden habe, geht die Led ja nach 100
Timerüberläufen an, oder? Das ist im Moment eine Zeitspanne von etwas
über 10s, also von PWM noch weit entfernt.
Habe ich noch einen Fehler bei den Bezeichnungen, einen grundsätzlichen
Gedankenfehler oder lässt sich das ganze leicht an kürzere Taktzeiten
anpassen?
Code in C:
#include <avr/io.h>
#include <avr/interrupt.h>
volatile uint8_t pwm_soll = 50, i=0;
ISR(TIMER0_OVF_vect)
{
i++;
if( i >= pwm_soll)
{
PORTA = 0x01; // LED ein
}
if( i == 100)
{
i = 0;
PORTA = 0x00; // LED aus
}
}
ISR(PCINT2_vect)
{
pwm_soll = 60;
}
int main( void )
{
DDRA = 0xFF;
DDRC = 0xBF; // Alle auf Ausgang, außer PIN6
PCICR |= (1 << PCIE2);
PCMSK2 |= (1<<PCINT22);
TCCR0B |= (1<<CS00)|(1<<CS02);
TIMSK0 |= (1<<TOIE0);
sei();
while(1){};
return 0;
}
Ah, ich sehe gerade, wenn ich den Vorteiler entferne, scheint es ohne
Probleme zu funktionieren.
Wer mal das gleiche Problem hat:
Ich habe also: TCCR0B |= (1<<CS00)|(1<<CS02);
durch: TCCR0B |= (1<<CS00);
ersetzt.
Nun scheint alles ok zu sein, oder?
Heiko L. schrieb:> Nun scheint alles ok zu sein, oder?
Was nicht in Ordnung ist ist, dass du diese Dinge durch Versuch und
Irrtum klärst, wenn du sie eigentlich durchrechnen solltest.
Ja, man kann berechnen, wie lange die Dinge dauern. Und nein, das ist
gar nicht mal so schwer, sondern absolut logisch nachvollziehbar.
http://www.mikrocontroller.net/articles/FAQ#Timer
Mir ist klar, dass man das auch ausrechnen kann, doch bringt das nichts,
wenn man versehentlich einen Faktor von 1000 im Programm hat, den man
erst beim zweiten Durchschauen sieht.
Ach ja, es gibt Menschen, die eher durch ausprobieren und und
berichtigen eigener Fehler lernen, als sich den Stoff NUR anzulesen.
Gruß