Diskussion:LED-Fading

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

Hinweise zu Anpassungen an andere AVRs als ATmega32

Sparsamere Implementierung

Hallo, ich habe vorhin auch einen Farbverlauf implementiert, und erst im Nachhinein den Artikel gesehen. Soweit kein Problem, da mir die theoretischen Hintergründe eh bekannt sind. Auch ich habe eine exponentielle Korrekturkurve verwendet, da diese für das Faden von LEDs ausreicht und sich bedeutend effizienter als eine Gamma-Kurve implementieren lässt. Da mein Code wesentlich platzsparender ist (ich verwende ATtiny2x MCUs) werde ich den hier mal präsentieren.

Ich verwende in meinem Programm ein 7 Bit (0..127) auf 8 Bit (1..255) Mapping. Das ist noch recht kompakt und sehr speichereffizient, und die Stufen sind kaum sichtbar. Soweit ich das aus dem optimierten Code sehen kann, braucht er 8 zusätzliche Taktzyklen und 28 Byte an Speicher.

uint8_t exptable4[16] PROGMEM =
  {133, 139, 145, 151, 158, 165, 172, 180,
  188, 196, 205, 214, 224, 234, 244, 255};

inline uint8_t expvalue7( const uint8_t linear )
{/* Returns the exponential value (approx. 1.0443^x).                        *
  * argument: 7 bit unsigned (0..127)  return: 8 bit unsigned (1..255)       */
  // look up exponential
  uint8_t exp = pgm_read_byte(&exptable4[ linear % 16 ]);
  // scale magnitude
  return exp >> (7 - linear / 16);
}

Ich speichere also nur die Zwischenwerte für einen Bereich von Max bis Max/2. Kleinere Werte kann ich dann sehr schnell durch Schieben der Zahlen erhalten.

Die Exponentialfunktion hat die Eigenschaft, dass exp(0) == 1. Daher bietet es sich für LEDs an, entweder bei einem Sollwert von 0 das PWM auf 0 zu stellen, oder einfach vom Ergebnis eins abzusiehen. So werden die LEDs auch dunkel (ggf. mit allgemeiner Einschränkung des PWM).

Die 127 Stufen sind bei 8-Bit schon sehr verschwenderisch, aber es kostet aber fast nix (8 Byte mehr als 64er, bzw. 12 Byte mehr als 32er).

Falls sich jetzt jemand fragt, wie ich die Wertetabelle erstellt habe: Dazu habe ich einfach 2^(7+n/16) ausgerechnet. Die 7 sind log2(res)-1 also entsprechend für uint8_t. Die Anzahl der Stufen (hier 16) kann beliebig gewählt werden, wobei 2er Potenzen in Schiebefunktionen umgewandelt werden, und daher (gerade auf ATtinys) sehr viel schneller berechnet werden.

Wer möglichst jedes Byte einsparen will, der wird so etwas verwenden:

uint8_t exptable2[4] PROGMEM = {151, 180, 214, 255};

inline uint8_t expvalue5( const uint8_t linear )
{/* Returns the exponential value (approx. 1.19^x).                          *
  * argument: 5 bit unsigned (0..31)  return: 8 bit unsigned (1..255)        */
  // look up exponential
  uint8_t exp = pgm_read_byte(&exptable2[ linear % 4 ]);
  // scale magnitude
  return exp >> (7 - linear / 4);
}

Wer dagegen bei einem 16-Bit Timer Ressourcen weniger Beachtung schenkt, wird vielleicht eher diese Zeilen verwenden.

uint8_t exptable5[32] PROGMEM =
  {130, 133, 136, 139, 142, 145, 148, 151, 155, 158, 161, 165, 169, 172, 176, 180,
  184, 188, 192, 196, 201, 205, 210, 214, 219, 224, 229, 234, 239, 244, 249, 255};

inline uint16_t expvalue9(const uint16_t linear)
{/* Returns the exponential value (approx. 1.0219^x).                          *
  * argument: 9 bit unsigned (0..511)  return: 16 bit unsigned (1..65280)    */
  // look up exponential
  uint16_t exp = pgm_read_byte(&exptable5[ linear % 32 ]) << 8;
  // scale magnitude
  return exp >> (15 - linear / 32);
}

Man könnte das Ganze noch weiter treiben, doch ist sicher für das Auge sicher keine Verbesserung mehr wahrnehmbar. Eine Speicherung der Werte als uint16_t ist eigentlich nie sinnvoll, da uint8_t bis 10 Bit Eingangsauflösung nicht schlechter ist (Schritte > 1 LSB). Eine Ausgangsauflösung von 16 Bit kann schon ein 10 Bit Eingangssignal etwa in der unteren Hälfte (1..428) nicht mehr auflösen (Schritte < 1 LSB).

Ach noch was: Ich bin neu auf dieser Seite (und auch mit AVR Programmierung). Daher wollte ich nicht enfach umfangreiche Änderungen in den Artikel einpflegen. Wenn jemand hier etwas mehr zu Hause ist, dann kann er gerne diesen Code in den Artikel einpflegen.

Diskussion wissenschaftl.-technischer Hintergrund

Das Helligkeitsempfinden des Auges ist NICHT logarithmisch!

Es genügt der Gleichung (für nahezu alle unsere Anwendungsfälle):

E = R ^ γ

wobei:

E = Empfinden

R = Reizintensität (Tastverhältnis der PWM)

γ = Gammakorrekturwert

Je nach Größe der Lichtquelle wählt man:

γ = 0.5 für punktförmige oder aufblitzende Helligkeiten

γ = 0.33 für Lichtquellen bei 5° Blickwinkel

γ = 1/2.2 ist meine Empfehlung für diffus strahlende LEDs - entspricht der Gamma-korrektur von VGA-Bildschirmen - hat in unseren Selbstversuchen hervorragende Ergebnisse geliefert

Die umgekehrte Look-Up-Wertetabelle ergibt sich aus:

R = E ^ 1/γ (wie man sieht auch hier KEIN Logarithmus! Der Exponent ist fix!)

oder für diskrete Werte:

R[i] = round((z - 1) * (i / (n - 1)) ^ (1/γ))

oder (je nachdem welche Rundungsfunktion verfügbar ist)

R[i] = floor((z - 1) * (i / (n - 1)) ^ (1/γ) + 0.5)

mit

n = Anzahl der Einträge in der Look-Up-Tabelle (z.B. 256)

i = Laufindex [0..n-1]

z = Anzahl der linearen Stufen, die die PWM-Routine wiedergeben kann (z.B. 65536)

Oder eingesetzt für den typischen Anwendungsfall:

R[i] = floor(65535.0 * (i / 255.0) ^ 2.2 + 0.5)

In Wikipedia ist's übrigens auch falsch beschrieben:

Der Artikel Gammakorrektur verweist fälschlicher Weise auf das Weber-Fechner-Gesetz welches den Logarithmus zur Grundlage hat. Weiter unten im Weber-Fechner-Gesetz-Artikel findet sich der Hinweis, das die Stevensschen Potenzfunktionen besser geeignet seien. In besagtem Artikel finden sich dann auch wieder die Verallgemeinerungen der Gamma-Korrektur wieder. Im Englischen Artikel zum Stevensschen Potenzgesetz sind auch einige Exponenten für die Reizfunktionen zu finden.

Komme mir hier im Forum manchmal wie ein Geisterfahrer vor... das wird einfach so oft falsch verwendet...

Ich hoffe mal, dass sich hier noch Gleichgesinnte einfinden und jemand (im Einverständnis mit dem Originalautor) den Artikel entsprechend anpasst - oder die entsprechende Gegenaussage begründen kann.

in diesem Sinne... schönen Restabend noch!


Ich bin der Autor des Artikels. Was die biologisch-physiologische Seite des Problems angeht, bin ich sicher nicht die Fachkraft, meine Kenntnisse bauen auch nur auf Wikipedia & Co auf. Wobei der praktische Unterschied zwischen Weber Fechner und Stevenssche Potenzfunktion relativ gering ist und bestenfalls für die Profis interessant ist. Aber meinestwegen kann der Artikel von dir dahingehend angepasst werden. Man lernt ja nie aus ;-)

MfG Falk


Bob Pease hat mal den Dynamikumfag des Auges abgeschätzt

http://electronicdesign.com/Articles/Index.cfm?AD=1&AD=1&ArticleID=6059

"What's All This Optical Stuff, Anyhow?" Er rechnet mit dB, also logarithmischen Maßen, und kommt auf 145 dB. Er gibt allerdings zu, dass er in optischen Maßeinheiten nicht so bewandert ist.


Danke euch für das schnelle Feedback. Ich schau mal, dass ich 'nen Millimeter Zeit finde um den Artikel (und die Wertetabellen) anzupassen.

Der Unterschied ist wirklich nicht groß, aber wenn's passende Formeln gibt, die zu dem nicht schwieriger zu rechnen sind, sollten die meiner Meinung nach verwendet werden. Die Unterschiede sind leider gerade im unteren Helligkeitsbereich nicht ohne :( Das Auge könnte da ruhig was kooperativer sein.

Danke und n8

---

Ihr macht folgenden Denkfehler: Die LED wird nicht alleine betrachtet sondern mitsamt ihrer Umgebunb! Daher passt sich das Auge insgesamt nicht stark an, wenn die LEd die Helligkeit ändert - höchstens lokal ist das im Geringen Umfang der Fall. Damit gilt weder die globale Logarithmische Empfindlichkeit, noch kann man Linearität unterstellen.

Der Umstand, dass eine PWM beim linearen durchfahren keinen linearen H-Verluf produziert, liegt zudem an der LED! Das kann man leicht nachmessen.

Danke!

Erstmal herzlichen Dank für diesen Artikel! Der hat mir schon sehr geholfen. Mir ist schon klar, dass das Hauptaugenmerk auf der Helligkeitsverteilung liegt. Was mir nur beim Studieren des Programms schwer gefallen ist, war die Initialisierung des Timers. Dort wird immer das ganze Register auf einmal geschrieben, mit einem Wert in Hex-Form. Könnte man das nicht ändern in die übliche Schreibweise register = (1<<BITNAME) | (1<<NOCHNBIT)? Dann wüsste man sofort welche Bits gesetzt sind und welche nicht. Ich würde mich auch dafür zur VErfügung stellen, das alles umzurechnen/nachzuschlagen. Was hälst du davon, Falk?

LG, Björn


Mach mal, klingt vernünftig. Aber bitte dann den Code REAL testen, damit sich keine Fehler einschleichen.

MFg Falk


>Interessant wäre ja noch, ob denn die LED nicht auch noch ein unlineares >Verhalten hat.

Hat sie, ist aber für diese Anwendung verschwindend gering.

> Die ist ja ein Diode. Im "Knick" der LED steigt doch die Helligkeit auch > nicht linear an. Ist das bei den Formeln schon berücksichtigt?

Welcher "Knick"? Die Strom-Spannungskennlinie? Ist bei LEDs im Wesentlichen uninteressant. Die werden sowiso mit einer Konstantstromquelle betrieben, siehe Artikel LED.

MfG Falk


Stimmt, war ein Denkfehler von mir. --> Ist ja PWM-Betrieb, nicht linear...