AVR PWM

Aus der Mikrocontroller.net Artikelsammlung, mit Beiträgen verschiedener Autoren (siehe Versionsgeschichte)
Wechseln zu: Navigation, Suche

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
  • 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.

Cauer-Tiefpass mit 3,3mH und 10 mH Festinduktivität

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