Correzione della luminosità non lineare nei LED quando si utilizza PWM


33

Quando si guida un LED con PWM, la luminosità (come la percepisco) non si ridimensiona linearmente con il duty cycle. La luminosità aumenta lentamente, quindi aumenta esponenzialmente con il ciclo di lavoro.

Qualcuno può suggerire una regola empirica da utilizzare come fattore di correzione o altra soluzione alternativa?


Quando ho creato un paio di gemelli Knight Rider, ho dovuto usare x ^ 10 per rendere piacevole la dissolvenza!
Rocketmagnet,

3
Sei sicuro che non sia "la luminosità inizialmente aumenta in modo esponenziale, quindi rallenta lentamente"?
Dmitry Grigoryev il

1
Credo che i nostri occhi rispondano logaritmicamente alla luminosità.
DKNguyen,

Risposte:


13

Per 16 livelli è facile fare una semplice tabella di ricerca "a mano" e convertire il valore a 4 bit in un valore a 8 bit per passare al controller PWM: questo è il componente che ho usato nel mio driver array con FPGA. Per un controller di livello a 8 bit, è necessario almeno 11-12 bit di output dalla tabella di ricerca.

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;

Sto cercando di capire esattamente quale sia la tua formula. È notevolmente vicino a f (x) = x ^ 2, ma la curva non è abbastanza profonda. f (x) = x ^ 3/13 mi avvicina molto di più.
ajs410,

Non è una formula (non intenzionalmente) ... Ho scoperto i valori iniziali del linearizzatore solo indovinando :-). Ho quindi alimentato l'array, guidato le colonne led in ordine di luminosità e modificato i valori per ottenere una rampa uniforme. È davvero facile con solo 16 livelli.
Axeman,

1
@ ajs410 - mi sembra più simile a : il primo bit sposta più o meno a sinistra di 1 posizione a sinistra ad ogni passo. 2n1
Stevenvh,

17

In teoria dovrebbe essere esponenziale, ma ho i migliori risultati per lo sbiadimento usando una funzione quadratica.

Penso anche che tu l'abbia ottenuto al contrario. A ciclo di lavoro basso, l'aumento di luminosità percepito è molto maggiore rispetto a un ciclo di lavoro quasi completo, dove l'aumento di luminosità è quasi impercettibile.


Vedi anche Correzione gamma .
Starblue,

17

Ho esaminato questo argomento negli ultimi giorni perché ho lo stesso problema ... cercando di attenuare i LED usando PWM in modo visibilmente lineare, ma voglio una risoluzione completa di 256 passi. Cercare di indovinare 256 numeri per creare manualmente una curva non è un compito facile!

Non sono un matematico esperto, ma conosco abbastanza per generare alcune curve di base combinando alcune funzioni e formule senza sapere davvero come funzionano. Trovo che usando un foglio di calcolo (ho usato Excel) puoi giocare con un set di numeri da 0 a 255, inserire alcune formule nella cella successiva e rappresentarle graficamente.

Sto usando pic assemblatore per eseguire lo sbiadimento, quindi puoi persino ottenere il foglio di calcolo per generare il codice dell'assemblatore con una formula ( ="retlw 0x" & DEC2HEX(A2)). Questo rende molto semplice e veloce provare una nuova curva.

Dopo aver giocato un po 'con le funzioni LOG e SIN, la media delle due e poche altre cose, non sono riuscito a ottenere la curva giusta. Quello che sta succedendo è che la parte centrale della dissolvenza stava avvenendo più lentamente dei livelli più bassi e più alti. Inoltre, se una dissolvenza è immediatamente seguita da una dissolvenza verso il basso, si è verificato un forte picco evidente nell'intensità. Ciò che è necessario (secondo me) è una curva a S.

Una rapida ricerca su Wikipedia ha prodotto la formula necessaria per una curva a S. Ho inserito questo nel mio foglio di calcolo e ho apportato alcune modifiche per farlo moltiplicare per la mia gamma di valori, e ho trovato questo:

Curva a S.

L'ho provato sul mio impianto di perforazione e ha funzionato magnificamente.

La formula di Excel che ho usato era questa:

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

dove A2 è il primo valore nella colonna A, che aumenta A3, A4, ..., A256 per ciascun valore.

Non ho idea se questo sia matematicamente corretto o meno, ma produce i risultati desiderati.

Ecco il set completo di 256 livelli che ho usato:

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

Questa equazione ha funzionato perfettamente per me.
Ignacio Vazquez-Abrams,


4

Stavo usando un ATtiny per illuminare il mio mazzo. La luminosità è controllata tramite una pentola collegata al pin ADC.

Ho provato la funzione esponenziale e l'uscita PWM basata su questo sembra dare un aumento lineare della luminosità percepita.

Stavo usando queste formule:

out = pow(out_max, in/in_max)

Attiny85 @ 8MHz impiegava circa 210us per eseguire il calcolo di cui sopra. Per migliorare le prestazioni, creato una tabella di ricerca. Poiché l'input proveniva da ADC a 10 bit e la memoria ATtiny è limitata, volevo creare anche una tabella più breve.

Invece di creare una tabella di ricerca con 1024 voci, ha creato una tabella di ricerca inversa con 256 voci (512 byte) nella memoria del programma (PGMEM). È stata scritta una funzione per eseguire la ricerca binaria su quella tabella. Questo metodo richiede solo 28uS per ogni ricerca. Se uso una tabella di ricerca diretta, richiederebbe 2kb di memoria, ma la ricerca richiederebbe solo 4 uS circa.

I valori calcolati nella tabella di ricerca utilizzano solo l'intervallo di input 32-991, scartando l'intervallo inferiore / superiore dell'ADC, nel caso in cui si verifichino problemi con il circuito.

Di seguito è quello che ho ora.

// programma di test anti_log

/ * LED collegato a PIN6 (PB1) * /
#define LED 1 

// Tabella di ricerca anti-log (inversa) 
// y = 0-255 (output pwm), y_range = 256
// x = 0-1023 (ingresso ADC a 10 bit); 
// supponendo che non sia possibile utilizzare l'estremità inferiore / superiore dei valori di uscita ADC
// scartando i primi 32 e gli ultimi 32 valori.
// 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)
// dato il valore di x, esegue una ricerca binaria nella tabella sottostante
// impiega circa 28uS per il clock Attiny85 @ 8MHz
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, 0x03bb,
  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, 0x03df
};

// Ricerca binaria usando la tabella sopra.
byte antilog (int x)
{
  byte y = 0x80;
  int av;
  per (int i = 0x40; i> 0; i >> = 1)
  {
    av = pgm_read_word_near (ANTI_LOG + y);
    if (av> x)
    {
      y - = i;
    }
    altrimenti if (av <x) 
    {
      y | = i;
    }
    altro
    {
      ritorna y;
    }
  }
  if (pgm_read_word_near (ANTI_LOG + y)> x)
  {
    y - = 1;
  }
  ritorna y;
}


void setup ()
{
  pinMode (LED, OUTPUT);
  digitalWrite (LED, BASSO);
}

#define MIN_X 0
#define MAX_X 1024

void loop ()
{
  int i;
  // antilog_drive
  per (i = MIN_X; i <MAX_X; i ++)
  {
    analogWrite (LED, antilog (i));
    ritardo (2);
  }
  per (--i; i> = MIN_X; i--)
  {
    analogWrite (LED, antilog (i));
    ritardo (2);
  }
  ritardo (1000);
  // Azionamento lineare
  per (i = MIN_X; i <MAX_X; i ++)
  {
    analogWrite (LED, i >> 2);
    ritardo (2);
  }
  per (--i; i> = MIN_X; i--)
  {
    analogWrite (LED, i >> 2);
    ritardo (2);
  }
  ritardo (2000);
}

1

Questo PDF spiega la curva necessaria, apparentemente logaritmica. Se hai un dimmer lineare (il tuo valore PWM), la funzione dovrebbe essere logaritmica.

Qui puoi trovare una tabella di ricerca per 32 livelli di luminosità per PWM a 8 bit.

Qui per 16 passaggi.


1

Ecco cosa ho fatto sulla base di quella risposta del forum Arduino . Ho calcolato i valori da 0 a 255, quindi è facile da usare con pwm su arduino

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};

Quindi per usare su Arduino basta fare così:

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

Spero che sia utile per alcune persone;)


1

Ho a che fare con questo ora e sto adottando un approccio leggermente diverso. Voglio 256 livelli di luminosità, ma la mappatura di un intervallo 0-255 lineare su un intervallo 0-255 non lineare finisce, come puoi vedere in alcune delle altre risposte, con molte voci duplicate. (Cioè, molti dei tuoi valori di input producono lo stesso livello di luminosità.)

Ho provato a modificare l'algoritmo per mappare un intervallo di input 0-256 su un intervallo di output 0-1023, ma anche quello aveva diversi valori mappati su 0. Quindi sto provando qualcosa di un po 'diverso - Sto usando il livello 0-255 per generare valori non lineari nell'intervallo 0-769 (ovvero 1023 meno 255) utilizzando sin(), quindi aggiungerlo al livello di input per ottenere un output nell'intervallo 0-1023 senza duplicati. Configurerò un timer per utilizzare un contatore di 1023 e imposterò il comparatore per l'uscita PWM sui valori della tabella di ricerca in base al livello di illuminazione desiderato (0-255).

Ecco il programma C che ho usato per generare la mia tabella di ricerca:

#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" );
}

Ed ecco la tabella:

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
};

Probabilmente indagherò su altre funzioni (come log()) una volta che questo sarà attivo e funzionante.


Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.