Corrección de brillo no lineal en LED cuando se usa PWM

33

Al conducir un LED con PWM, el brillo (como lo percibo) no se escala linealmente con el ciclo de trabajo. El brillo es lento para aumentar, luego aumenta exponencialmente con el ciclo de trabajo.

¿Alguien puede sugerir una regla general para usar como factor de corrección u otra solución?

Toby Jaffey
fuente
¡Cuando hice un par de gemelos Knight Rider, tuve que usar x ^ 10 para que el desvanecimiento se viera bien!
Rocketmagnet
3
¿Estás seguro de que no es "el brillo inicialmente aumenta exponencialmente y luego aumenta lentamente"?
Dmitry Grigoryev
1
Creo que nuestros ojos responden logarítmicamente al brillo.
DKNguyen

Respuestas:

13

Para 16 niveles, es fácil hacer una tabla de búsqueda simple "a mano" y convertir el valor de 4 bits en un valor de 8 bits para pasar al controlador PWM: este es el componente que he usado en mi controlador de matriz de LED FPGA. Para un controlador de nivel de 8 bits, necesitará al menos una salida de 11-12 bits de la tabla de búsqueda.

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
fuente
Estoy tratando de averiguar exactamente cuál es tu fórmula. Es notablemente cercano a f (x) = x ^ 2, pero la curva no es lo suficientemente profunda. f (x) = x ^ 3/13 me acerca mucho más.
ajs410
No es una fórmula (no intencionalmente) ... He agregado los valores iniciales del linealizador simplemente adivinando :-). Luego alimente la matriz, conduje las columnas led en orden de brillo y modifique los valores para obtener una rampa uniforme. Es realmente fácil con solo 16 niveles.
Axeman
1
2norte1
17

En teoría, debería ser exponencial, pero obtuve los mejores resultados para el desvanecimiento mediante el uso de una función cuadrática.

También creo que lo tienes al revés. En el ciclo de trabajo bajo, el aumento percibido del brillo es mucho mayor que en el ciclo de trabajo casi completo, donde el aumento del brillo es casi imperceptible.

starblue
fuente
Ver también corrección gamma .
Starblue
17

He estado investigando este tema en los últimos días ya que tengo el mismo problema ... tratando de atenuar los LED usando PWM de una manera visiblemente lineal, pero quiero una resolución completa de 256 pasos. ¡Intentar adivinar 256 números para crear una curva manualmente no es una tarea fácil!

No soy un matemático experto, pero sé lo suficiente como para generar algunas curvas básicas combinando algunas funciones y fórmulas sin saber realmente cómo funcionan. Me parece que usando una hoja de cálculo (usé Excel) puedes jugar con un conjunto de números del 0 al 255, poner algunas fórmulas en la siguiente celda y representarlas gráficamente.

Estoy usando pic assembler para hacer el desvanecimiento, por lo que incluso puede obtener la hoja de cálculo para generar el código del ensamblador con una fórmula ( ="retlw 0x" & DEC2HEX(A2)). Esto hace que sea muy rápido y fácil probar una nueva curva.

Después de jugar un poco con las funciones LOG y SIN, el promedio de los dos y algunas otras cosas, realmente no pude obtener la curva correcta. Lo que está sucediendo es que la parte media del desvanecimiento fue más lenta que los niveles más bajos y más altos. Además, si un desvanecimiento es seguido inmediatamente por un desvanecimiento, hubo un fuerte aumento notable en la intensidad. Lo que se necesita (en mi opinión) es una curva en S.

Una búsqueda rápida en Wikipedia arrojó la fórmula necesaria para una curva en S. Lo conecté a mi hoja de cálculo e hice algunos ajustes para que se multiplique en mi rango de valores, y se me ocurrió esto:

S curva

Lo probé en mi plataforma y funcionó muy bien.

La fórmula de Excel que utilicé fue esta:

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

donde A2 es el primer valor en la columna A, que aumenta A3, A4, ..., A256 para cada valor.

No tengo idea si esto es matemáticamente correcto o no, pero produce los resultados deseados.

Aquí está el conjunto completo de 256 niveles que utilicé:

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
fuente
Esta ecuación funcionó perfectamente para mí.
Ignacio Vazquez-Abrams
4

Estaba usando un ATtiny para iluminar mi terraza. El brillo se controla usando una olla conectada al pin ADC.

Intentó la función exponencial y la salida PWM basada en eso parece estar dando un aumento lineal en el brillo percibido.

Estaba usando estas fórmulas:

out = pow(out_max, in/in_max)

Attiny85 @ 8MHz estaba tardando unos 210us en realizar el cálculo anterior. Para mejorar el rendimiento, hizo una tabla de búsqueda. Como la entrada era de ADC de 10 bits y la memoria ATtiny es limitada, también quería crear una tabla más corta.

En lugar de hacer una tabla de búsqueda con 1024 entradas, hizo una tabla de búsqueda inversa con 256 entradas (512 bytes) en la memoria del programa (PGMEM). Se escribió una función para realizar una búsqueda binaria en esa tabla. Este método solo requiere 28uS para cada búsqueda. Si uso una tabla de búsqueda directa, requeriría 2kb de memoria, pero la búsqueda tomaría solo 4 uS más o menos.

Los valores calculados en la tabla de búsqueda usan solo el rango de entrada 32-991, descartando el rango inferior / superior de ADC, en caso de que haya un problema con el circuito.

A continuación se muestra lo que tengo ahora.

// programa de prueba anti_log

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

// tabla de búsqueda Anti-Log (inversa) 
// y = 0-255 (salida pwm), rango_y = 256
// x = 0-1023 (entrada ADC de 10 bits); 
// suponiendo que no se pueden usar valores de salida ADC inferior / superior
// descartando los primeros 32 y los últimos 32 valores.
// 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)
// dado un valor de x, realice una búsqueda binaria en la tabla a continuación
// toma aproximadamente 28uS para el reloj 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, 0x02b7, 0x02b7, 0x02b7, 0x02b7, 0x02b7, 0x02b7
  0x02be, 0x02c2, 0x02c5, 0x02c9, 0x02cc, 0x02cf, 0x02d3, 0x02d6, 0x02d9, 0x02dc, 0x02df, 0x02e2, 0x02e5, 0x02e8, 0x02e8, 0x02e8, 0x02e8, 0x02e8, 0x02e8, 0x02e8
  0x02f0, 0x02f3, 0x02f5, 0x02f8, 0x02fa, 0x02fd, 0x0300, 0x0302, 0x0304, 0x0307, ​​0x0309, 0x030b, 0x030e, 0x0310, 0x0312, 0x0312
  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, 0x03ac, 0x33, 0x3, 0
  0x03ae, 0x03af, 0x03b0, 0x03b1, 0x03b2, 0x03b3, 0x03b4, 0x03b4, 0x03b5, 0x03b6, 0x03b7, 0x03b8, 0x03b9, 0x03ba, 0x03ba, 0x03ba, 0x03ba, 0x03ba, 0x03ba, 0x03ba, 0x03
  0x03bc, 0x03bd, 0x03be, 0x03bf, 0x03bf, 0x03c0, 0x03c1, 0x03c2, 0x03c3, 0x03c3, 0x03c4, 0x03c5, 0x03c6, 0x03c7, 0x03c7, 0x03c7, 0x03c7, 0x03c7, 0x03c7, 0x03c7, 0x03c7
  0x03c9, 0x03ca, 0x03ca, 0x03cb, 0x03cc, 0x03cd, 0x03cd, 0x03ce, 0x03cf, 0x03d0, 0x03d0, 0x03d1, 0x03d2, 0x03d3, 0x03d4, 0x03d3, 0x03
  0x03d5, 0x03d6, 0x03d6, 0x03d7, 0x03d8, 0x03d8, 0x03d9, 0x03da, 0x03db, 0x03db, 0x03dc, 0x03dd, 0x03dd, 0x03df, 0x03df, 0x03df, 0x03df, 0x03
};

// Búsqueda binaria usando la tabla anterior.
byte antilog (int x)
{
  byte y = 0x80;
  int av;
  para (int i = 0x40; i> 0; i >> = 1)
  {
    av = pgm_read_word_near (ANTI_LOG + y);
    si (av> x)
    {
      y - = i;
    }
    más si (av <x) 
    {
      y | = i;
    }
    más
    {
      volver y;
    }
  }
  if (pgm_read_word_near (ANTI_LOG + y)> x)
  {
    y - = 1;
  }
  volver y;
}


configuración nula ()
{
  pinMode (LED, SALIDA);
  digitalWrite (LED, BAJO);
}

#define MIN_X 0
#define MAX_X 1024

bucle vacío ()
{
  int i;
  // antilog_drive
  para (i = MIN_X; i <MAX_X; i ++)
  {
    analogWrite (LED, antilog (i));
    retraso (2);
  }
  para (--i; i> = MIN_X; i--)
  {
    analogWrite (LED, antilog (i));
    retraso (2);
  }
  retraso (1000);
  // accionamiento lineal
  para (i = MIN_X; i <MAX_X; i ++)
  {
    analogWrite (LED, i >> 2);
    retraso (2);
  }
  para (--i; i> = MIN_X; i--)
  {
    analogWrite (LED, i >> 2);
    retraso (2);
  }
  retraso (2000);
}
tash
fuente
1

Este PDF explica la curva necesaria, aparentemente una logarítmica. Si tiene un atenuador lineal (su valor PWM), entonces la función debe ser logarítmica.

Aquí puede encontrar una tabla de búsqueda para 32 pasos de brillo para PWM de 8 bits.

Aquí por 16 pasos.

Faraón
fuente
1

Esto es lo que he hecho en base a esa respuesta del foro arduino . He calculado los valores de 0 a 255, por lo que es fácil de usar con pwm en 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};

Luego, para usar en Arduino, simplemente haga así:

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

Espero que sea útil para algunas personas;)

Jacob Yiendedoi
fuente
1

Estoy lidiando con esto ahora, y estoy tomando un enfoque ligeramente diferente. Quiero 256 niveles de brillo, pero el mapeo de un rango lineal de 0-255 a un rango no lineal de 0-255 termina, como puede ver en algunas de las otras respuestas, con muchas entradas duplicadas. (Es decir, varios de sus valores de entrada dan como resultado el mismo nivel de brillo).

Traté de modificar el algoritmo para asignar un rango de entrada 0-256 a un rango de salida 0-1023, pero incluso eso tenía varios valores de asignación a 0. Así que estoy intentando algo un poco diferente: estoy usando el nivel 0-255 para generar valores no lineales en el rango 0-769 (eso es 1023 menos 255) usando sin(), luego agregue eso al nivel de entrada para obtener una salida en el rango 0-1023 sin duplicados. Configuraré un temporizador para usar un contador de 1023 y estableceré el comparador para la salida PWM a los valores de la tabla de búsqueda en función del nivel de iluminación que quiero (0-255).

Aquí está el programa C que usé para generar mi tabla de búsqueda:

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

Y aquí está la tabla:

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

Probablemente investigaré otras funciones (como log()) una vez que tenga esto en funcionamiento.

usuario112358
fuente