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

31

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?

    
pregunta Toby Jaffey

7 respuestas

13

Para 16 niveles es fácil hacer una tabla de consulta 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 matriz de FPGA led conductor. Para un controlador de nivel de 8 bits, necesitará al menos una salida de 11 a 12 bits de la tabla 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;
    
respondido por el Axeman
15

En teoría, debería ser exponencial, pero tengo 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 en el brillo es mucho mayor que en el ciclo de servicio casi completo, donde el aumento en el brillo es casi imperceptible.

    
respondido por el starblue
15

He estado estudiando este tema durante los últimos días, ya que tengo el mismo problema ... intentando atenuar los LED utilizando PWM de forma visiblemente lineal, pero quiero una resolución completa de 256 pasos. ¡Tratar de 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 al usar una hoja de cálculo (yo usé Excel) puedes jugar con un conjunto de números del 0 al 255, poner algunas fórmulas en la siguiente celda y graficarlas.

Estoy usando el ensamblador pic para hacer el desvanecimiento, por lo que incluso puedes 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 las dos y algunas otras cosas, realmente no pude obtener la curva correcta. Lo que está sucediendo es que la parte media del desvanecimiento se produjo más lentamente que los niveles inferiores y superiores. 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 S.

Una búsqueda rápida en Wikipedia produjo la fórmula necesaria para una curva S. Introduje esto en mi hoja de cálculo, e hice algunos ajustes para multiplicarla en mi rango de valores, y encontré esto:

Lo probé en mi plataforma y funcionó a la perfección.

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 de 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
    
respondido por el BG100
6

Encontré a este tipo que usa un método que él llama "Anti-Log Drive". Aquí está el enlace de descarga directa para su información.

    
respondido por el davr
4

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

La función exponencial probada y la salida de PWM basada en eso parecen dar un aumento lineal en el brillo percibido.

Estaba usando estas fórmulas:

out = pow(out_max, in/in_max)

Attiny85 @ 8MHz estaba tomando aproximadamente 210us para realizar el cálculo anterior. Para mejorar el rendimiento, se 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, creó 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 toma solo 28uS para cada búsqueda. Si utilizo una tabla de búsqueda directa, requeriría 2kb de memoria, pero la búsqueda tomaría solo 4uS o menos.

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

Debajo está lo que tengo ahora.

// anti_log test program

/*LED connected to PIN6(PB1)*/
#define LED 1 

// Anti-Log (reverse) lookup table 
// y = 0-255 (pwm output), y_range=256
// x = 0-1023 (10-bit ADC input); 
// assuming lower/higher end of ADC out values cannot be used
// discarding first 32 and last 32 values.
// 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 )
// given a value of x, perform a binary lookup on below table
// takes about 28uS for Attiny85 @8MHz clock
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
};

// Binary lookup using above table.
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 );
    if ( av > x )
    {
      y -= i;
    }
    else if ( av < x ) 
    {
      y |= i;
    }
    else
    {
      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

void loop()
{
  int i;
  // antilog_drive
  for( i=MIN_X; i<MAX_X; i++ )
  {
    analogWrite( LED, antilog( i ) );
    delay( 2 );
  }
  for( --i; i>=MIN_X; i-- )
  {
    analogWrite( LED, antilog( i ) );
    delay( 2 );
  }
  delay( 1000 );
  // Linear drive
  for( i=MIN_X; i<MAX_X; i++ )
  {
    analogWrite( LED, i>>2 );
    delay( 2 );
  }
  for( --i; i>=MIN_X; i-- )
  {
    analogWrite( LED, i>>2 );
    delay( 2 );
  }
  delay( 2000 );
}
    
respondido por el tash
1

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

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

Aquí para 16 pasos.

    
respondido por el FarO
0

Para mí, esta ley parece funcionar bastante bien: enlace

    
respondido por el hachpai

Lea otras preguntas en las etiquetas