"Additive" PWM

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

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.