Mengoreksi kecerahan non-linear pada LED saat menggunakan PWM


33

Saat mengendarai LED dengan PWM, kecerahan (seperti yang saya pahami) tidak berskala linier dengan siklus kerja. Kecerahan lambat untuk meningkat, kemudian meningkat secara eksponensial dengan siklus tugas.

Adakah yang bisa menyarankan aturan praktis untuk digunakan sebagai faktor koreksi, atau solusi lain?


Ketika saya membuat sepasang manset Knight Rider, saya harus menggunakan x ^ 10 untuk membuat pudar terlihat bagus!
Rocketmagnet

3
Apakah Anda yakin itu bukan "kecerahan awalnya meningkat secara eksponensial, dan kemudian lambat untuk meningkat"?
Dmitry Grigoryev

1
Saya percaya mata kita merespons secara logis terhadap kecerahan.
DKNguyen

Jawaban:


13

Untuk 16 level, mudah untuk membuat tabel pencarian sederhana "dengan tangan" dan mengonversi nilai 4 bit menjadi nilai 8 bit untuk diteruskan ke pengontrol PWM: ini adalah komponen yang saya gunakan dalam driver array yang dipimpin FPGA. Untuk pengontrol level 8 bit, Anda membutuhkan setidaknya 11-12 bit output dari tabel pencarian.

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;

Saya mencoba mencari tahu apa formula Anda. Ini sangat dekat dengan f (x) = x ^ 2, tetapi kurva tidak cukup dalam. f (x) = x ^ 3/13 membuat saya lebih dekat.
ajs410

Ini bukan rumus (tidak sengaja) ... Saya sudah tahu nilai-nilai awal linearizer hanya menebak :-). Saya kemudian memberi daya pada array, menggerakkan kolom yang dipimpin dalam urutan kecerahan, dan mengubah nilai untuk mendapatkan ramp yang rata. Ini sangat mudah dengan hanya 16 level.
Axeman

1
@ ajs410 - lebih mirip bagi saya: bit pertama lebih atau kurang bergeser meninggalkan 1 posisi ke kiri dengan setiap langkah. 2n1
stevenvh

17

Secara teori itu harus eksponensial, tapi saya punya hasil terbaik untuk memudar dengan menggunakan fungsi kuadratik.

Saya juga berpikir Anda mendapatkannya mundur. Pada siklus rendah, peningkatan kecerahan yang dirasakan jauh lebih besar daripada pada siklus hampir penuh, di mana peningkatan kecerahan hampir tidak dapat digerakkan.


Lihat juga koreksi Gamma .
starblue

17

Saya telah melihat ke dalam subjek ini selama beberapa hari terakhir karena saya memiliki masalah yang sama ... mencoba meredupkan LED menggunakan PWM dalam cara yang terlihat linier, tapi saya ingin resolusi penuh 256 langkah. Mencoba menebak 256 angka untuk membuat kurva secara manual bukanlah tugas yang mudah!

Saya bukan ahli matematika, tapi saya cukup tahu untuk menghasilkan beberapa kurva dasar dengan menggabungkan beberapa fungsi dan formula tanpa benar-benar tahu cara kerjanya. Saya menemukan bahwa menggunakan spreadsheet (saya menggunakan Excel) Anda bisa bermain-main dengan satu set angka dari 0 hingga 255, meletakkan beberapa rumus di sel berikutnya, dan grafik mereka.

Saya menggunakan assembler pic untuk melakukan fading, dan Anda bahkan bisa mendapatkan spreadsheet untuk menghasilkan kode assembler dengan formula ( ="retlw 0x" & DEC2HEX(A2)). Ini membuatnya sangat cepat dan mudah untuk mencoba kurva baru.

Setelah sedikit bermain-main dengan fungsi LOG dan SIN, rata-rata keduanya, dan beberapa hal lainnya, saya tidak bisa benar-benar mendapatkan kurva yang tepat. Apa yang terjadi adalah bahwa bagian tengah fade terjadi lebih lambat daripada level yang lebih rendah dan lebih tinggi. Juga, jika fade-up segera diikuti oleh fade-down ada lonjakan tajam yang terlihat dalam intensitas. Apa yang dibutuhkan (menurut saya) adalah kurva S.

Pencarian cepat di Wikipedia muncul dengan rumus yang diperlukan untuk kurva S. Saya menyambungkan ini ke spreadsheet saya, dan membuat beberapa penyesuaian untuk membuatnya melipatgandakan rentang nilai saya, dan muncul dengan ini:

Kurva S

Saya mengujinya di rig saya, dan itu bekerja dengan indah.

Rumus Excel yang saya gunakan adalah ini:

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

di mana A2 adalah nilai pertama dalam kolom A, yang meningkatkan A3, A4, ..., A256 untuk setiap nilai.

Saya tidak tahu apakah ini benar secara matematis atau tidak, tetapi menghasilkan hasil yang diinginkan.

Berikut adalah set lengkap 256 level yang saya gunakan:

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

Persamaan ini bekerja dengan baik untuk saya.
Ignacio Vazquez-Abrams


4

Saya menggunakan ATtiny untuk menyalakan dek saya. Kecerahan dikontrol menggunakan pot yang terhubung ke pin ADC.

Mencoba fungsi eksponensial dan output PWM berdasarkan yang tampaknya memberikan peningkatan linear dalam kecerahan yang dirasakan.

Saya menggunakan rumus ini:

out = pow(out_max, in/in_max)

Attiny85 @ 8MHz membutuhkan sekitar 210us untuk melakukan perhitungan di atas. Untuk meningkatkan kinerja, buat tabel pencarian. Karena input dari ADC 10-bit dan memori ATtiny terbatas, saya ingin membuat tabel yang lebih pendek juga.

Alih-alih membuat tabel pencarian dengan 1024 entri, buat tabel pencarian terbalik dengan 256 entri (512 byte) dalam memori program (PGMEM). Fungsi ditulis untuk melakukan pencarian biner pada tabel itu. Metode ini hanya membutuhkan 28uS untuk setiap pencarian. Jika saya menggunakan tabel pencarian langsung, itu akan membutuhkan memori 2kb, tetapi pencarian hanya membutuhkan sekitar 4uS.

Nilai yang dihitung dalam tabel pencarian hanya menggunakan rentang input 32-991, membuang kisaran ADC yang lebih rendah / atas, jika ada masalah dengan rangkaian.

Di bawah ini yang saya miliki sekarang.

// program uji anti_log

/ * LED terhubung ke PIN6 (PB1) * /
#define LED 1 

// Tabel pencarian Anti-Log (mundur) 
// y = 0-255 (output PWM), y_range = 256
// x = 0-1023 (input ADC 10-bit); 
// mengasumsikan nilai ADC out yang lebih rendah / lebih tinggi tidak dapat digunakan
// membuang 32 nilai pertama dan 32 nilai terakhir.
// min_x = 32, max_x = 1023-min_x, x_range = 1024-2 * min_x
// ANTI_LOG [y] = bulat (x_range * log (y, base = y_range) + min_x)
// diberi nilai x, lakukan pencarian biner pada tabel di bawah ini
// memakan waktu sekitar 28uS untuk jam Attiny85 @ 8MHz
PROGMEM prog_uint16_t ANTI_LOG [] = {
  0x0000, 0x0020, 0x0098, 0x00de, 0x0110, 0x0137, 0x0151, 0x0171, 0x0188, 0x019c, 0x01af, 0x01bf, 0x01ce, 0x01dc, 0x01e9, 0x01e9,
  0x0200, 0x020a, 0x0214, 0x021e, 0x0227, 0x022f, 0x0237, 0x023f, 0x0246, 0x0244, 0x0254, 0x025b, 0x0261, 0x0226, 0x026d, 0x026d, 0x026d, 0x026,
  0x0278, 0x027d, 0x0282, 0x0288, 0x028c, 0x0291, 0x0296, 0x029a, 0x029f, 0x02a3, 0x02a7, 0x02ab, 0x02b3, 0x02b3, 0x02b7, 0x02b7, 0x0229,
  0x02be, 0x02c2, 0x02c5, 0x02c9, 0x02cf, 0x02d3, 0x02d6, 0x02d6, 0x02d9, 0x02dc, 0x02df, 0x02e2, 0x02e5, 0x02e8, 0x02eb8, 0x02eb8, 0x02e8, 0x02e9
  0x02f0, 0x02f3, 0x02f5, 0x02f8, 0x02fa, 0x02fd, 0x0302, 0x0302, 0x0304, 0x0307, ​​0x0309, 0x030b, 0x03010, 0x0310, 0x0312, 0x0312, 0x02
  0x0317, 0x0319, 0x031b, 0x031d, 0x031f, 0x0321, 0x0323, 0x0325, 0x0329, 0x0329, 0x032b, 0x032d, 0x032f, 0x0321, 0x0333, 0x031,
  0x0336, 0x0338, 0x033a, 0x033c, 0x033d, 0x0341, 0x0341, 0x0342, 0x0346, 0x0347, 0x0347, 0x0349, 0x034b, 0x034c, 0x034c, 0x034f, 0x034f
  0x0351, 0x0352, 0x0354, 0x0355, 0x0357, 0x0358, 0x035a, 0x035b, 0x035d, 0x035e, 0x0360, 0x0361, 0x0363, 0x0363, 0x0364, 0x0364, 0x0364, 0x0354, 0x0354
  0x0368, 0x0369, 0x036b, 0x036c, 0x036d, 0x036f, 0x0370, 0x0371, 0x0374, 0x0374, 0x0375, 0x0376, 0x0378, 0x0379, 0x0379, 0x037b, 0x036c
  0x037c, 0x037e, 0x037f, 0x0380, 0x0381, 0x0382, 0x0383, 0x0385, 0x0387, 0x0387, 0x0388, 0x0389, 0x038a, 0x038b, 0x038b, 0x038c, 0x038c
  0x038f, 0x0390, 0x0391, 0x0392, 0x0393, 0x0395, 0x0396, 0x0397, 0x0398, 0x0399, 0x039a, 0x039b, 0x039b, 0x039c, 0x039c, 0x039d, 0x039d
  0x039f, 0x03a0, 0x03a1, 0x03a2, 0x03a3, 0x03a4, 0x03a5, 0x03a7, 0x03a7, 0x03a8, 0x03a9, 0x03a9, 0x03a0, 0x03ab, 0x03a0, 0x03a3
  0x03ae, 0x03af, 0x03b0, 0x03b1, 0x03b2, 0x03b3, 0x03b4, 0x03b5, 0x03b5, 0x03b5, 0x03b7, 0x03b7, 0x03b8, 0x03b9, 0x03b9, 0x03b9, 0x03b9, 0x03b7
  0x03bc, 0x03bd, 0x03bf, 0x03bf, 0x03bf, 0x03c0, 0x03c1, 0x03c3, 0x03c3, 0x03c3, 0x03c4, 0x03c5, 0x03c7, 0x03c7, 0x03c7, 0x03c7, 0x03c7, 0x03c3
  0x03c9, 0x03ca, 0x03ca, 0x03cb, 0x03cc, 0x03cd, 0x03cd, 0x03ce, 0x03cf, 0x03d0, 0x03d0, 0x03d1, 0x03d1, 0x03d2, 0x03d3, 0x03d3, 0x03d3, 0x03c9
  0x03d5, 0x03d6, 0x03d6, 0x03d7, 0x03d8, 0x03d8, 0x03d9, 0x03db, 0x03db, 0x03dc, 0x03dd, 0x03dd, 0x03dd, 0x03dd, 0x03dd, 0x03dd, 0x03d6
};

// Pencarian biner menggunakan tabel di atas.
byte antilog (int x)
{
  byte y = 0x80;
  int av;
  untuk (int i = 0x40; i> 0; i >> = 1)
  {
    av = pgm_read_word_near (ANTI_LOG + y);
    jika (av> x)
    {
      y - = i;
    }
    lain jika (av <x) 
    {
      y | = i;
    }
    lain
    {
      kembalikan y;
    }
  }
  if (pgm_read_word_near (ANTI_LOG + y)> x)
  {
    y - = 1;
  }
  kembalikan y;
}


pengaturan batal ()
{
  pinMode (LED, OUTPUT);
  digitalWrite (LED, LOW);
}

#define MIN_X 0
#define MAX_X 1024

membatalkan loop ()
{
  int i;
  // antilog_drive
  untuk (i = MIN_X; i <MAX_X; i ++)
  {
    analogWrite (LED, antilog (i));
    keterlambatan (2);
  }
  untuk (--i; i> = MIN_X; i--)
  {
    analogWrite (LED, antilog (i));
    keterlambatan (2);
  }
  keterlambatan (1000);
  // Penggerak linear
  untuk (i = MIN_X; i <MAX_X; i ++)
  {
    analogWrite (LED, i >> 2);
    keterlambatan (2);
  }
  untuk (--i; i> = MIN_X; i--)
  {
    analogWrite (LED, i >> 2);
    keterlambatan (2);
  }
  delay (2000);
}

1

PDF ini menjelaskan kurva yang diperlukan, tampaknya yang logaritmik. Jika Anda memiliki linear dimmer (nilai PWM Anda) maka fungsinya harus logaritmik.

Di sini Anda dapat menemukan tabel pencarian untuk 32 langkah kecerahan untuk 8 bit PWM.

Di sini untuk 16 langkah.


1

Inilah yang saya lakukan berdasarkan respon forum arduino . Saya telah menghitung nilai dari 0 hingga 255 sehingga mudah digunakan dengan PWM di 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};

Kemudian untuk digunakan pada Arduino lakukan saja seperti itu:

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

Semoga bermanfaat bagi sebagian orang;)


1

Saya berurusan dengan ini sekarang, dan saya mengambil pendekatan yang sedikit berbeda. Saya ingin 256 tingkat kecerahan, tetapi memetakan rentang 0-255 linier ke rentang 0-255 non-linear berakhir, seperti yang dapat Anda lihat di beberapa jawaban lain, dengan banyak entri duplikat. (Yaitu, beberapa nilai input Anda menghasilkan tingkat kecerahan yang sama.)

Saya mencoba memodifikasi algoritme untuk memetakan rentang input 0-256 ke rentang output 0-1023, tetapi bahkan memiliki beberapa pemetaan nilai hingga 0. Jadi saya mencoba sesuatu yang sedikit berbeda - Saya menggunakan level 0-255 untuk menghasilkan nilai non-linear dalam rentang 0-769 (yaitu 1023 minus 255) menggunakan sin(), kemudian tambahkan itu ke level input untuk mendapatkan output dalam kisaran 0-1023 tanpa duplikat. Saya akan mengkonfigurasi timer untuk menggunakan penghitung 1023, dan mengatur komparator untuk output PWM ke nilai dari tabel pencarian berdasarkan tingkat pencahayaan apa yang saya inginkan (0-255).

Inilah program C yang saya gunakan untuk menghasilkan tabel pencarian saya:

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

Dan inilah tabelnya:

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

Saya mungkin akan menyelidiki fungsi-fungsi lain (seperti log()) setelah saya menjalankan dan menjalankannya.


Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.