Software-Fader

 

Hinweis:

Bei Ansicht auf Smartphones werden Zeilen umgebrochen, so dass die Strukturierung der s’AVR-Programme teilweise verloren geht.

Der kurze Weg geht hier entlang:


LED-Crossfader mit Luminanz-Korrektur (27.4.2016)

Nach LED-Lauflicht und PWM-Poti-Dimmer gibt es nun noch einen µC-basierenden LED-Crossfader als s’AVR-Programm, wenn auch hier aufgrund des einfachen Programms mit nur zwei s’AVR-Strukturen.

Zum Umblenden zwischen zwei LED-Treibern dient wiederum ein Poti, wobei z.B. Links-Anschlag LED-Treiber #1 100% AUS und Rechts-Anschlag LED-Treiber #1 100% EIN und LED-Treiber #2 umgekehrt.

Beide LED-Treiber benötigen einen PWM-Dimm-Eingang (meist ein Enable-Eingang), der jeweils durch einen der beiden PWM-Ausgänge des AVR-µC angesteuert wird.

Beide PWM-Kanäle werden in diesem Beispiel mit Timer 1 des AVR-µC und für superweiches Umblenden mit 10 Bit Auflösung realisiert. Die PWM-Frequenz beträgt 225 Hz (bedingt durch 3,6864 MHz CPU-Takt).

Und so geradlinig schaut das s’AVR-Programm einschließlich der großen Tabelle für die 10-bit-Luminanz-Korrektur aus (wiederum eine Kopie aus der Atmel-Studio-Quelldatei, Assembler-Befehle sind blau markiert):

.def rmp = r16          ; rmp = temporary register (multi purpose)

.def ADCresultL = r20   ; ADC result

.def ADCresultH = r21   ; ADC result

.def PWMvalueL = r22    ; value for PWM

.def PWMvalueH = r23    ; value for PWM

 

clr  rmp

out  DDRC,rmp           ; all Port C pins are inputs (default)

                        ; ADC (using pin ADC5 at PORTC,5):

ldi  rmp,(1<<REFS0 | 1<< MUX2 | 1<< MUX0) ; 0b0100_0101

sts  ADMUX,rmp          ; select VCC ref, ADC5

clr  rmp

sts  ADCSRB,rmp         ; ADC free running mode (default setting)

ldi  rmp,1<<ADC5D       ; 0b0010_0000

sts  DIDR0,rmp          ; disable digital in for ADC5

ldi  rmp,(1<<ADEN | 1<<ADSC | 1<<ADPS2 | 1<<ADPS0) ; 0b1100_0101

sts  ADCSRA,rmp         ; enable ADC, 114 kHz sampling rate

                        ; for free running mode ADSC must be set as well!

 

                        ; PWM using TC1, OC1A at PORTB,1 and OC1B at PortB,2

                        ; (pins 15 and 16 for ATmega328P)

                        ; 10 bit PWM, non-inverted mode:

ldi  rmp,(1<<COM1A1 | 1<<COM1B1| 1<<WGM11 | 1<<WGM10)

sts  TCCR1A,rmp         ; WGM12/13 = 0 --> Phase Correct PWM mode

ldi  rmp,(1<<CS11)      ; prescaler /8 --> 225 Hz PWM @3,6864 MHz clock

sts  TCCR1B,rmp

sbi  DDRB,1             ; enable OC1A as output (pin 15 for ATmega328P)

sbi  DDRB,2             ; enable OC1B as output (pin 16 for ATmega328P)

cbi  PORTB,1            ; prepare Port B1 for LOW if set as input

cbi  PORTB,2            ; prepare Port B2 for LOW if set as input

 

LOOP                    ; main loop

  lds   rmp,ADCSRA      ; read ADCSRA

  sbr   rmp,1<<ADSC     ; set start ADC bit

  sts   ADCSRA,rmp      ; write back ADCSRA

  REPEAT

    lds  rmp,ADCSRA     ; check ADCSRA

  UNTIL !rmp,ADSC       ; ADSC cleared after ADC ready

  lds   ADCresultL,ADCL ; get ADC low byte

  lds   ADCresultH,ADCH ; get ADC high byte

  ldi   zl,low(2*LumiTable)

  ldi   zh,high(2*LumiTable)

  mov   xl,ADCresultL   ; keep original ADC copy

  mov   xh,ADCresultH   ; keep original ADC copy

                        ; movw xl,ADCResultL

                        ; alternately MOVW instead of 2x MOV

  lsl   xl              ; *2 due to word data

  rol   xh              ; *2 due to word data

  add   zl,xl

  adc   zh,xh

  lpm   PWMvalueL,z+    ; get corrected value for PWM ...

  lpm   PWMvalueH,z     ; ... from LumiTable

  sts   OCR1AH,PWMvalueH ; initiate PWM, high byte first

  sts   OCR1AL,PWMvalueL

  ldi   rmp,0xff        ; now handle complementary PWM channel

  sub   rmp,ADCresultL  ; get 0xffff - ADCResult for channel B

  mov   ADCResultL,rmp

  ldi   rmp,0x03        ; take only 10 bits into account

  sbc   rmp,ADCresultH

  mov   ADCresultH,rmp  ; now complementary value available

  ldi   zl,low(2*LumiTable)

  ldi   zh,high(2*LumiTable)

  lsl   ADCresultL      ; *2 due to word data

  rol   ADCresultH      ; *2 due to word data

  add   zl,ADCresultL

  adc   zh,ADCresultH

  lpm   PWMvalueL,z+    ; get corrected value for PWM ...

  lpm   PWMvalueH,z     ; ... from LumiTable

  sts   OCR1BH,PWMvalueH ; initiate PWM, high byte first

  sts   OCR1BL,PWMvalueL

ENDL                    ; main loop (forever)

 

LumiTable:

.dw 0,0,0,0,0,1,1,1,1,1,1,1,1,1,2,2

.dw 2,2,2,2,2,2,2,3,3,3,3,3,3,3,3,3

.dw 4,4,4,4,4,4,4,4,4,5,5,5,5,5,5,5

.dw 5,5,6,6,6,6,6,6,6,6,6,7,7,7,7,7

.dw 7,7,7,7,8,8,8,8,8,8,8,8,8,9,9,9

.dw 9,9,9,9,9,9,10,10,10,10,10,10,10,10,10,11

.dw 11,11,11,11,11,11,12,12,12,12,12,12,12,12,13,13

.dw 13,13,13,13,13,14,14,14,14,14,14,14,15,15,15,15

.dw 15,15,16,16,16,16,16,16,17,17,17,17,17,17,18,18

.dw 18,18,18,18,19,19,19,19,19,19,20,20,20,20,20,21

.dw 21,21,21,21,22,22,22,22,22,23,23,23,23,23,24,24

.dw 24,24,24,25,25,25,25,26,26,26,26,26,27,27,27,27

.dw 28,28,28,28,29,29,29,29,29,30,30,30,30,31,31,31

.dw 31,32,32,32,32,33,33,33,34,34,34,34,35,35,35,35

.dw 36,36,36,37,37,37,37,38,38,38,39,39,39,39,40,40

.dw 40,41,41,41,42,42,42,42,43,43,43,44,44,44,45,45

.dw 45,46,46,46,47,47,47,48,48,48,49,49,49,50,50,50

.dw 51,51,51,52,52,52,53,53,54,54,54,55,55,55,56,56

.dw 56,57,57,58,58,58,59,59,60,60,60,61,61,61,62,62

.dw 63,63,63,64,64,65,65,66,66,66,67,67,68,68,68,69

.dw 69,70,70,71,71,72,72,72,73,73,74,74,75,75,76,76

.dw 76,77,77,78,78,79,79,80,80,81,81,82,82,83,83,84

.dw 84,85,85,86,86,86,87,87,88,89,89,90,90,91,91,92

.dw 92,93,93,94,94,95,95,96,96,97,97,98,98,99,100,100

.dw 101,101,102,102,103,103,104,105,105,106,106,107,107,108,109,109

.dw 110,110,111,111,112,113,113,114,114,115,116,116,117,118,118,119

.dw 119,120,121,121,122,122,123,124,124,125,126,126,127,128,128,129

.dw 129,130,131,131,132,133,133,134,135,135,136,137,137,138,139,140

.dw 140,141,142,142,143,144,144,145,146,146,147,148,149,149,150,151

.dw 152,152,153,154,154,155,156,157,157,158,159,160,160,161,162,163

.dw 163,164,165,166,166,167,168,169,170,170,171,172,173,173,174,175

.dw 176,177,177,178,179,180,181,182,182,183,184,185,186,186,187,188

.dw 189,190,191,192,192,193,194,195,196,197,197,198,199,200,201,202

.dw 203,204,204,205,206,207,208,209,210,211,212,213,213,214,215,216

.dw 217,218,219,220,221,222,223,224,225,225,226,227,228,229,230,231

.dw 232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247

.dw 248,249,250,251,252,253,254,255,256,257,258,259,260,261,262,263

.dw 264,265,266,268,269,270,271,272,273,274,275,276,277,278,279,280

.dw 281,283,284,285,286,287,288,289,290,291,293,294,295,296,297,298

.dw 299,300,302,303,304,305,306,307,309,310,311,312,313,314,316,317

.dw 318,319,320,322,323,324,325,326,328,329,330,331,332,334,335,336

.dw 337,339,340,341,342,344,345,346,347,349,350,351,352,354,355,356

.dw 357,359,360,361,363,364,365,367,368,369,370,372,373,374,376,377

.dw 378,380,381,382,384,385,386,388,389,391,392,393,395,396,397,399

.dw 400,402,403,404,406,407,408,410,411,413,414,416,417,418,420,421

.dw 423,424,426,427,428,430,431,433,434,436,437,439,440,442,443,445

.dw 446,448,449,451,452,454,455,457,458,460,461,463,464,466,467,469

.dw 470,472,473,475,476,478,480,481,483,484,486,487,489,491,492,494

.dw 495,497,499,500,502,503,505,507,508,510,511,513,515,516,518,520

.dw 521,523,525,526,528,530,531,533,535,536,538,540,541,543,545,546

.dw 548,550,552,553,555,557,558,560,562,564,565,567,569,571,572,574

.dw 576,578,579,581,583,585,587,588,590,592,594,596,597,599,601,603

.dw 605,606,608,610,612,614,616,617,619,621,623,625,627,629,630,632

.dw 634,636,638,640,642,644,646,647,649,651,653,655,657,659,661,663

.dw 665,667,669,671,673,674,676,678,680,682,684,686,688,690,692,694

.dw 696,698,700,702,704,706,708,710,712,714,716,719,721,723,725,727

.dw 729,731,733,735,737,739,741,743,745,748,750,752,754,756,758,760

.dw 762,764,767,769,771,773,775,777,779,782,784,786,788,790,792,795

.dw 797,799,801,803,806,808,810,812,814,817,819,821,823,826,828,830

.dw 832,835,837,839,841,844,846,848,851,853,855,857,860,862,864,867

.dw 869,871,874,876,878,881,883,885,888,890,892,895,897,899,902,904

.dw 907,909,911,914,916,919,921,923,926,928,931,933,936,938,940,943

.dw 945,948,950,953,955,958,960,963,965,968,970,973,975,978,980,983

.dw 985,988,990,993,995,998,1000,1003

.dw 1005,1008,1011,1013,1016,1018,1021,1023


Einzig die Include-Zeile für den jeweiligen AVR-µC (hier wiederum ein ATmega328P), die Interrupt-Sprungtabelle und die Stack-Initialisierung sind nicht im Quellprogramm dargestellt und müssen je nach verwendetem AVR-µC entsprechend ergänzt werden.

 

Komplement

 

Im vorliegenden Beispiel wird die Helligkeit zwischen links und rechts linear aufgeteilt und zwar so, dass die Summe immer 100% ist. D.h. wenn beim linken Kanal 0% eingestellt ist (also komplett dunkel), ergeben sich für den rechten Kanal 100% (also maximal hell).

 

Bei 10-bit-Auflösung des A/D-Wandlers ergibt sich folgende Formel:

 

Helligkeit_rechts = 0x3ff - Helligkeit_links

 

Aus den beiden Werten Helligkeit_links und Helligkeit_rechts wird das PWM-Tastverhältnis (also der LED-Strom) für beide Kanäle getrennt gemäß Luminanz-Kennlinie per Software bestimmt.

 

D.h., dass bei 50% Helligkeit beide Kanäle für einigermaßen richtiges Helligkeitsempfinden nur ca. 18,35% PWM-Tastverhältnis bzw. LED-Strom haben.

 

Screen-Shots

 

Hier ein paar Oszillogramme, zunächst beide Kanäle gleich hell eingestellt (Poti in Mittenstellung, Kanal #1 = 0x1ff, Kanal #2 = 0x200), wodurch sich ca. 18,35% PWM-Tastverhältnis ergeben:

 

CrossFader18,35

 

 

Hier Kanal #1 mit einem Helligkeitswert von 0xff (bzw. 4,4% LED-Strom) und Kanal #2 = 0x300:

 

Crossfader4,4

 

Und hier noch die entgegengesetzte Einstellung, nämlich Kanal # 1 = 0x2ff (bzw. 48,3% LED-Strom) und Kanal #2 = 0x100:

 

CrossFader48,3

 

Bei Poti-Anschlag links oder rechts ist einer der beiden LED-Treiber 100% ein und der andere 100% aus, so wie es sein soll.

 

Enable oder Shutdown

 

Falls die beiden mittels PWM angesteuerten LED-Treiber statt einem Enable-Eingang einen Shutdown-Eingang haben, muss man die PWM des µC per TCCR1A eben statt "non-inverting" als "inverting" konfigurieren. Der Rest des Programms bleibt unverändert.

 

Statt Poti

 

Dank µC ist man bezüglich Bedienelement natürlich sehr flexibel.

 

So könnte man zum Umblenden statt einem Potenziometer auch zwei entprellte Taster "Umblenden nach rechts" bzw. "Umblenden nach links" nehmen, oder gleich einen Mini-Joy-Stick oder Dreh-Encoder, mit deren Taster man den jeweils eingestellten Wert bei Bedarf in das EEPROM des µC abspeichert, damit die gewünschte Einstellung beim nächsten Einschalten erhalten bleibt (es sei denn, man wünscht eine beliebige andere Voreinstellung beim Einschalten).

 

Das Abspeichern der aktuellen Einstellung kann man sich bei Verwendung eines Potis natürlich ersparen.

 

Tunable White

 

Anstatt zwischen zwei gleichfarbigen LEDs zu faden, dürfen es natürlich auch LEDs mit unterschiedlichen Farben sein - jeweils per eigenem LED-Treiber mit PWM- bzw. Enable-Eingang versorgt, so dass man die beiden LED-Fader-Ausgänge dort anschließen kann.

 

Und sind es weiße LEDs mit unterschiedlicher Farbtemperatur (also warmweiß und kaltweiß), hat man auch schon das nahezu perfekte "Tunable White".

 

nach oben

 


 

LED-Crossfader phasenverschoben für Tunable White (5.5.2019)

 

Wie in obigen Screen-Shots für den LED-Crossfader ersichtlich, liegen die Mitten der beiden PWM-Ausgänge aufgrund des verwendeten Phase-Correct-Modes prinzipbedingt exakt übereinander, was im Normalfall bei der Ansteuerung zweier unabhängigen LED-Treiber nicht relevant ist.

 

Bei "Tunable White" gibt es vereinzelt antiparallel geschaltete weiße LEDs, die per zwei Adern und abwechselnder Polarität versorgt werden.

 

Das bedeutet, dass man dann zur Ansteuerung einen LED-Treiber benötigt, der entweder als Vollbrücke oder aus zwei Halbbrücken aufgebaut ist. Wird damit schlicht die Versorgungsspannung geschaltet bzw. umgepolt, benötigen die angeschlossenen LEDs bzw. der LED-Strang natürlich einen Vorwiderstand.

 

Und genau für eine solche Ansteuerung werden zwei um 180° gegeneinander phasenverschobene[1] PWM-Signale benötigt.

 

Für den zweiten PWM-Port lässt sich dies in obigem Programm mit Luminanz-Korrektur einfach durch zwei kleine Eingriffe erreichen, nämlich zum einen durch eine Negierung des zweiten PWM-Ausgangs (Teil der PWM-Initialisierung) und zum anderen einer Differenzbildung des über LumiTable gefundenen PWM-Tastverhältnisses zum 100%-Wert (sprich zu 0x3FF bei 10 Bit Auflösung) des zweiten PWM-Ausgangs.

 

Die beiden PWM-Signale schauen für unterschiedliche Tastverhältnisse dann wie folgt aus, zunächst bei Poti in Mittenstellung, Kanal #1 = 0x1ff, Kanal #2 = 0x200:

 

CrossF50

 

Dann Kanal #1 mit einem Helligkeitswert von 0xff (bzw. Poti bei 25% mit 4,4% LED-Strom) und Kanal #2 = 0x300:

 

CrossF25

 

Und schließlich Kanal # 1 = 0x2ff (bzw. Poti bei 75% und 48,3% LED-Strom) und Kanal #2 = 0x100:

 

CrossF75

 

Bei Links- bzw. Rechtsanschlag des Potis ist natürlich jeweils der eine Kanal vollständig AUS und der andere vollständig EIN.

 

Dem aufmerksamen Betrachter fällt auf, dass die LOW-Pegel der beiden Signale immer dann geringfügig angehoben sind (und das Rauschen etwas geringer wird), wenn das jeweils andere Signal HIGH-Pegel hat.

 

Das liegt daran, dass bei meinem Testaufbau keine separate H-Brücke verwendet wurde, sondern die antiparallelen LEDs nebst Vorwiderstand einfach zwischen den beiden Port-Pins angeschlossen sind, was ja auch einer Brückenschaltung entspricht.

 

Der kleine Spannungssprung ist durch den Spannungsabfall in den Ausgangstransistoren des ATmega328P auf der GND-Seite durch den (relativ kleinen) LED-Strom bedingt.

 

Da ich für diese Messungen den ATmega328P mit 16 MHz getaktet habe, wurden auch die Einstellungen für den ADC- und PWM-Vorteiler angepasst, wodurch sich eine PWM-Frequenz von 122 Hz ergibt.

 

Deshalb hier das vollständige s’AVR-Quellprogramm (wiederum ohne Interrupt-Sprungtabelle und Stack-Initialisierung, die wesentlichen Unterschiede sind rot markiert):

 

.def rmp = r16         ; multi purpose register

.def ADCresultL = r20  ; ADC result

.def ADCresultH = r21  ; ADC result

.def PWMvalueL = r22   ; value for PWM

.def PWMvalueH = r23   ; value for PWM

 

clr  rmp

out  DDRC,rmp          ; all Port C pins are inputs (default)

 

ldi  rmp,(1<<REFS0 | 1<< MUX2 | 1<< MUX0) ; ADC (using pin ADC5 at PORTC,5)

sts  ADMUX,rmp         ; select VCC ref, ADC5

clr  rmp

sts  ADCSRB,rmp        ; ADC free running mode (default setting)

ldi  rmp,1<<ADC5D      ; 0b0010_0000

sts  DIDR0,rmp         ; disable digital in for ADC5

ldi  rmp,(1<<ADEN | 1<<ADSC | 1<<ADPS2 | 1<<ADPS1 | 1<<ADPS0) ; 0b1100_0111

     ; enable ADC, prescaler /128 for 125 kHz sampling rate @ 16 MHz,

     ; for free running mode ADSC must be set as well!

sts  ADCSRA,rmp

     ; PWM using TC1 at pin 15 and pin 16 (OC1A at PORTB,1 and OC1B at PORTB,2):

     ; non-inverted mode port A, inverting mode port B, 10 bit PWM

ldi  rmp,(1<<COM1A1 | 1<<COM1B1 | 1<<COM1B0 | 1<<WGM11 | 1<<WGM10)

sts  TCCR1A,rmp

     ; WGM12/13 = 0 should be Phase Correct PWM mode,

     ; prescaler /64 results in 244 Hz/2 = 122 Hz PWM @ 16 MHz

ldi  rmp,(1<<CS11 | 1<<CS10)

sts  TCCR1B,rmp

 

sbi  DDRB,1            ; enable OC1A as output (Port B1, pin 15)

sbi  DDRB,2            ; enable OC1B as output (Port B2, pin 16)

cbi  PORTB,1           ; prepare Port B1 for LOW if set as input

cbi  PORTB,2           ; prepare Port B2 for LOW if set as input

 

LOOP                   ; main loop (LOOP is not an address!)

  lds   rmp,ADCSRA     ; read ADCSRA

  sbr   rmp,1<<ADSC    ; set start ADC bit

  sts   ADCSRA,rmp     ; write back ADCSRA

  REPEAT

    lds  rmp,ADCSRA    ; check ADCSRA

  UNTIL !rmp,ADSC      ; ADSC cleared after ADC ready

  lds  ADCResultL,ADCL ; get ADC low byte

  lds  ADCResultH,ADCH ; get ADC high byte

 

  ldi  zl,low(2*LumiTable)

  ldi  zh,high(2*LumiTable)

  movw xl,ADCResultL   ; MOVW instead of 2x MOV

  lsl  xl              ; *2 due to word data

  rol  xh              ; *2 due to word data

  add  zl,xl

  adc  zh,xh

  lpm  PWMvalueL,z+    ; get corrected value for PWM ...

  lpm  PWMvalueH,z     ; ... from LumiTable

 

  sts  OCR1AH,PWMvalueH ; initiate PWM, high byte first

  sts  OCR1AL,PWMvalueL

 

  ldi  rmp,0xff        ; now handle complementary PWM channel

  sub  rmp,ADCResultL  ; get 0xffff - ADCResult for channel B

  mov  ADCResultL,rmp

  ldi  rmp,0x03        ; take only 10 bits into account

  sbc  rmp,ADCResultH

  mov  ADCResultH,rmp  ; now complementary value available

 

  ldi  zl,low(2*LumiTable)

  ldi  zh,high(2*LumiTable)

  lsl  ADCResultL      ; *2 due to word data

  rol  ADCResultH      ; *2 due to word data

  add  zl,ADCResultL

  adc  zh,ADCResultH

  lpm  PWMvalueL,z+    ; get corrected value for PWM ...

  lpm  PWMvalueH,z     ; ... from LumiTable

 

  ; Make the 10-bit complement of calculated Port B PWM value

  ; to get 180° phase shift against Port A:

  ldi  rmp,0xff        ; now handle complementary PWM channel

  sub  rmp,PWMvalueL   ; get 0xffff - ADCResult for channel B

  mov  PWMvalueL,rmp

  ldi  rmp,0x03        ; take only 10 bits into account

  sbc  rmp,PWMvalueH

  mov  PWMvalueH,rmp   ; now complementary value available

 

  ; finally store PWM values for Port B:

  sts  OCR1BH,PWMvalueH ; initiate PWM, high byte first

  sts  OCR1BL,PWMvalueL

 

  rcall Delay10ms      ; insert a short delay if no additional code follows

ENDL                   ; main loop (forever)

 

Falls jemand das Programm in reinem AVR-Assembler schreiben will, hier ein Ausschnitt der Hauptschleife (LOOP bis ENDL) mit dem von s’AVR erzeugten flachen ASM-Code (rot markiert):

 

  ;01// LOOP           ; main loop (LOOP is not an address!)

_L1:

  lds rmp,ADCSRA       ; read ADCSRA

  sbr rmp,1<<ADSC      ; set start ADC bit

  sts ADCSRA,rmp       ; write back ADCSRA

  ;02// REPEAT

_L4:

  lds rmp,ADCSRA       ; check ADCSRA

  ;02// UNTIL !rmp,ADSC ; ADSC cleared after ADC ready

  SBRC rmp,ADSC

  RJMP _L4

  lds ADCResultL,ADCL  ; get ADC low byte

  lds ADCResultH,ADCH  ; get ADC high byte

  .

  .

  .

  rcall Delay10ms

  ;01// ENDL           ; main loop (forever)

  RJMP _L1

 

Der Rest ist identisch mit dem s’AVR-Quellprogramm, einschließlich der großen Tabelle für die Luminanz-Korrektur (LumiTable) wie im Programm ganz oben.

 

Weniger LED-Spitzenstrom

 

Ein schöner Nebeneffekt der phasenverschobenen PWM-Signale ist der halbe LED-Spitzenstrom im Vergleich zu den gleichphasigen PWM-Signalen. Der mittlere LED-Strom ist natürlich in beiden Fällen derselbe.

 

Und selbstverständlich kann man mit den phasenverschobenen PWM-Signalen genau so gut zwei einzelne LED-Treiber ansteuern.

 

nach oben

 


 

[1] Zumindest dürfen die beiden PWM-Signale nicht gleichzeitig aktiv sein.