"Additive" PWM
Es gibt hier fast täglich Fragen zu RGB-LEDs, PWM mit x Kanälen und Timing. Hier folgt ein möglicher Lösungsvorschlag:
Die sogenannte "Additive" PWM - auch Carry- (Überlauf-)PWM genannt - bietet einen Lösungsansatz, der sich nicht nur bei LEDs schon vor über 20 Jahren bewährt hat. Das Prinzip stammt aus den frühen µC-Zeiten als Timer selten und Speicher knapp war. Im Grunde handelt es sich bei diesem Prinzip um einen NCO/DCO.
Prinzip
Um eine "wertgesteuerte" Spannung zu erzeugen wurde keine PWM mit fester Frquenz programmiert sondern mit einem festen Zeitfenster wurde ein Ausgang wertgesteuert ein- bzw. ausgeschaltet. Da es meist 8 Bit Controller waren ergab sich automatisch eine Auflösung von ebenfalls 8 Bit bei Benutzung des kpl. Bytes und Verwendung des Carry-Bits.
Damit war das System äußerst einfach:
- Warten bis das Zeitfenster abgelaufen ist - Wert auf einen Zähler addieren (z=z+w -> c) - Carry-Bit auf Ausgangpin kopieren
Ein Überlauf erfolgt alle 256/Wert * Zeitfenster. Daher steigt die Frequenz mit der ein High-Signal am Pin ansteht sehr schnell. Ein Flackern bei LEDs ist bei einem Zeitfenster von 2 KHz schon bei einem Wert von 4-5 nicht mehr störend (31,25 bzw 39,06 Hz). Der schnelle Anstieg dieser Ausgabefrequenz bzw. das bei höheren Werten hintereinander Auftreten von High-Signalen erlaubt auch ein leichteres Mitteln der Ausgabespannung mit einem kleindimensionierten RC-Glied. Nur ein durchgehendes High-Signal ist nicht direkt möglich, da bei Zählerstand 0 und Wert 255 kein Überlauf stattfindet.
Programm
Das Programm ist das aus dem Turorial Softwar-PWM mit einigen Änderungen.
Am Port B werden an den Pins PB0 bis PB5 wieder insgesamt 6 LEDs gemäß der Verschaltung aus dem I/O Artikel angeschlossen. Jede einzelne LED kann durch setzen eines Wertes von 0 bis 255 in die zugehörigen Register ocr_1 bis ocr_6 auf einen anderen Helligkeitswert eingestellt werden. Der Timer0 Overflowinterrupt wird alle 512 µs ausgelöst (~ 2 KHz).
Die Reihenfolge der Auswertung liegt an der Reihenfolge, wie die Bits in das Zwischenregister geschoben werden.
; Additive PWM
; Demoprogramm 6 LEDs am Mega8
; AVRSTUDIO 4.16
; HANSL
.include "m8def.inc"
.def temp = r16
.def ocr_1 = r18 ; Helligkeitswert Led1: 0 .. 255
.def ocr_2 = r19 ; Helligkeitswert Led2: 0 .. 255
.def ocr_3 = r20 ; Helligkeitswert Led3: 0 .. 255
.def ocr_4 = r21 ; Helligkeitswert Led4: 0 .. 255
.def ocr_5 = r22 ; Helligkeitswert Led5: 0 .. 255
.def ocr_6 = r23 ; Helligkeitswert Led6: 0 .. 255
.def pwm_1 = r24 ; PWM-Zähler Led1
.def pwm_2 = r25 ; PWM-Zähler Led2
.def pwm_3 = r26 ; PWM-Zähler Led3
.def pwm_4 = r27 ; PWM-Zähler Led4
.def pwm_5 = r28 ; PWM-Zähler Led5
.def pwm_6 = r29 ; PWM-Zähler Led6
.org 0x0000
rjmp main ; Reset Handler
.org OVF0addr
rjmp timer0_overflow ; Timer Overflow Handler
main:
ldi temp, LOW(RAMEND) ; Stackpointer initialisieren
out SPL, temp
ldi temp, HIGH(RAMEND)
out SPH, temp
ldi temp, 0xFF ; Port B auf Ausgang
out DDRB, temp
ldi ocr_1, 0
ldi ocr_2, 10
ldi ocr_3, 75
ldi ocr_4, 128
ldi ocr_5, 200
ldi ocr_6, 255
ldi temp, 0b00000010 ; CS00 setzen: Teiler 8
out TCCR0, temp
ldi temp, 0b00000001 ; TOIE0: Interrupt bei Timer Overflow
out TIMSK, temp
sei
loop: rjmp loop ; Hier PWM-Werte einlesen/ändern
timer0_overflow: ; Timer 0 Overflow Handler
clr temp ; Zwischenspeicher löschen
; Reihenfolge 6 -> 1 da rol-Befehl
clc ; Carry löschen
adc pwm_6,ocr_6 ; PWM-Zähler 6 + Helligkeitzwert 6
rol temp ; Carry in Zwischenspeicher schieben
adc pwm_5,ocr_5 ; PWM-Zähler 5 + Helligkeitzwert 5
rol temp ; Carry in Zwischenspeicher schieben
adc pwm_4,ocr_4 ; PWM-Zähler 4 + Helligkeitzwert 4
rol temp ; Carry in Zwischenspeicher schieben
adc pwm_3,ocr_3 ; PWM-Zähler 3 + Helligkeitzwert 3
rol temp ; Carry in Zwischenspeicher schieben
adc pwm_2,ocr_2 ; PWM-Zähler 2 + Helligkeitzwert 2
rol temp ; Carry in Zwischenspeicher schieben
adc pwm_1,ocr_1 ; PWM-Zähler 1 + Helligkeitzwert 1
rol temp ; Carry in Zwischenspeicher schieben
out PORTB, temp ; Zwischenspeicher auf PortB ausgeben
reti
Bei Benutzug des SRAM und ein oder mehreren Schieberegister kann auf diese Art ebenso wie bei der Software-PWM
eine relativ große Anzahl an LEDs gedimmt werden.
Bei den Schieberegistern kann dabei das Carry-Bit nach der Berechnung direkt ohne Zwischenspeicherung rausgeschoben
werden, was nocheinmal schneller und einfacher ist.
Der Versuch diese Methode in C zu programmieren dürfte meist am Compiler bzw. seinen Optimierungen scheitern, es ist eine wirklich auf Assembler optimierte Variante.
Da die Berechnung/Ausgabe im Timerinterrupt abläuft und eine konstante Rechenzeit benötigt, ist es einfach die übrigen Aufgaben, z. B. das Einlesen/ Ändern der Werte in der Main/Endlosschleife unterzubringen.