Korrektur für nichtlineare Helligkeit in LEDs bei Verwendung von PWM

33

Wenn Sie eine LED mit PWM betreiben, skaliert die Helligkeit (wie ich sie sehe) nicht linear mit dem Tastverhältnis. Die Helligkeit steigt langsam an und steigt dann mit dem Arbeitszyklus exponentiell an.

Kann jemand eine Faustregel zur Verwendung als Korrekturfaktor oder eine andere Problemumgehung vorschlagen?

Toby Jaffey
quelle
Als ich ein Paar Knight Rider-Manschettenknöpfe gemacht habe, musste ich x ^ 10 verwenden, damit das Ausblenden schön aussieht!
Rocketmagnet
3
Sind Sie sicher, dass es nicht "die Helligkeit zunächst exponentiell ansteigt und dann nur langsam ansteigt"?
Dmitry Grigoryev
1
Ich glaube, unsere Augen reagieren logarithmisch auf Helligkeit.
DKNguyen

Antworten:

13

Für 16 Ebenen ist es einfach, eine einfache Nachschlagetabelle "von Hand" zu erstellen und den 4-Bit-Wert in einen 8-Bit-Wert zu konvertieren, der an den PWM-Controller übergeben wird. Dies ist die Komponente, die ich in meinem FPGA-LED-Array-Treiber verwendet habe. Für einen 8-Bit-Level-Controller benötigen Sie mindestens eine 11-12-Bit-Ausgabe aus der Nachschlagetabelle.

library IEEE;
use IEEE.Std_logic_1164.all;

entity Linearize is
port ( reqlev : in std_logic_vector (3 downto 0) ;
    pwmdrive : out std_logic_vector (7 downto 0) );
    end Linearize;

architecture LUT of Linearize is
    begin
    with reqlev select
        pwmdrive <= "00000000" when "0000",
                    "00000010" when "0001",
                    "00000100" when "0010",
                    "00000111" when "0011",
                    "00001011" when "0100",
                    "00010010" when "0101",
                    "00011110" when "0110",
                    "00101000" when "0111",
                    "00110010" when "1000",
                    "01000001" when "1001",
                    "01010000" when "1010",
                    "01100100" when "1011",
                    "01111101" when "1100",
                    "10100000" when "1101",
                    "11001000" when "1110",
                    "11111111" when "1111",
                    "00000000" when others;
    end LUT;
Axeman
quelle
Ich versuche genau herauszufinden, wie Ihre Formel lautet. Es ist bemerkenswert nahe an f (x) = x ^ 2, aber die Kurve ist nicht tief genug. f (x) = x ^ 3/13 bringt mich viel näher.
ajs410,
Es ist keine Formel (nicht absichtlich) ... Ich habe die Anfangswerte des Linearisierers nur erraten :-). Ich habe dann das Array mit Strom versorgt, LED-Spalten in Helligkeitsreihenfolge angesteuert und die Werte angepasst, um eine gleichmäßige Rampe zu erzielen. Mit nur 16 Levels ist es wirklich einfach.
Axeman
1
2n1
17

Theoretisch sollte es exponentiell sein, aber ich habe die besten Ergebnisse beim Fading mit einer quadratischen Funktion erzielt.

Ich denke auch, dass Sie es falsch verstanden haben. Bei niedrigem Arbeitszyklus ist die wahrgenommene Helligkeitszunahme viel größer als bei fast vollem Arbeitszyklus, bei dem die Helligkeitszunahme fast nicht wahrnehmbar ist.

sternenblau
quelle
Siehe auch Gammakorrektur .
sternenblau
17

Ich habe mich in den letzten Tagen mit diesem Thema befasst, da ich das gleiche Problem habe. Ich habe versucht, LEDs mit PWM sichtbar linear zu dimmen, aber ich möchte eine volle Auflösung von 256 Schritten. Es ist keine leichte Aufgabe, 256 Zahlen zu erraten, um manuell eine Kurve zu erstellen!

Ich bin kein erfahrener Mathematiker, aber ich weiß genug, um einige grundlegende Kurven durch Kombinieren einiger Funktionen und Formeln zu erzeugen, ohne wirklich zu wissen, wie sie funktionieren. Ich finde, dass Sie mit einer Tabelle (ich habe Excel verwendet) mit einer Reihe von Zahlen von 0 bis 255 herumspielen, ein paar Formeln in die nächste Zelle einfügen und sie grafisch darstellen können.

Ich verwende pic assembler, um das Ausblenden zu erledigen, und Sie können sogar die Tabelle dazu bringen, den Assembler-Code mit einer Formel ( ="retlw 0x" & DEC2HEX(A2)) zu generieren . Dies macht es sehr schnell und einfach, eine neue Kurve auszuprobieren.

Nachdem ich ein bisschen mit den LOG- und SIN-Funktionen, dem Durchschnitt der beiden und ein paar anderen Dingen herumgespielt hatte, konnte ich nicht wirklich die richtige Kurve finden. Was passiert ist, dass der mittlere Teil der Überblendung langsamer war als die unteren und oberen Ebenen. Wenn auf ein Abblenden sofort ein Abblenden folgt, war ein scharfer, wahrnehmbarer Anstieg der Intensität zu verzeichnen. Was (meiner Meinung nach) benötigt wird, ist eine S-Kurve.

Eine schnelle Suche in Wikipedia ergab die Formel für eine S-Kurve. Ich habe dies in meine Tabelle eingefügt und einige Anpassungen vorgenommen, um es über meinen Wertebereich zu multiplizieren.

S-Kurve

Ich habe es auf meinem Rig getestet und es hat wunderbar funktioniert.

Die von mir verwendete Excel-Formel lautete wie folgt:

=1/(1+EXP(((A2/21)-6)*-1))*255

Dabei ist A2 der erste Wert in Spalte A, der A3, A4, ..., A256 für jeden Wert erhöht.

Ich habe keine Ahnung, ob dies mathematisch korrekt ist oder nicht, aber es liefert die gewünschten Ergebnisse.

Hier ist der vollständige Satz von 256 Levels, die ich verwendet habe:

0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
0x02, 0x02, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x04, 0x04, 0x04, 0x04, 0x04, 0x05, 0x05, 0x05,
0x05, 0x06, 0x06, 0x06, 0x07, 0x07, 0x07, 0x08, 0x08, 0x08, 0x09, 0x09, 0x0A, 0x0A, 0x0B, 0x0B,
0x0C, 0x0C, 0x0D, 0x0D, 0x0E, 0x0F, 0x0F, 0x10, 0x11, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1F, 0x20, 0x21, 0x23, 0x24, 0x26, 0x27, 0x29, 0x2B, 0x2C,
0x2E, 0x30, 0x32, 0x34, 0x36, 0x38, 0x3A, 0x3C, 0x3E, 0x40, 0x43, 0x45, 0x47, 0x4A, 0x4C, 0x4F,
0x51, 0x54, 0x57, 0x59, 0x5C, 0x5F, 0x62, 0x64, 0x67, 0x6A, 0x6D, 0x70, 0x73, 0x76, 0x79, 0x7C,
0x7F, 0x82, 0x85, 0x88, 0x8B, 0x8E, 0x91, 0x94, 0x97, 0x9A, 0x9C, 0x9F, 0xA2, 0xA5, 0xA7, 0xAA,
0xAD, 0xAF, 0xB2, 0xB4, 0xB7, 0xB9, 0xBB, 0xBE, 0xC0, 0xC2, 0xC4, 0xC6, 0xC8, 0xCA, 0xCC, 0xCE,
0xD0, 0xD2, 0xD3, 0xD5, 0xD7, 0xD8, 0xDA, 0xDB, 0xDD, 0xDE, 0xDF, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5,
0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xED, 0xEE, 0xEF, 0xEF, 0xF0, 0xF1, 0xF1, 0xF2,
0xF2, 0xF3, 0xF3, 0xF4, 0xF4, 0xF5, 0xF5, 0xF6, 0xF6, 0xF6, 0xF7, 0xF7, 0xF7, 0xF8, 0xF8, 0xF8,
0xF9, 0xF9, 0xF9, 0xF9, 0xFA, 0xFA, 0xFA, 0xFA, 0xFA, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFC,
0xFC, 0xFC, 0xFC, 0xFC, 0xFC, 0xFC, 0xFC, 0xFC, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD,
0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFF, 0xFF
BG100
quelle
Diese Gleichung hat bei mir perfekt funktioniert.
Ignacio Vazquez-Abrams
4

Ich benutzte einen ATtiny, um mein Deck anzuzünden. Die Helligkeit wird mit einem Poti gesteuert, der an den ADC-Pin angeschlossen ist.

Versuchte Exponentialfunktion und die darauf basierende PWM-Ausgabe scheinen eine lineare Zunahme der wahrgenommenen Helligkeit zu ergeben.

Ich habe diese Formeln benutzt:

out = pow(out_max, in/in_max)

Attiny85 @ 8MHz benötigte ungefähr 210us, um die obige Berechnung durchzuführen. Erstellen Sie eine Nachschlagetabelle, um die Leistung zu verbessern. Da die Eingabe vom 10-Bit-ADC erfolgte und der ATtiny-Speicher begrenzt ist, wollte ich auch eine kürzere Tabelle erstellen.

Anstatt eine Lookup-Tabelle mit 1024 Einträgen zu erstellen, erstellen Sie eine Reverse-Lookup-Tabelle mit 256 Einträgen (512 Byte) im Programmspeicher (PGMEM). Es wurde eine Funktion geschrieben, um eine binäre Suche in dieser Tabelle durchzuführen. Diese Methode benötigt nur 28uS für jede Suche. Wenn ich eine direkte Nachschlagetabelle verwende, würde dies 2 KB Arbeitsspeicher erfordern, die Nachschlagetabelle würde jedoch nur etwa 4 US-Dollar benötigen.

Die berechneten Werte in der Nachschlagetabelle verwenden nur den Eingangsbereich 32-991, wobei der untere / obere Bereich des ADC verworfen wird, falls ein Problem mit der Schaltung vorliegt.

Unten ist, was ich jetzt habe.

// Anti_Log-Testprogramm

/ * LED an PIN6 (PB1) angeschlossen * /
# LED definieren 1 

// Anti-Log (Reverse) Nachschlagetabelle 
// y = 0-255 (PWM-Ausgabe), y_range = 256
// x = 0-1023 (10-Bit-ADC-Eingang); 
// Unter der Annahme, dass das untere / obere Ende der ADC-Ausgangswerte nicht verwendet werden kann
// Die ersten 32 und letzten 32 Werte werden verworfen.
// min_x = 32, max_x = 1023-min_x, x_range = 1024-2 * min_x
// ANTI_LOG [y] = round (x_range * log (y, base = y_range) + min_x)
// Führe bei einem Wert von x eine binäre Suche für die folgende Tabelle durch
// benötigt für Attiny85 @ 8MHz Takt ungefähr 28uS
PROGMEM prog_uint16_t ANTI_LOG [] = {
  0x0000, 0x0020, 0x0098, 0x00de, 0x0110, 0x0137, 0x0156, 0x0171, 0x0188, 0x019c, 0x01af, 0x01bf, 0x01ce, 0x01dc, 0x01e9, 0x01f5,
  0x0200, 0x020a, 0x0214, 0x021e, 0x0227, 0x022f, 0x0237, 0x023f, 0x0246, 0x024d, 0x0254, 0x025b, 0x0261, 0x0267, 0x026d, 0x0273,
  0x0278, 0x027d, 0x0282, 0x0288, 0x028c, 0x0291, 0x0296, 0x029a, 0x029f, 0x02a3, 0x02a7, 0x02ab, 0x02af, 0x02b3, 0x02b7, 0x02bb,
  0x02be, 0x02c2, 0x02c5, 0x02c9, 0x02cc, 0x02cf, 0x02d3, 0x02d6, 0x02d9, 0x02dc, 0x02df, 0x02e2, 0x02e5, 0x02e8, 0x02eb, 0x02ed,
  0x02f0, 0x02f3, 0x02f5, 0x02f8, 0x02fa, 0x02fd, 0x0300, 0x0302, 0x0304, 0x0307, ​​0x0309, 0x030b, 0x030e, 0x0310, 0x0312, 0x0314,
  0x0317, 0x0319, 0x031b, 0x031d, 0x031f, 0x0321, 0x0323, 0x0325, 0x0327, 0x0329, 0x032b, 0x032d, 0x032f, 0x0331, 0x0333, 0x0334,
  0x0336, 0x0338, 0x033a, 0x033c, 0x033d, 0x033f, 0x0341, 0x0342, 0x0344, 0x0346, 0x0347, 0x0349, 0x034b, 0x034c, 0x034e, 0x034f,
  0x0351, 0x0352, 0x0354, 0x0355, 0x0357, 0x0358, 0x035a, 0x035b, 0x035d, 0x035e, 0x0360, 0x0361, 0x0363, 0x0364, 0x0365, 0x0367,
  0x0368, 0x0369, 0x036b, 0x036c, 0x036d, 0x036f, 0x0370, 0x0371, 0x0372, 0x0374, 0x0375, 0x0376, 0x0378, 0x0379, 0x037a, 0x037b,
  0x037c, 0x037e, 0x037f, 0x0380, 0x0381, 0x0382, 0x0383, 0x0385, 0x0386, 0x0387, 0x0388, 0x0389, 0x038a, 0x038b, 0x038c, 0x038e,
  0x038f, 0x0390, 0x0391, 0x0392, 0x0393, 0x0394, 0x0395, 0x0396, 0x0397, 0x0398, 0x0399, 0x039a, 0x039b, 0x039c, 0x039d, 0x039e,
  0x039f, 0x03a0, 0x03a1, 0x03a2, 0x03a3, 0x03a4, 0x03a5, 0x03a6, 0x03a7, 0x03a8, 0x03a9, 0x03aa, 0x03ab, 0x03ab, 0x03ac, 0x03ad,
  0x03ae, 0x03af, 0x03b0, 0x03b1, 0x03b2, 0x03b3, 0x03b4, 0x03b4, 0x03b5, 0x03b6, 0x03b7, 0x03b8, 0x03b9, 0x03ba, 0x03ba, 0x03bbb,
  0x03bc, 0x03bd, 0x03be, 0x03bf, 0x03bf, 0x03c0, 0x03c1, 0x03c2, 0x03c3, 0x03c3, 0x03c4, 0x03c5, 0x03c6, 0x03c7, 0x03c7, 0x03c8,
  0x03c9, 0x03ca, 0x03ca, 0x03cb, 0x03cc, 0x03cd, 0x03cd, 0x03ce, 0x03cf, 0x03d0, 0x03d0, 0x03d1, 0x03d2, 0x03d3, 0x03d3, 0x03d4,
  0x03d5, 0x03d6, 0x03d6, 0x03d7, 0x03d8, 0x03d8, 0x03d9, 0x03da, 0x03db, 0x03db, 0x03dc, 0x03dd, 0x03dd, 0x03de, 0x03df
};

// Binäre Suche mit der obigen Tabelle.
Byte-Antilog (int x)
{
  Byte y = 0x80;
  int av;
  für (int i = 0x40; i> 0; i >> = 1)
  {
    av = pgm_read_word_near (ANTI_LOG + y);
    wenn (av> x)
    {
      y - = i;
    }
    sonst wenn (av <x) 
    {
      y | = i;
    }
    sonst
    {
      return y;
    }
  }
  if (pgm_read_word_near (ANTI_LOG + y)> x)
  {
    y - = 1;
  }
  return y;
}


void setup ()
{
  PinMode (LED, OUTPUT);
  digitalWrite (LED, LOW);
}

#define MIN_X 0
#define MAX_X 1024

leere Schleife ()
{
  int i;
  // antilog_drive
  für (i = MIN_X; i <MAX_X; i ++)
  {
    analogWrite (LED, Antilog (i));
    Verzögerung (2);
  }
  für (--i; i> = MIN_X; i--)
  {
    analogWrite (LED, Antilog (i));
    Verzögerung (2);
  }
  Verzögerung (1000);
  // Linearantrieb
  für (i = MIN_X; i <MAX_X; i ++)
  {
    analogWrite (LED, i >> 2);
    Verzögerung (2);
  }
  für (--i; i> = MIN_X; i--)
  {
    analogWrite (LED, i >> 2);
    Verzögerung (2);
  }
  Verzögerung (2000);
}
tash
quelle
1

Dieses PDF erklärt die benötigte Kurve, anscheinend eine logarithmische. Wenn Sie einen linearen Dimmer (Ihren PWM-Wert) haben, sollte die Funktion logarithmisch sein.

Hier finden Sie eine Nachschlagetabelle für 32 Helligkeitsstufen für 8-Bit-PWM.

Hier für 16 Schritte.

FarO
quelle
1

Hier ist, was ich basierend auf dieser Antwort des Arduino-Forums getan habe . Ich habe die Werte von 0 bis 255 berechnet, damit es mit pwm auf Arduino einfach zu verwenden ist

byte ledLookupTable[] = {0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,2,2,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,6,6,6,7,7,7,8,8,8,9,9,9,10,10,11,11,11,12,12,13,13,14,14,15,15,16,16,17,17,18,18,19,19,20,20,21,21,22,23,23,24,24,25,26,26,27,28,28,29,30,30,31,32,32,33,34,35,35,36,37,38,38,39,40,41,42,42,43,44,45,46,47,47,48,49,50,51,52,53,54,55,56,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,73,74,75,76,77,78,79,80,81,82,84,85,86,87,88,89,91,92,93,94,95,97,98,99,100,102,103,104,105,107,108,109,111,112,113,115,116,117,119,120,121,123,124,126,127,128,130,131,133,134,136,137,139,140,142,143,145,146,148,149,151,152,154,155,157,158,160,162,163,165,166,168,170,171,173,175,176,178,180,181,183,185,186,188,190,192,193,195,197,199,200,202,204,206,207,209,211,213,215,217,218,220,222,224,226,228,230,232,233,235,237,239,241,243,245,247,249,251,253,255};

Dann verwenden Sie auf Arduino einfach so:

analogWrite(ledPin, ledLookupTable[brightness]); //with brighness between 0 and 255

Hoffe, es ist hilfreich für einige Leute;)

Jacob Yiendedoi
quelle
1

Ich beschäftige mich jetzt damit und gehe etwas anders vor. Ich möchte 256 Helligkeitsstufen, aber die Zuordnung eines linearen Bereichs von 0 bis 255 zu einem nicht linearen Bereich von 0 bis 255 führt zum Ergebnis, wie Sie in einigen anderen Antworten sehen können, mit vielen doppelten Einträgen. (Das heißt, mehrere Ihrer Eingabewerte führen zu derselben Helligkeitsstufe.)

Ich habe versucht, den Algorithmus so zu modifizieren, dass er einen Eingangsbereich von 0 bis 256 auf einen Ausgangsbereich von 0 bis 1023 abbildet, aber selbst dieser hatte mehrere Werte, die mit 0 übereinstimmen Um nichtlineare Werte im Bereich von 0 bis 769 (das sind 1023 bis 255) zu generieren sin(), addieren Sie diese Werte zum Eingangspegel, um eine Ausgabe im Bereich von 0 bis 1023 ohne Duplikate zu erhalten. Ich konfiguriere einen Timer für die Verwendung eines Zählers von 1023 und setze den Komparator für die PWM-Ausgabe auf Werte aus der Nachschlagetabelle basierend auf der gewünschten Beleuchtungsstärke (0-255).

Hier ist das C-Programm, mit dem ich meine Nachschlagetabelle erstellt habe:

#include <stdio.h>
#include <math.h>

int main() {
    int i;
    double j;
    int k;

    printf( "int brightness[] = {\n" );
    for( i=0; i<256; i++ ) {
        // 0 - 255 => 1.0 - 0.0, multiply by 90 degrees (in radians)
        j = (1 - (i / 255.0)) * M_PI / 2;
        j = sin( j );
        k = (1023-255) - j * (1023-255);
        printf( "%s%d%s%s",
                (((i % 8) == 0) ? "    " : " "), // leading space at start of line
                k+i,
                ((i < 255) ? "," : ""),          // comma after all but last value
                (((i % 8) == 7) ? "\n" : "")     // line break every 8 items
              );
    }
    printf( "  };\n" );
}

Und hier ist die Tabelle:

int brightness[] = {
    0, 1, 2, 3, 4, 5, 6, 7,
    8, 10, 11, 12, 14, 15, 16, 18,
    19, 21, 22, 24, 25, 27, 29, 30,
    32, 34, 35, 37, 39, 41, 43, 44,
    46, 48, 50, 52, 54, 56, 58, 61,
    63, 65, 67, 69, 72, 74, 76, 78,
    81, 83, 86, 88, 91, 93, 96, 98,
    101, 103, 106, 109, 111, 114, 117, 120,
    122, 125, 128, 131, 134, 137, 140, 143,
    146, 149, 152, 155, 158, 161, 164, 168,
    171, 174, 177, 181, 184, 187, 191, 194,
    198, 201, 205, 208, 212, 215, 219, 222,
    226, 230, 233, 237, 241, 244, 248, 252,
    256, 260, 263, 267, 271, 275, 279, 283,
    287, 291, 295, 299, 303, 307, 312, 316,
    320, 324, 328, 333, 337, 341, 345, 350,
    354, 358, 363, 367, 372, 376, 381, 385,
    390, 394, 399, 403, 408, 412, 417, 422,
    426, 431, 436, 440, 445, 450, 455, 459,
    464, 469, 474, 479, 484, 489, 493, 498,
    503, 508, 513, 518, 523, 528, 533, 538,
    543, 548, 554, 559, 564, 569, 574, 579,
    584, 590, 595, 600, 605, 610, 616, 621,
    626, 632, 637, 642, 647, 653, 658, 664,
    669, 674, 680, 685, 690, 696, 701, 707,
    712, 718, 723, 729, 734, 740, 745, 751,
    756, 762, 767, 773, 778, 784, 790, 795,
    801, 806, 812, 818, 823, 829, 834, 840,
    846, 851, 857, 863, 868, 874, 880, 885,
    891, 897, 902, 908, 914, 920, 925, 931,
    937, 942, 948, 954, 960, 965, 971, 977,
    982, 988, 994, 1000, 1005, 1011, 1017, 1023
};

Ich werde wahrscheinlich andere Funktionen (wie log()) untersuchen, sobald ich diese zum Laufen gebracht habe.

user112358
quelle