AVR PWM
Vorwort
Dieser Artikel ist noch nicht vollständig! Und außerdem überschneidet er sich teilweise mit dem Tutorial, weil PWM und Timer zum Verständnis praktisch dasselbe sind. Vielleicht kann ja jemand, der gerade dabei ist, sich diese Dinge anzueignen, die Beschreibung vorantreiben (erweitern/entschlacken)?
Hier sollen die Möglichkeiten und die Funktionsweise der PWM mit AVRs erläutert werden, so daß Anfänger auf ihrem Weg zum Ziel unterstützt werden, ohne sich erst durch die wenig erklärenden Beiträge im Forum zu quälen. Auch wenn das Verständnis (hoffentlich) dann nicht mehr aus dem Datenblatt kommen muß, ist dieses für die spezifischen Einstellungen und Feinheiten absolut notwendig. Aber mit dieser Übersicht sollte es leichter fallen, die relevanten Informationen schneller zu finden.
Ich gehe dabei von meiner Situation aus: "Gerade mit AVRs angefangen, die LED blinkt, Taster wird abgefragt, schonmal von PWM gehört und unter den AVR Pins welche mit OC.. entdeckt, das hängt damit irgendwie zusammen." Man sollte sich auch um die Prozessorfrequenz gekümmert haben, also die AVR_Fuses entsprechend gesetzt haben.
Wer in Begriff steht, sein erstes Board zu ätzen, sollte sich über die verschiedenen Möglichkeiten, die die OCnx Pins bieten, informiert haben.
Übrigens lässt es sich besser lesen, wenn man sein Browserfenster so schmal macht, daß der Text in eine schöne Spalte gezwungen wird.
Einführung
Im AVR-GCC-Tutorial werden im Abschnitt DAC-Optionen verschiedene Möglichkeiten angesprochen, analoge Spannungen zu generieren.
Darunter fällt auch die Pulsweitenmodulation, bei der durch schnelles Ein- und Ausschalten eines Ausgangs (über einen Filter) eine analoge Spannung generiert werden kann.
Beim Dimmen von Lichtquellen wirkt die Trägheit des Auges als Filter, wenn z. B. eine LED im Mittel die Hälfte der Zeit eingeschaltet ist, scheint es also, als würde sie nur halb so hell leuchten.
Bei Motoren läßt sich PWM gut zum Dosieren des Stroms einsetzen, ohne große Verluste zu haben. Für einen Teil der Zeit wird also der volle Motorstrom eingeschaltet, d.h. das Drehmoment ist maximal.
Die Rechtecksignale lassen sich mit Mikrocontrollern auf zwei Wegen erzeugen:
PWM per Software
- Kostet Rechenzeit, erhöhter Softwareaufwand
- Signalausgabe auf jedem I/O-Pin möglich
- Höhere Kanalanzahl möglich
oder
PWM per Hardware
- Läuft unabhängig vom Programm
- Bietet mehr Möglichkeiten bei gleichem Softwareaufwand
- Signalausgabe nur auf bestimmten, fest vorgegebenen Pins möglich
- Benötigt einen Timer
- Nur begrenzte Anzahl an PWM-Kanälen vorhanden (viele verbreitete AVR-Typen haben 2 bis 3, neuere auch mehr)
Alles was mit Pulsen und Modulation zu tun hat, hat auch was mit Zeit zu tun – denn im Prinzip soll mit einer bestimmten Frequenz für eine bestimmte Dauer ein Pin eingeschaltet werden.
Alles was bei Mikrocontrollern mit Zeit zu tun hat, hat wahrscheinlich auch etwas mit einem Timer bzw. Counter zu tun.
Timer / Counter
Unter Timer bzw. Counter (T/C) steht noch nicht soviel, aber man sollte kurz mal reinsehen, oder mehr dazu schreiben, oder die fehlende Verknüpfung anlegen.
Ein Timer ist nichts anderes als ein selbständiger Zähler (Counter), der mit einer bestimmten Frequenz einen Wert raufzählt. Und zwar in Hardware, also unabhängig vom Programm. Seine Zählfrequenz wird vom Prozessortakt abgeleitet, das erledigt der Prescaler in einstellbaren Schritten (Frequenzteiler).
Der Zählerstand läßt sich sowohl in Software als auch von der Hardware selbst überwachen - und schon lassen sich damit periodisch Ereignisse auslösen.
Deswegen lassen sich die T/C für viele Zwecke verwenden, wir wollen den T/C für PWM nutzen (trotzdem gleich eine Übersicht über die verschiedenen Modi).
Es lohnt sich natürlich, das Prinzip der T/C verstanden zu haben. Ein Blick ins GCC-Tutorial lohnt, die Atmel Application Note 130: Setup and Use the AVR Timers schadet auch nicht.
Wie schon angedeutet, gibt es - je nach AVR - einen oder mehrere T/C . Sie unterscheiden sich erwartungsgemäß durch ihre Parameter und Optionen, z. B. die Auflösung, Frequenz, Zählweise und andere Betriebsmodi.
Und natürlich auch durch den Namen, der sich auch in den Registern widerspiegelt: Sie werden nämlich numeriert (im Folgenden hier und im Datenblatt mit Platzhalter n).
T/C 0 ist beim tiny2313 der 'einfache' mit 8 Bit Auflösung (das Aus-An Verhältnis läßt sich in 256 Stufen einstellen), T/C 1 dagegen hat eine Auflösung von 16 Bit und bietet einige weitere Möglichkeiten.
8 oder 16 Bit ?
Außer der Tatsache, daß die Auflösung bei 16 Bit mit 65536 Stufen um einiges feiner ist, gibt es noch folgende Unterschiede:
- Er verwendet einige 16 Bit Register
- Schreiben/Lesen dieser Register erfolgt in Schritten
Mit dem Zähler alleine kann man noch nicht so viel anfangen. Ausgedacht wurde deswegen außerdem die
Output Compare Unit
was soviel bedeutet wie Ausgangsvergleichseinheit.
Jeder Zähler hat eine oder mehrere voneinander unabhängige Output Compare Units (OC), auch wieder mit den dazugehörigen Registern.
Die verschiedenen OCs und ihre Register werden mit Buchstaben ('A', 'B') benannt. (Im PWM Modus hängt das direkt mit den Pins zusammen: OC1B ist der Ausgang der OC des T/C 1. Dazu gleich mehr..)
Die OC vergleicht den Zählerstand (im Register TCNTn) ständig mit ihren eigenen Registerinhalten (OCRnx). Wenn diese übereinstimmen, passiert etwas.
Was passiert, wird bestimmt durch die
Betriebsmodi
Der Zähler zählt. Die OC Unit vergleicht dessen Zählerstand mit einem Wert. Wenn diese übereinstimmen, kann etwas passieren.
Weil es hier gleich mit den Einstellungen in den Registern losgeht, noch ein Hinweis:
Die Kontrolle über das Verhalten der Zähler und OCs wird über Register vorgenommen, deren Namen nichts mit den OC Units zu tun haben! Die Einstellungen sind lediglich auf zwei Register verteilt, die Timer/Counter Control Register - TCCRnA & TCCRnB.
Ein paar Notizen:
- In verschiedenen Modi haben auch Bits in den Registern eine andere Bedeutung!
- Meist können Interrupts ausgelöst werden.
- Es kann häufig auch der Zählerstand per Software verändert werden.
- Die Frequenz der ausgegebenen Waveform hängt ab von
- I/O Clock (CPU)
- Prescaler
- Counter Modus
Die verschiedenen Modi (vorerst die vom 8 Bit Zähler):
- Normal (evtl. für Software PWM)
- Clear Timer on Compare (CTC) (eingeschränkte PWM)
- Fast PWM
- Phase Correct PWM
Dazu hier noch folgende Begriffe im Zusammenhang mit dem Zähler:
BOTTOM: 0x00 MAX : 0xFF bei 8 Bit, 0xFFFF bei 16 Bit TOP : MAX oder OCRnx
Normal
(evtl. für Software PWM)
Der Zähler zählt rauf (BOTTOM->MAX), und wird nicht zurückgesetzt, sondern läuft einfach über, und setzt dabei sein Overflow-Flag. Dieser Modus wird zur PWM nicht empfohlen, weil er im Vergleich mit Hardware-PWM viel CPU-Zeit benötigt - das ist logisch: Bei jedem Nulldurchgang müsste man einen Interrupt verwenden, der dann eine Routine ausführt, die den Ausgang umschaltet. Und man müsste den Zählerstand manipulieren, um die Pulslänge zu verändern.
Für die verhältnismäßig langsame (Menschenzeit) Programmsteuerung ist dieser Modus aber ideal. Während das Hauptprogramm i.A. einfach endlos durchläuft, wird die Programmzeit durch einen Timer(-Interrupt) in Time-Slots gerastert (z. B. 1/10s). Damit lassen sich bequem Wartezeiten bzw. zeitabhängige Ereignisse steuern, ohne das Programm anzuhalten.
Clear Timer on Compare (CTC)
(eingeschränkte PWM)
Der Zähler zählt hoch, bis er mit OCRnx übereinstimmt (BOTTOM->OCRnx: Match!) und wird dann auf Null gesetzt. Der maximale Wert lässt sich also über das Register OCRnx komfortabel bestimmen.
Konkret bedeutet das, dass die in diesem Modus vom Prescaler erzeugte Basisfrequenz nochmals durch den Wert von OCRnx geteilt wird.
Für PWM:
Wenn eingestellt ist, dass der OC-Ausgang bei jedem Match umschaltet (toggle), entspricht der eingestellte Wert dem Pulsweitenverhältnis. Bei OCRnx=128 des 8 Bit T/C wäre also etwa die Hälfte der Zeit der Pin eingeschaltet.
Allerdings kann das beim T/C 0 des tiny2313 nur der Ausgang A (OC0A). Also ins Datenblatt gucken!
Fast PWM
Einer von den zwei eigentlichen PWM-Betriebsarten ist der Fast PWM Modus. Der Counter zählt von BOTTOM bis TOP, wobei TOP entweder 0xFF oder OCRnx sein kann.
Bei einem Match wird im
a) nicht-invertierenden Modus der Zähler gelöscht, und bei BOTTOM gesetzt
b) invertierenden Modus der Zähler gesetzt, und bei BOTTOM gelöscht.
Klingt theoretisch kompliziert, praktisch invertiert es nur den Ausgang. Aber der Vergleichswert muss anscheinend ständig aktualisiert werden!?
Dieser Modus hat eine asymmetrische Ausgangsform, weil der Ausgang periodisch umgeschaltet wird (also immer nach der gleichen Zeit) und dann nach Ablauf der variablen Pulslänge wieder invertiert wird.
Und es gibt noch einen Toggle-Modus, der allerdings nur für den Ausgang OC0A zur Verfügung steht.
Phase Correct PWM
Ist nur halb so schnell wie Fast PWM, dafür aber mit symmetrischer Wellenform.
Erreicht wird das, indem von BOTTOM->TOP gezählt wird, und dann wieder runter: TOP-BOTTOM.
TOP kann entweder 0xFF oder OCRnx sein.
Auch hier gibt es wieder den nicht-invertierenden, den invertierenden, und den toggle-Modus (nicht an OC0B).
Der symmetrische PWM-Modus wird gerne für Motorsteuerungen verwendet, wenn man den Strom in den Motorwindungen messen möchte. Da man nicht während der Schaltzeitpunkte der H-Brückentransistoren messen möchte (noise), braucht man einen Messzeitpunkt der maximal weit von diesen Schaltzeitpunkten entfernt ist. Die BOTTOM und TOP Werte des Counters bieten genau dies, da sie in der Mitte des High- bzw. Lowpegels liegen.
Praktisches Vorgehen
- Pins low setzen
- Pins als Ausgang konfigurieren.
- Geeignete Wellenform ermitteln
- Registerinformationen für ausgewählten T/C im Datenblatt aufschlagen
- Modus & Prescaler setzen und damit starten
- Bits in Control-Register TCCRnA & TCCRnB schreiben. Siehe Bitmanipulation
- Vergleichswert OCRnx setzen
Programmbeispiele
PWM per Software
Pseudocode
//Initialisierung pwm_phase = 0 // von 0 bis 100(99) ergibt ein moduliertes Signal pwm_soll = 30 // Tastverhältnis in Prozent (Werte von 0..100) //alle s Sekunden tue: wenn pwm_soll = pwm_phase dann ausgang = LOW wenn pwm_phase++ = 100 dann pwm_phase = 0 ausgang = HIGH
Das Tastverhältnis ist [math]\displaystyle{ t_\text{HIGH}=\frac{100}{\text{pwm}\_\text{soll}} }[/math]
Die Frequenz ist [math]\displaystyle{ f=\frac{s}{100} \text{Hz} }[/math]
ASM
Der Code ist nicht von mir, ich hab den John Honniball auch nicht um Erlaubnis gefragt, den Code hier zu posten. Trotzdem finde ich das Ganze recht nützlich und hab' es mir trozdem erlaubt:
; ledpwm.asm --- drive a blue LED with PWM 21/04/2006
; Copyright (c) 2006 John Honniball
.include "m8def.inc"
.org 0x0000
; Blue LED on Port B bit 2
.equ LEDPORT = PortB
.equ LEDBIT = 0
; This program drives a single LED connected to the AVR's I/O port. It
; is connected so that the cathode of the LED is wired to the AVR pin,
; and the anode of the LED is wired to the 5V power supply via a
; resistor. The value of that resistor depends on the colour of the LED,
; but is usually a few hundred ohms.
; We control the brightness of the LED with Pulse Width Modulation (PWM),
; for two reasons. Firstly, we have no analog outputs on the AVR chip,
; only digital ones. Secondly, a LED's brightness does not respond
; linearly to variations in supply voltage, but it responds much better
; to PWM.
; Pulsating LED looks better if it never quite goes "off", but cycles from
; full brightness to a dim state, and back again
.equ MINBRIGHT = 25
.equ MAXBRIGHT = 255
; This value controls how fast the LED cycles from bright to dim. It is
; the number of PWM cycles that we generate for each step in the brightness
; ramp, up and down. Larger numbers will make the pulsation slower.
.equ NCYCLES = 1
; Start of program execution after a Reset
ldi r16,low(RAMEND) ; Initialise stack to top of RAM
out SPL,r16
ldi r16,high(RAMEND)
out SPH,r16
; Initialise the hardware
ldi r16,0xff ; Set Port B to all outputs
out DDRB,r16
sbi LEDPORT,LEDBIT ; Switch off blue LED by setting output pin high
; Start with LED at its lowest level, then ramp up to maximum
dopwm: ldi r17,MINBRIGHT ; R17 holds current brightness level
l1: ldi r18,NCYCLES ; R18 counts PWM cycles, and hence pulsation speed
l2: cbi LEDPORT,LEDBIT ; Output pin low, LED on
mov r16,r17 ; R16 controls length of delay (= R17)
rcall delayn4us ; Call delay subroutine
sbi LEDPORT,LEDBIT ; Output pin high, LED off
ldi r16,255
sub r16,r17 ; R16 controls length of delay (= 255 - R17)
rcall delayn4us ; Call delay subroutine
dec r18 ; Decrement PWM cycle counter
brne l2
inc r17 ; Increase brightness by one step
brne l1
; Now ramp back down to the minimum brightness
ldi r17,MAXBRIGHT ; R17 holds current brightness level
l3: ldi r18,NCYCLES ; R18 counts PWM cycles, and hence pulsation speed
l4: cbi LEDPORT,LEDBIT ; Output pin low, LED on
mov r16,r17 ; R16 controls length of delay (= R17)
rcall delayn4us ; Call delay subroutine
sbi LEDPORT,LEDBIT ; Output pin high, LED off
ldi r16,255
sub r16,r17 ; R16 controls length of delay (= 255 - R17)
rcall delayn4us ; Call delay subroutine
dec r18 ; Decrement PWM cycle counter
brne l4
dec r17 ; Decrease brightness by one step
cpi r17,MINBRIGHT ; Have we reached the minimum?
brne l3
rjmp dopwm ; Loop back to start
; DELAYN4US
; Delay for (R16 * 4) microseconds
delayn4us: tst r16 ; R16 = 0? (no delay)
breq dly4
dly2: ldi r24,low(16)
ldi r25,high(16)
dly3: sbiw r24,1 ; 2 cycles
brne dly3 ; 2 cycles
dec r16
brne dly2
dly4: ret ; Return to caller
C
Dies ist ein einfaches Beispiel einer dimmbaren LED als Software PWM in C.
// F_CPU 4 MHz
#include <avr/io.h>
int main( void )
{
uint8_t pwm_soll = 30; // gewünschter Dimmerwert 0..100
uint8_t pwm_phase = 0; // Laufwert der Schleife 0..100
// LED + Widerstand mit PB0 und +5V verbunden
// PB0 o-----|<-----###------o Vcc 5V
DDRB |= (1<<PB0); // Pin PB0 an Port B als Ausgang
// LED ist bereits an
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;
}
Eine komplexere Variante mit Interrupts wird im Artikel Soft-PWM beschrieben.
BASCOM
Der entsprechende Quelltext in Bascom:
$regfile = "m8def.dat" $crystal = 4000000 Ddrb = &H01 Dim Pwm_phase As Integer , Pwm_soll As Integer Do If Pwm_soll = Pwm_phase Then Portb.0 = 1 End If Incr Pwm_phase If Pwm_phase = 100 Then Pwm_phase = 0 Portb.0 = 0 End If Loop End
PWM per Hardware
Programmbeispiele
ASM
Für AtMega8.
.include "m8def.inc"
.def temp = r16
start:
ldi temp, LOW (RAMEND)
out SPL, temp
ldi temp, HIGH (RAMEND)
out SPH, temp
ldi temp, 0xFF
out DDRB, temp ;define PortB as output
ldi temp, 0xF3
out TCCR1A, temp ;10bit Phase Correct PWM
ldi temp, 0x0A
out TCCR1B, temp ;set Prescaler
sei
main:
ldi temp, 0x1
out OCR1AH, temp ;set pwm pin 1 highbyte
ldi temp, 0x00
out OCR1AL, temp ;set pwm pin 1 lowbyte
;pin: PortB1
ldi temp, 0x00
out OCR1BH, temp ;set pwm pin 2 highbyte
ldi temp, 0x00
out OCR1BL, temp ;set pwm pin 2 lowbyte
;pin: PortB2
loop:
rjmp loop
C
Hier wird mit dem 16-Bit-Counter 1 im PWM phase correct 8-Bit Modus eine LED am Pin OC1A gedimmt. Die Frequenz beträgt
- [math]\displaystyle{ f = \frac{\text{Taktfrequenz}}{2 \cdot \text{Prescaler} \cdot 256} \,\text{Hz} }[/math]
In [1] wurde beobachtet, dass der Ausgabepin OC1A unbedingt vor der Initialisierung der PWM auf Ausgang gesetzt werden muss, wie auch oben unter Praktisches Vorgehen erläutert ist.
DDRB |= (1<<OC1A); // Port OC1A mit angeschlossener LED als Ausgang
TCCR1A = (1<<WGM10) | (1<<COM1A1); // PWM, phase correct, 8 bit.
TCCR1B = (1<<CS11) | (1<<CS10); // Prescaler 64 = Enable counter
OCR1A = 128-1; // Duty cycle 50% (Anm. ob 128 oder 127 bitte prüfen)
Ein sehr gut erklärtes C PWM Beispielprogramm (inc. vielen Codekommentaren) kann man bei extremeelectronics.co.in finden.
Tiefpassfilter-Berechnung
Die PWM-Frequenz muß meistens mit einem Tiefpassfilter entfernt werden, da sie nachfolgende Verstärkerstufen übersteuert oder den Hörgenuss trübt. Ein einfacher RC-Tiefpass kann für Motorsteuerungen ausreichen, für Audioanwendungen ist der Abstand zwischen höchster Niederfrequenz und PWM-Frequenz zu klein. Ein aktives Filter mit Operationsverstärker kann die Lösung sein, oder ein passiver LC-Tiefpass. Dessen Berechnung mittels AADE Filter Designer soll hier an einem Fallbeispiel erläutert werden.
Ein ATmega48 mit 20 MHz Quarz soll mittels 10 Bit "fast PWM" des 16 Bit- Timers 1 ein Stereosignal am Pin OC1A und OC1B ausgeben. Die PWM-Frequenz beträgt somit knapp 20 kHz, nach dem Abtasttheorem sind maximal 10 kHz Nutzsignal möglich. Mit der Faustregel "6dB pro Bit" erreichen wir einen Dynamikumfang oder Störabstand von 60 dB. Etwa dieselbe Unterdrückung sollte auch das Tiefpassfilter erreichen.
Zunächst brauchen wir noch eine Abschätzung der zulässigen Ausgangsbelastung. Laut Datenblatt beträgt der maximal zulässige Strom pro Pin 40 mA, entsprechend einem 125Ω Widerstand von 5V nach Masse. Mit 10 bis 20 mA entsprechend 500 bis 250Ω als Maximalwert dürfte also nichts passieren. Der zeitliche Mittelwert liegt für Audioanwendungen bei 2,5V, also der halben Maximalspannung. Ein hochohmiger Kopfhörer, z. B. 600Ω, läßt sich so noch ohne weitere Verstärker anschließen.
Die größte Filtersteilheit erreicht das Cauer- oder Elliptic Filter, auf Kosten von größeren Phasenänderungen/ Gruppenlaufzeit gegenüber anderen Filtertypen. Wir starten also das Filterberechnungsprogramm mit den Vorgaben "lowpass", "Cauer/Elliptic", "3.Ordnung", was eine Schaltung mit einer Induktivität und drei Kapazitäten berechnet. Den Ein- und Ausgangswiderstand geben wir erst mal irgendwo in dem genannten Bereich vor, die Durchlasswelligkeit kann auf 1 dB bleiben, Durchlassfrequenz wie gesagt 10000 Hz, die Sperrfrequenz etwas unterhalb der PWM-Frequenz, ca. 17500Hz, da der Dämpfungspol dann etwa auf 20 kHz fällt. Mit "analyze voltage insertion gain" berechnen wir eine Durchlasskurve und kontrollieren die korrekte Lage des Dämpfungspols. Jetzt variieren wir die beiden Widerstände, bis die Induktivität etwa einem leicht erhältlichen Normwert entspricht. Die drei Kondensatoren werden am Schluß ebenfalls mit dem nächsten Normwert bestückt. Sicherheitshalber kann man diese endgültige Schaltung noch mit einem Schaltungssimulationsprogramm überprüfen und die Bauteilwerte leicht korrigieren.
Als Induktivität kommen eher die größeren Bauformen infrage, die "Garnrollen"-Form oder die axiale Bauform 77A von Fastron. Hier gilt: je größer desto höhere Güte, wie man aus den Katalogangaben zum Gleichstromwiderstand schließen kann. Von magnetischen Wechselfeldern wie etwa Schaltregler-Trafos sollte man ein paar Zentimeter Abstand halten.
Im Bild sind zwei Schaltungen für die genannten Frequenzen mit einer 3,3mH-Drossel und einer 10mH-Drossel gezeigt. Die Kurven sind noch mit verlustlosen Bauteilen und den berechneten krummen Bauteilwerten gezeichnet. Der Widerstand am Ausgang kann auch durch die Last wie der genannte Kopfhörer gebildet werden. Ein größerer Wert hat hier wenig Einfluss auf die Filterkurve, während der Widerstand am Eingang genau den vorgegebenen Wert haben sollte.
Siehe auch
- AVR-Tutorial: PWM
- Beiträge im Forum Codesammlung, die den Begriff PWM enthalten: hier