Виправлення нелінійної яскравості світлодіодів при використанні ШІМ


33

Під час керування світлодіодом із ШІМ яскравість (як я це сприймаю) не масштабується лінійно з робочим циклом. Яскравість повільно наростає, потім зростає експоненціально з робочим циклом.

Чи може хтось запропонувати правило, яке слід використовувати як коригуючий коефіцієнт, або інше рішення?


Коли я зробив пару манжет Knight Rider, мені довелося використовувати x ^ 10, щоб зникнення виглядало красиво!
Rocketmagnet

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
@ ajs410 - більше схожий 2ндля мене: перший 1біт більш-менш зміщує ліву позицію на 1 вліво з кожним кроком.
stevenvh

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 @ 8 МГц для виконання вищезгаданого обчислення потребував приблизно 210us. Для підвищення продуктивності зробили таблицю пошуку. Оскільки вхід був від 10-розрядного АЦП і пам'ять ATtiny обмежена, я також хотів створити більш коротку таблицю.

Замість того, щоб робити таблицю пошуку з 1024 записами, зробили таблицю зворотного пошуку з 256 записами (512 байти) в пам'яті програми (PGMEM). Була написана функція для виконання двійкового пошуку в цій таблиці. Цей метод займає лише 28uS для кожного пошуку. Якщо я використовую таблицю прямого пошуку, для цього знадобиться 2 кб пам'яті, але пошук займе лише 4uS або близько того.

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

Нижче - що я маю зараз.

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

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

// Таблиця пошуку анти-журналу (зворотного) 
// y = 0-255 (вихідний пвм), 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] = круглий (x_range * журнал (y, основа = y_range) + min_x)
// задавши значення x, виконайте двійковий пошук нижче таблиці
// займає близько 28uS для годин 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
};

// Двійковий пошук за допомогою наведеної вище таблиці.
байт-антилог (int x)
{
  байт y = 0x80;
  int av;
  для (int i = 0x40; i> 0; i >> = 1)
  {
    av = pgm_read_word_near (ANTI_LOG + y);
    якщо (av> x)
    {
      y - = i;
    }
    інакше якщо (av <x) 
    {
      y | = i;
    }
    ще
    {
      повернути y;
    }
  }
  якщо (pgm_read_word_near (ANTI_LOG + y)> x)
  {
    у - = 1;
  }
  повернути y;
}


встановлення недійсності ()
{
  pinMode (світлодіодний, вихідний);
  digitalWrite (світлодіодний, низький);
}

#define MIN_X 0
#define MAX_X 1024

невірна петля ()
{
  int i;
  // antilog_drive
  для (i = MIN_X; i <MAX_X; i ++)
  {
    analogWrite (світлодіод, антилог (i));
    затримка (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.