PWM per Software

einfache Lösung  bessere Lösung  AVR Optimierung

2011-06-01:
Die nachfolgenden Beschreibungen und Programme sind schon älter und beziehen sich auf einen seinerzeit gängigen AT90S8515 mit 8MHz Taktfrequenz. Mit einem heute üblichen ATmega48 lassen sich 64 PWM-Kanäle per Schieberegister ansteuern und über einen TWI-Bus kontrollieren. Eine Beschreibung befindet sich hier.

2002-09-25:
Bei der Puls-Weiten-Modulation wird ein digitales Ausgangssignal erzeugt, dessen Tastverhältnis moduliert wird. In einem Zeitintervall T von z.B. 100*t erzeugt ein Vorgabewert von 30 ein '1'-Signal von 30*t gefolgt von einem '0'-Signal mit 70*t. Der zeitliche Mittelwert des Ausgangssignals läßt sich durch PWM von 0 bis 100 verändern. Ein PWM-Signal läßt sich dann verwenden, wenn die Änderungen des Vorgabewertes langsam stattfinden (>= T). Bei in Prozessoren integrierten PWM-Einheiten entspricht t zumeist dem Prozessortakt; T liegt dann im Bereich von unter 50us bei 8-bit Auflösung, sodaß das Signal außerhalb des (menschlichen) Hörbereiches liegt und nicht stört.

Viele Anwendungen wie Motorsteuerung, Heizungssteuerung, Lampensteuerung oder einfache A/D-Wandlung lassen sich mit Pulsweitenmodulation eines digitalen Ausgangssignals realisieren, wobei das PWM-Signal eine Leistungsstufe steuert oder über ein Tiefpassfilter ein analoges Ausgangssignal erzeugt. Sofern der verwendete Prozessor über eine integrierte PWM-Einheit verfügt, sollte diese bevorzugt hierfür eingesetzt werden.

Werden mehr PWM-Signale benötigt als der Prozessor bietet, oder werden weniger hohe Anforderungen an die PWM-Signale gestellt, muß/kann die Pulsweitenmodulation per Software erzeugt werden. Erstrebenswert ist dabei die Zeit t möglichst klein zu halten, um eine schnelle Einstellzeit bzw. wenig Störgeräusche - insbesondere bei Motorsteuerung - zu erhalten.

 

einfache Lösung:
Das Beispielprogramm 'pwm1.c' zeigt die einfachste Art der PWM-Erzeugung für 8 Ausgangskanäle an PortA bei einem AT90S8515. Ein Schritt t entspricht 32us bei 8MHz CPU-Takt, sodaß bei max. 100 Schritten T 3,2ms entspricht. Die Ausgangsfrequenz beträgt etwa 310 Hz. Solange 'wert[i] > schritt' wird das betreffende Ausgangsbit von PortA gesetzt, anderfalls bleibt es auf '0'. Zum Testen: 'pwm1.hex'

Da nicht jeder Compiler schnellen Code für die Schleife und die Indizierung erzeugt, kann es sinnvoll sein, die Schleife in einzelne Befehle aufzulösen. Mit Schleife beträgt die Belastung des Prozessors etwa 65%. nach oben

 

bessere Lösung:
Bei 'pwm2.c' wird versucht, eine möglichst hohe Ausgangsfrequenz zu erreichen, indem die '1'-Impulse nicht zusammenhängend erzeugt werden. Bei einem Sollwert wert[] von 1, wird zwar noch ein Signal mit ca. 310 Hz und 1% Tastverhältnis erzeugt. Liegt der Sollwert bei z.B. 16, so wird eine Ausgangsfrequenz von 5 kHz mit 16% Tastverhältnis ausgegeben. Dieses Signal läßt sicht besser filtern und kann zu weniger Störgeräuschen führen. Bei einem Sollwert von 50 beträgt die Ausgangsfrequenz 15,625 kHz bei 50% Tastverhältnis. Diese Art von PWM bietet eine vom Ausgabewert abhängige (hohe) Ausgangsfrequenz. Erreicht wird dies dadurch, daß der Ausgabewert wert[] in summe[] aufsummiert wird und bei Überschreiten der Auflösung (hier 100 Schritte) eine '1' ausgegeben wird. Anschließend wird die summe[] korrigiert 'summe[]-=SCHRITTE', damit der Betrag beim Überlauf mit berücksichtigt wird. Je höher der Vorgabewert, desto öfter wird eine '1' erzeugt.

Die Ausgangssignale in einer Schleife zu erzeugen klappt hier wegen der Indizierung nicht ! Die Belastung des Prozessors ohne Schleife beträgt etwa 55%. Zum Testen: 'pwm2.hex'
nach oben

 

optimierte Lösung für AVR:
Während die vorangegangenen Beispielprogramme den Prozessor abhängig vom verwendeten Compiler zum Teil übermäßig stark belasten, bietet ein Assembler-Programm 'pwm_avr.s90' die Möglichkeit, das zuvor beschriebene Verfahren zur PWM-Erzeugung zu optimieren. Dies unter der Voraussetzung von 256 Schritten Auflösung und der Einschränkung, daß beim Wert 255 das Ausgangssignal nicht konstant auf '1' bleibt. Erreicht wird die Optimierung dadurch, daß bei der Addition von pwm_wert und pwm_summ das carry-bit den Überlauf anzeigt und direkt ins Ergebnisregister pwm_bits übernommen werden kann. Der Restbetrag der Addition steht anschließend schon in pwm_summ. Nur die benötigten Register müssen gerettet und restauriert werden. Bei acht Ausgangskanälen ergibt sich eine konstante Prozessorbelastung von (nur) 38% ! Zum Testen: 'pwm_avr.hex'

'pwm_avr.s90' ist 'sauber' programmiert, belegt keine internen Prozessorregister fest und kann daher problemlos in Verbindung mit einem C-Compiler eingesetzt werden. Programmiert man seine Anwendung in Assembler und kann interne Register beliebig nutzen, läßt sich die Routine erheblich optimieren, sodaß nur noch 13% Prozessorzeit benötigt werden. Als Beispiel soll hier 'pwm_8avr.ass' dienen.
nach oben

 

Alle Beispielprogramme erzeugen PWM-Signale für jeweils acht Kanäle; werden weniger Ausgänge benötigt, sinkt die Prozessorbelastung erheblich. Eine weitere Reduzierung der Prozessorlast wird erreicht, wenn der Timer mit einem Vorteiler versehen wird (z.B. /8); entsprechend sicken dann auch die Ausgangsfrequenzen. Für phasengenaue Ausgangssignale, darf der Interrupt des Prozessors nie gesperrt sein; anderfalls ergibt sich ein Jitter des Ausgangssignals, was aber in vielen Anwendungen auch bedeutungslos sein könnte. Eleganterweise werden zeitkritische Interruptanforderungen (z.B. Rx-Int/Tx-Int) durch Polling am Ende der Timer0-Routine erledigt, wie die in pwm_avr.s angedeutet ist. Mit über 30 kHz Abfragefrequenz geht hier bei üblichen Baudraten kein Zeichen verloren.

Kontakt:

zurück zur Übersicht