Korekta jasności nieliniowej w diodach LED podczas korzystania z PWM


33

Podczas jazdy diodą LED z PWM jasność (tak jak ją postrzegam) nie zmienia się liniowo wraz z cyklem roboczym. Jasność powoli rośnie, a następnie rośnie wykładniczo wraz z cyklem roboczym.

Czy ktoś może zasugerować ogólną zasadę jako czynnik korygujący lub inne obejście?


Kiedy zrobiłem parę spinki do mankietów Knight Rider, musiałem użyć x ^ 10, aby efekt zanikania wyglądał ładnie!
Rocketmagnet

3
Czy na pewno nie „początkowo jasność rośnie wykładniczo, a następnie powoli rośnie”?
Dmitrij Grigoryev,

1
Wierzę, że nasze oczy reagują logarytmicznie na jasność.
DKNguyen

Odpowiedzi:


13

Dla 16 poziomów łatwo jest zrobić „ręcznie” tablicę przeglądową i przekonwertować 4-bitową wartość na 8-bitową, aby przekazać ją do kontrolera PWM: jest to komponent, którego użyłem w moim sterowniku tablicy LED FPGA. W przypadku 8-bitowego kontrolera poziomu potrzebujesz co najmniej 11-12 bitów z tabeli przeglądowej.

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;

Próbuję dowiedzieć się dokładnie, jaka jest twoja formuła. Jest niezwykle zbliżony do f (x) = x ^ 2, ale krzywa nie jest wystarczająco głęboka. f (x) = x ^ 3/13 przybliża mnie do siebie.
ajs410,

To nie jest formuła (nie celowo) ... Włączyłem początkowe wartości linearyzatora, zgadując :-). Następnie zasiliłem tablicę, napędzając kolumny diod LED w kolejności jasności i poprawiłem wartości, aby uzyskać równą rampę. To naprawdę proste z zaledwie 16 poziomami.
Axeman

1
2)n1

17

Teoretycznie powinno to być wykładnicze, ale mam najlepsze wyniki dla zanikania za pomocą funkcji kwadratowej.

Myślę też, że masz to wstecz. W niskim cyklu pracy zauważalny wzrost jasności jest znacznie większy niż w prawie pełnym cyklu pracy, w którym wzrost jasności jest prawie nie do pomyślenia.


Zobacz także Korekcja gamma .
starblue

17

W ciągu ostatnich kilku dni przyglądałem się temu tematowi, ponieważ mam ten sam problem ... próbuję przyciemnić diody LED za pomocą PWM w sposób wyraźnie liniowy, ale chcę mieć rozdzielczość 256 kroków. Próba odgadnięcia 256 liczb w celu ręcznego utworzenia krzywej nie jest łatwym zadaniem!

Nie jestem matematykiem-ekspertem, ale wiem wystarczająco dużo, aby wygenerować podstawowe krzywe, łącząc kilka funkcji i formuł, nie wiedząc, jak działają. Uważam, że korzystając z arkusza kalkulacyjnego (korzystałem z programu Excel) możesz bawić się zestawem liczb od 0 do 255, wstawiać kilka formuł w następnej komórce i rysować je.

Używam Pic Asemblera do zanikania, więc możesz nawet pobrać arkusz kalkulacyjny do wygenerowania kodu asemblera za pomocą formuły ( ="retlw 0x" & DEC2HEX(A2)). Dzięki temu wypróbowanie nowej krzywej jest bardzo szybkie i łatwe.

Po odrobinie zabawy z funkcjami LOG i SIN, średnią z dwóch i kilku innych rzeczy, nie mogłem naprawdę uzyskać właściwej krzywej. To, co się dzieje, to fakt, że środkowa część przenikania zachodziła wolniej niż niższy i wyższy poziom. Ponadto, jeśli natychmiast po zanikaniu następuje zanikanie, nastąpił wyraźny skok intensywności. Moim zdaniem potrzebna jest krzywa S.

Szybkie wyszukiwanie w Wikipedii przyniosło wzór potrzebny na krzywą S. Podłączyłem to do arkusza kalkulacyjnego i wprowadziłem kilka zmian, aby pomnożyć go w całym zakresie wartości, i wymyśliłem:

Krzywa S.

Przetestowałem to na moim urządzeniu i działało pięknie.

Użyłem formuły Excel:

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

gdzie A2 jest pierwszą wartością w kolumnie A, która zwiększa A3, A4, ..., A256 dla każdej wartości.

Nie mam pojęcia, czy jest to poprawne matematycznie, czy nie, ale przynosi pożądane rezultaty.

Oto pełny zestaw 256 poziomów, których użyłem:

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

To równanie działało dla mnie idealnie.
Ignacio Vazquez-Abrams,


4

Użyłem ATtiny do oświetlenia mojej talii. Jasność jest kontrolowana za pomocą naczynia podłączonego do pinu ADC.

Wypróbowana funkcja wykładnicza i oparty na niej wynik PWM wydają się liniowo zwiększać postrzeganą jasność.

Korzystałem z tych wzorów:

out = pow(out_max, in/in_max)

Attiny85 @ 8MHz wykonało powyższe obliczenia około 210us. Aby poprawić wydajność, utworzono tabelę odnośników. Ponieważ dane wejściowe pochodziły z 10-bitowego ADC, a pamięć ATtiny jest ograniczona, chciałem również stworzyć krótszą tabelę.

Zamiast tworzyć tabelę wyszukiwania z 1024 pozycjami, utworzono tabelę wyszukiwania wstecznego z 256 pozycjami (512 bajtów) w pamięci programu (PGMEM). Została napisana funkcja do wykonywania wyszukiwania binarnego na tej tabeli. Ta metoda wymaga tylko 28uS dla każdego wyszukiwania. Jeśli użyję tabeli bezpośredniego wyszukiwania, wymagałoby to pamięci 2kb, ale wyszukiwanie zajmie tylko około 4uS.

Obliczone wartości w tabeli odnośników wykorzystują tylko zakres wejściowy 32-991, odrzucając dolny / górny zakres ADC, w przypadku problemów z obwodem.

Poniżej mam to, co mam teraz.

// program testowy anti_log

/ * Dioda LED podłączona do PIN6 (PB1) * /
# zdefiniować diodę LED 1 

// Tabela wyszukiwania Anti-Log (wstecz) 
// y = 0-255 (wyjście pwm), y_range = 256
// x = 0-1023 (10-bitowe wejście ADC); 
// zakładając, że nie można użyć niższych / wyższych wartości wyjściowych ADC
// odrzucając pierwsze 32 i ostatnie 32 wartości.
// min_x = 32, max_x = 1023-min_x, x_range = 1024-2 * min_x
// ANTI_LOG [y] = okrągły (x_zakres * log (y, podstawa = y_zakres) + min_x)
// biorąc pod uwagę wartość x, wykonaj wyszukiwanie binarne w poniższej tabeli
// zajmuje około 28uS dla zegara 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
};

// Wyszukiwanie binarne przy użyciu powyższej tabeli.
byte antilog (int x)
{
  bajt y = 0x80;
  int av;
  dla (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;
    }
    jeszcze
    {
      zwróć y;
    }
  }
  if (pgm_read_word_near (ANTI_LOG + y)> x)
  {
    y - = 1;
  }
  zwróć y;
}


void setup ()
{
  pinMode (LED, WYJŚCIE);
  digitalWrite (LED, LOW);
}

# zdefiniować MIN_X 0
# zdefiniować MAX_X 1024

void loop ()
{
  int i;
  // antilog_drive
  dla (i = MIN_X; i <MAX_X; i ++)
  {
    analogWrite (LED, antilog (i));
    opóźnienie (2);
  }
  dla (--i; i> = MIN_X; i--)
  {
    analogWrite (LED, antilog (i));
    opóźnienie (2);
  }
  opóźnienie (1000);
  // Napęd liniowy
  dla (i = MIN_X; i <MAX_X; i ++)
  {
    analogWrite (LED, i >> 2);
    opóźnienie (2);
  }
  dla (--i; i> = MIN_X; i--)
  {
    analogWrite (LED, i >> 2);
    opóźnienie (2);
  }
  opóźnienie (2000);
}

1

Ten plik PDF wyjaśnia wymaganą krzywą, najwyraźniej logarytmiczną. Jeśli masz ściemniacz liniowy (twoja wartość PWM), to funkcja powinna być logarytmiczna.

Tutaj możesz znaleźć tabelę przeglądową dla 32 kroków jasności dla 8 bitów PWM.

Tutaj 16 kroków.


1

Oto, co zrobiłem w oparciu o odpowiedź forum arduino . Obliczyłem wartości od 0 do 255, więc jest łatwy w użyciu z pwm na 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};

Aby skorzystać z Arduino, po prostu zrób tak:

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

Mam nadzieję, że jest to pomocne dla niektórych osób;)


1

Radzę sobie z tym teraz i podchodzę nieco inaczej. Chcę 256 poziomów jasności, ale mapowanie liniowego zakresu 0-255 na nieliniowy zakres 0-255 kończy się, jak widać w niektórych innych odpowiedziach, z wieloma zduplikowanymi wpisami. (Tzn. Kilka wartości wejściowych powoduje ten sam poziom jasności).

Próbowałem zmodyfikować algorytm, aby odwzorować zakres wejściowy 0–256 na zakres wyjściowy 0–1023, ale nawet to miało kilka wartości odwzorowujących na 0. Więc próbuję czegoś nieco innego - używam poziomu 0–255 aby wygenerować wartości nieliniowe w zakresie 0-769 (to jest 1023 minus 255) za pomocą sin(), a następnie dodać to do poziomu wejściowego, aby uzyskać wynik w zakresie 0-1023 bez duplikatów. Skonfiguruję licznik, aby używał licznika 1023, i ustawię komparator dla wyjścia PWM na wartości z tabeli odnośników w zależności od tego, jaki poziom oświetlenia chcę (0–255).

Oto program C, którego użyłem do wygenerowania mojej tabeli odnośników:

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

A oto tabela:

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

Prawdopodobnie zbadam inne funkcje (jak log()), kiedy już to uruchomię.


Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.