Исправление нелинейной яркости в светодиодах при использовании ШИМ


33

При управлении светодиодом с широтно-импульсной модуляцией яркость (как я ее понимаю) не зависит линейно от коэффициента заполнения. Яркость медленно увеличивается, а затем экспоненциально увеличивается с коэффициентом заполнения.

Может ли кто-нибудь предложить практическое правило для использования в качестве поправочного коэффициента или другого обходного пути?


Когда я сделал пару запонок для Knight Rider, мне пришлось использовать x ^ 10, чтобы эффект затухания выглядел красиво!
Ракетный

3
Вы уверены, что это не «яркость изначально увеличивается в геометрической прогрессии, а затем медленно увеличивается»?
Дмитрий Григорьев

1
Я считаю, что наши глаза логарифмически реагируют на яркость.
DKNguyen

Ответы:


13

Для 16 уровней легко составить простую справочную таблицу «вручную» и преобразовать 4-битное значение в 8-битное значение для передачи в ШИМ-контроллер: это компонент, который я использовал в своем драйвере светодиодного массива FPGA. Для 8-битного контроллера уровня вам понадобится как минимум 11-12 битный вывод из справочной таблицы.

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;

Я пытаюсь выяснить, какова ваша формула. Это замечательно близко к f (x) = x ^ 2, но кривая недостаточно глубока. F (X) = X ^ 3/13 приближает меня.
ajs410

Это не формула (не преднамеренно) ... Я бросил в линеаризатор начальные значения просто угадывая :-). Затем я запитал массив, привел светодиодные столбцы в порядке яркости и настроил значения, чтобы получить равномерное увеличение. Это действительно просто, всего 16 уровней.
Axeman

1
2N1

17

Теоретически это должно быть экспоненциально, но я получил лучшие результаты для затухания с помощью квадратичной функции.

Я также думаю, что вы получили это задом наперед. При малом рабочем цикле воспринимаемое увеличение яркости намного больше, чем при почти полном рабочем цикле, где увеличение яркости практически не заметно.


Смотрите также Гамма-коррекция .
starblue

17

В течение последних нескольких дней я изучал эту тему, поскольку у меня возникла та же проблема: я пытался затемнить светодиоды с помощью ШИМ с видимой линейностью, но мне нужно полное разрешение 256 шагов. Попытка угадать 256 чисел для создания кривой вручную - задача не из легких!

Я не эксперт по математике, но я знаю достаточно, чтобы генерировать некоторые базовые кривые, комбинируя несколько функций и формул, не зная, как они работают. Я обнаружил, что с помощью электронной таблицы (я использовал Excel) вы можете поиграть с набором чисел от 0 до 255, поместить несколько формул в следующую ячейку и построить их график.

Я использую pic на ассемблере для замирания, поэтому вы даже можете получить электронную таблицу для генерации кода на ассемблере с формулой ( ="retlw 0x" & DEC2HEX(A2)). Это позволяет очень быстро и легко опробовать новую кривую.

Немного поиграв с функциями LOG и SIN, усреднением двух и несколькими другими вещами, я не смог получить правильную кривую. Происходит то, что средняя часть замирания происходила медленнее, чем нижний и верхний уровни. Кроме того, если сразу за постепенным исчезновением следует постепенное уменьшение, то наблюдается резкий заметный всплеск интенсивности. Что нужно (на мой взгляд) это S-образная кривая.

При быстром поиске в Википедии была найдена формула, необходимая для кривой S. Я включил это в свою электронную таблицу и сделал несколько корректировок, чтобы он умножился на мой диапазон значений, и придумал следующее:

S curve

Я проверил его на своей установке, и он работал прекрасно.

Формула Excel, которую я использовал, была такой:

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

где A2 - первое значение в столбце A, которое увеличивает A3, A4, ..., A256 для каждого значения.

Я понятия не имею, математически правильно это или нет, но это дает желаемые результаты.

Вот полный набор из 256 уровней, которые я использовал:

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

Это уравнение отлично сработало для меня.
Игнасио Васкес-Абрамс


4

Я использовал ATtiny для освещения своей колоды. Яркость контролируется с помощью кюветы, подключенной к выводу АЦП.

Испытанная экспоненциальная функция и ШИМ-выход, основанный на этом, дают линейное увеличение воспринимаемой яркости.

Я использовал эту формулу:

out = pow(out_max, in/in_max)

Attiny85 @ 8MHz потребовалось около 210us для выполнения вышеуказанного расчета. Для повышения производительности сделана справочная таблица. Поскольку вход был из 10-битного АЦП и объем памяти ATtiny ограничен, я хотел также создать более короткую таблицу.

Вместо создания таблицы поиска с 1024 записями была создана таблица обратного просмотра с 256 записями (512 байтов) в памяти программ (PGMEM). Была написана функция для выполнения бинарного поиска по этой таблице. Этот метод занимает всего 28 мкс для каждого поиска. Если я использую таблицу прямого просмотра, это потребует 2 КБ памяти, но поиск займет всего 4 мкс или около того.

Вычисленные значения в справочной таблице используют только диапазон ввода 32-991, исключая нижний / верхний диапазон АЦП, в случае возникновения проблем со схемой.

Ниже то, что у меня есть сейчас.

// тестовая программа anti_log

/ * Светодиод подключен к PIN6 (PB1) * /
#define LED 1 

// Anti-Log (обратная) таблица поиска 
// y = 0-255 (вывод pwm), y_range = 256
// x = 0-1023 (10-битный вход АЦП); 
// предполагается, что нельзя использовать нижний / верхний предел значений АЦП
// отбрасываем первые 32 и последние 32 значения.
// 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)
// учитывая значение x, выполнить бинарный поиск по таблице ниже
// занимает около 28 мкс для Attiny85 при тактовой частоте 8 МГц
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
};

// Двоичный поиск с использованием таблицы выше.
байтовый антилог (int x)
{
  байт у = 0х80;
  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 );
}

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.


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


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.


Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.