Correção de brilho não linear em LEDs ao usar PWM

33

Ao dirigir um LED com PWM, o brilho (como eu o percebo) não aumenta linearmente com o ciclo de trabalho. O brilho é lento para aumentar, depois aumenta exponencialmente com o ciclo de trabalho.

Alguém pode sugerir uma regra prática a ser usada como fator de correção ou outra solução alternativa?

Toby Jaffey
fonte
Quando fiz um par de abotoaduras Knight Rider, tive que usar x ^ 10 para fazer com que o desbotamento ficasse bonito!
Rocketmagnet
3
Tem certeza de que não é "o brilho inicialmente aumenta exponencialmente e depois é lento para aumentar"?
Dmitry Grigoryev
1
Acredito que nossos olhos respondem logaritmicamente ao brilho.
DKNguyen 14/06

Respostas:

13

Para 16 níveis, é fácil fazer uma tabela de consulta simples "à mão" e converter o valor de 4 bits em um valor de 8 bits para passar para o controlador PWM: esse é o componente que eu usei no meu driver de matriz de led FPGA. Para um controlador de nível de 8 bits, você precisará de pelo menos 11 a 12 bits de saída da tabela de consulta.

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
fonte
Estou tentando descobrir exatamente qual é a sua fórmula. É notavelmente próximo de f (x) = x ^ 2, mas a curva não é suficientemente profunda. f (x) = x ^ 3/13 me aproxima muito mais.
ajs410 27/09/10
Não é uma fórmula (não intencionalmente) ... Eu pulei nos valores iniciais do linearizador apenas adivinhando :-). Em seguida, liguei a matriz, conduzindo as colunas de led na ordem de brilho e ajustei os valores para obter uma rampa uniforme. É realmente fácil com apenas 16 níveis.
Axeman
1
2n1
Stevenvh
17

Em teoria, deveria ser exponencial, mas tenho melhores resultados para o desbotamento usando uma função quadrática.

Eu também acho que você entendeu de trás para a frente. No ciclo de trabalho baixo, o aumento percebido no brilho é muito maior do que no ciclo de trabalho quase completo, onde o aumento no brilho é quase imperceptível.

starblue
fonte
Veja também Correção gama .
starblue 18/06
17

Eu estive estudando esse assunto nos últimos dias, pois tenho o mesmo problema ... tentando escurecer os LEDs usando o PWM de uma maneira visivelmente linear, mas quero uma resolução completa de 256 etapas. Tentar adivinhar 256 números para criar manualmente uma curva não é uma tarefa fácil!

Não sou um matemático experiente, mas sei o suficiente para gerar algumas curvas básicas combinando algumas funções e fórmulas sem realmente saber como elas funcionam. Acho que, usando uma planilha (usei o Excel), você pode brincar com um conjunto de números de 0 a 255, colocar algumas fórmulas na próxima célula e fazer um gráfico delas.

Estou usando o pic assembler para fazer o desbotamento e, assim, você pode obter a planilha para gerar o código do assembler com uma fórmula (="retlw 0x" & DEC2HEX(A2) ). Isso torna muito rápido e fácil experimentar uma nova curva.

Depois de brincar um pouco com as funções LOG e SIN, a média das duas e algumas outras coisas, eu não conseguia realmente obter a curva certa. O que está acontecendo é que a parte do meio do desbotamento estava acontecendo mais lentamente que os níveis mais baixo e mais alto. Além disso, se um desbotamento for seguido imediatamente por um desbotamento, haverá um aumento acentuado na intensidade. O que é necessário (na minha opinião) é uma curva S.

Uma pesquisa rápida na Wikipedia surgiu com a fórmula necessária para uma curva S. Conectei isso à minha planilha e fiz alguns ajustes para que ele se multiplicasse em meu intervalo de valores e criei o seguinte:

Curva S

Testei no meu equipamento e funcionou lindamente.

A fórmula do Excel que usei foi esta:

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

onde A2 é o primeiro valor na coluna A, que aumenta A3, A4, ..., A256 para cada valor.

Não tenho ideia se isso é matematicamente correto ou não, mas produz os resultados desejados.

Aqui está o conjunto completo de 256 níveis que eu usei:

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
fonte
Essa equação funcionou perfeitamente para mim.
Ignacio Vazquez-Abrams
6

Eu encontrei esse cara que usa um método que ele chama de "Anti-Log Drive". Aqui está o link de download direto para suas informações.

davr
fonte
4

Eu estava usando um ATtiny para iluminar meu deck. O brilho é controlado usando um pote conectado ao pino ADC.

Tentei a função exponencial e a saída PWM com base nisso parece estar dando um aumento linear no brilho percebido.

Eu estava usando estas fórmulas:

out = pow(out_max, in/in_max)

Attiny85 @ 8MHz estava demorando cerca de 210us para executar o cálculo acima. Para melhorar o desempenho, fez uma tabela de pesquisa. Como a entrada era do ADC de 10 bits e a memória ATtiny é limitada, eu também queria criar uma tabela mais curta.

Em vez de criar tabela de pesquisa com 1024 entradas, crie uma tabela de pesquisa inversa com 256 entradas (512 bytes) na memória do programa (PGMEM). Uma função foi escrita para executar a pesquisa binária nessa tabela. Este método leva apenas 28uS para cada pesquisa. Se eu usar uma tabela de pesquisa direta, isso exigiria 2kb de memória, mas a pesquisa levaria apenas 4uS ou mais.

Os valores calculados na tabela de pesquisa usam apenas a faixa de entrada 32-991, descartando a faixa inferior / superior do ADC, caso haja algum problema com o circuito.

Abaixo está o que eu tenho agora.

// programa de teste anti_log

/ * LED conectado ao PIN6 (PB1) * /
#define LED 1 

// Tabela de pesquisa Anti-Log (reversa) 
// y = 0-255 (saída pwm), y_range = 256
// x = 0-1023 (entrada ADC de 10 bits); 
// supondo que os valores inferiores / superiores dos valores de saída ADC não possam ser usados
// descartando os primeiros 32 e os últimos 32 valores.
// min_x = 32, max_x = 1023-min_x, x_range = 1024-2 * min_x
// ANTI_LOG [y] = arredondado (intervalo_x * log (y, base = intervalo_y) + min_x)
// dado um valor de x, faça uma pesquisa binária na tabela abaixo
// leva cerca de 28 uS para o relógio Attiny85 a 8 MHz
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, 0x3ac, 0x3ac
  0x03ae, 0x03af, 0x03b0, 0x03b1, 0x03b2, 0x03b3, 0x03b4, 0x03b4, 0x03b5, 0x03b6, 0x03b7, 0x03b8, 0x03b9, 0x3bab, 0x03bab
  0x03bc, 0x03bd, 0x03be, 0x03bf, 0x03bf, 0x03c0, 0x03c1, 0x03c2, 0x03c3, 0x03c3, 0x03c4, 0x03c5, 0x03c6, 0x03c7
  0x03c9, 0x03ca, 0x03ca, 0x03cb, 0x03cc, 0x03cd, 0x03cd, 0x03ce, 0x03cf, 0x03d0, 0x03d0, 0x03d1, 0x03d2, 0x03d3, 0x03d3
  0x03d5, 0x03d6, 0x03d6, 0x03d7, 0x03d8, 0x03d8, 0x03d9, 0x03da, 0x03db, 0x03db, 0x03dc, 0x03dd, 0x03dd, 0x03df
};

// Pesquisa binária usando a tabela acima.
byte antilog (int x)
{
  byte y = 0x80;
  int av;
  for (int i = 0x40; i> 0; i >> = 1)
  {
    av = pgm_read_word_near (ANTI_LOG + y);
    se (av> x)
    {
      y - = i;
    }
    senão se (av <x) 
    {
      y | = i;
    }
    outro
    {
      retornar y;
    }
  }
  if (pgm_read_word_near (ANTI_LOG + y)> x)
  {
    y = 1;
  }
  retornar y;
}


configuração nula ()
{
  pinMode (LED, SAÍDA);
  digitalWrite (LED, BAIXO);
}

#define MIN_X 0
#define MAX_X 1024

loop vazio ()
{
  int i;
  // antilog_drive
  para (i = MIN_X; i <MAX_X; i ++)
  {
    analogWrite (LED, anti-log (i));
    atraso (2);
  }
  para (--i; i> = MIN_X; i--)
  {
    analogWrite (LED, anti-log (i));
    atraso (2);
  }
  atraso (1000);
  // Movimentação linear
  para (i = MIN_X; i <MAX_X; i ++)
  {
    analogWrite (LED, i >> 2);
    atraso (2);
  }
  para (--i; i> = MIN_X; i--)
  {
    analogWrite (LED, i >> 2);
    atraso (2);
  }
  atraso (2000);
}
tash
fonte
1

This PDF explains the curve needed, apparently a logarithmic one. If you have a linear dimmer (your PWM value) then the function should be logarithmic.

Here you can find a lookup table for 32 steps of brightness for 8 bit PWM.

Here for 16 steps.

FarO
fonte
1

Here is what I have done based on that arduino forum response. I have computed the values from 0 to 255 so it's easy to use with pwm on 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};

Then to use on Arduino just do like that :

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

Hope it is helpfull for some people ;)

Jacob Yiendedoi
fonte
1

I'm dealing with this now, and I'm taking a slightly different approach. I want 256 levels of brightness, but mapping a linear 0-255 range to a non-linear 0-255 range winds up, as you can see in some of the other answers, with a lot of duplicate entries. (I.e., several of your input values result in the same brightness level.)

I tried modifying the algorithm to map a 0-256 input range to a 0-1023 output range, but even that had several values mapping to 0. So I'm trying something a bit different - I'm using the 0-255 level to generate non-linear values in the range 0-769 (that's 1023 minus 255) using sin(), then add that to the input level to get an output in the range 0-1023 with no duplicates. I'll configure a timer to use a counter of 1023, and set the comparator for the PWM output to values from the lookup table based on what lighting level I want (0-255).

Here's the C program I used to generate my lookup table:

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

And here's the table:

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

I'll probably investigate other functions (like log()) once I've got this up and running.

user112358
fonte