PWM使用時のLEDの非線形輝度の修正


33

PWMを使用してLEDを駆動する場合、明るさ(私が認識しているように)はデューティサイクルに比例してスケーリングしません。輝度の上昇は遅く、デューティサイクルとともに指数関数的に増加します。

誰かが補正係数として使用する経験則、または他の回避策を提案できますか?


ナイトライダーのカフスリンクを2つ作成したとき、フェードオフの見栄えを良くするためにx ^ 10を使用する必要がありました。
Rocketmagnet

3
「最初は明るさが指数関数的に増加し、その後、ランプアップが遅い」ということではないのですか?
ドミトリーグリゴリエフ

1
私たちの目は明るさに対して対数的に反応すると信じています。
DKNguyen

回答:


13

16レベルの場合、単純なルックアップテーブルを「手動」で実行し、4ビット値を8ビット値に変換してPWMコントローラーに渡すのは簡単です。これは、FPGA LEDアレイドライバーで使用したコンポーネントです。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

公式ではありません(意図的ではありません)...リニアライザの初期値を推測するだけで推測できます:-)。次に、アレイの電源を入れて、LEDの列を明るさの順に駆動し、値を微調整して均等なランプを得ました。16レベルしかないのでとても簡単です。
アックスマン

1
2n1

17

理論的には指数関数的である必要がありますが、2次関数を使用することでフェージングに最適な結果が得られました。

私はあなたがそれを逆にしたと思います。デューティサイクルが低い場合、明るさの知覚される増加は、ほぼ完全なデューティサイクルよりもはるかに大きく、明るさの増加はほとんど知覚できません。


ガンマ補正も参照してください。
スターブルー

17

私は同じ問題を抱えているため、過去数日間このテーマを調査していました... PWMを目に見える直線的に使用してLEDを暗くしようとしていますが、256ステップのフル解像度が必要です。256個の数字を推測して曲線を手動で作成するのは簡単なことではありません。

私は数学の専門家ではありませんが、それらがどのように機能するかを実際に知らなくても、いくつかの関数と数式を組み合わせていくつかの基本曲線を生成するのに十分なことを知っています。スプレッドシート(​​Excelを使用)を使用すると、0から255までの一連の数値をいじり、次のセルにいくつかの数式を入力し、グラフ化することができます。

私はpicアセンブラーを使用してフェードを行っているため、スプレッドシートを取得して式(="retlw 0x" & DEC2HEX(A2))でアセンブラーコードを生成することさえできます。これにより、新しい曲線を簡単に試すことができます。

LOG関数とSIN関数、2つの関数の平均、その他いくつかのことをいじってみたところ、適切な曲線を得ることができませんでした。何が起こっているのかは、フェードの中央部分が低レベルと高レベルよりもゆっくりと起こっていたことです。また、フェードアップの直後にフェードダウンが続く場合、強度に顕著な顕著なスパイクがありました。(私の意見では)必要なのはSカーブです。

ウィキペディアで簡単に検索すると、Sカーブに必要な式が見つかりました。これをスプレッドシートにプラグインし、値の範囲全体で乗算するようにいくつかの調整を行って、これを思い付きました:

Sカーブ

私は自分のリグでそれをテストしました、そしてそれは美しく働きました。

私が使用した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を使用していました。輝度は、ADCピンに接続されたポットを使用して制御されます。

指数関数を試してみたところ、それに基づいたPWM出力は、知覚される明るさを直線的に増加させているようです。

私はこの式を使用していました:

out = pow(out_max, in/in_max)

Attiny85 @ 8MHzは、上記の計算を実行するのに約210usかかっていました。パフォーマンスを向上させるために、ルックアップテーブルを作成しました。入力は10ビットADCからのものであり、ATtinyメモリは限られているため、より短いテーブルを作成したかったのです。

1024エントリのルックアップテーブルを作成する代わりに、プログラムメモリ(PGMEM)に256エントリ(512バイト)の逆ルックアップテーブルを作成しました。そのテーブルでバイナリ検索を実行する関数が作成されました。この方法では、ルックアップごとに28uSしかかかりません。直接ルックアップテーブルを使用する場合、2kbのメモリが必要になりますが、ルックアップには4uS程度しかかかりません。

ルックアップテーブルの計算値は、回路に問題がある場合に備えて、入力範囲32〜991のみを使用し、ADCの下位/上位範囲を破棄します。

以下は私が今持っているものです。

// anti_logテストプログラム

/ * PIN6(PB1)に接続されたLED * /
#define LED 1 

//アンチログ(逆)ルックアップテーブル 
// y = 0-255(pwm出力)、y_range = 256
// x = 0-1023(10ビットADC入力); 
// ADCの出力値の下限/上限は使用できないと想定
//最初の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を指定し、下のテーブルでバイナリルックアップを実行します
// Attiny85 @ 8MHzクロックで約28uSかかります
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;
  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;
    }
    他に
    {
      yを返す;
    }
  }
  if(pgm_read_word_near(ANTI_LOG + y)> x)
  {
    y-= 1;
  }
  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);
  }
  遅延(1000);
  //リニア駆動
  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

このPDFは、明らかに対数曲線である必要な曲線を説明しています。線形調光器(PWM値)がある場合、関数は対数でなければなりません。

ここでは、8ビットPWMの32ステップの輝度のルックアップテーブルを見つけることができます。

ここで16の手順を実行します。


1

そのarduinoフォーラムレスポンスに基づいて私がしたことをここに示します。私は0から255までの値を計算したので、arduinoのpwmで簡単に使用できます

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

次に、Arduinoで使用するには、そのようにします:

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

それが一部の人々に役立つことを願っています;)


1

私は今これを扱っていますが、少し異なるアプローチを取っています。256レベルの明るさが必要ですが、線形の0-255の範囲を非線形の0-255の範囲にマッピングすると、他の回答の一部でわかるように、多くの重複エントリがあります。(つまり、入力値のいくつかは同じ輝度レベルになります。)

0〜256の入力範囲を0〜1023の出力範囲にマッピングするようにアルゴリズムを変更しようとしましたが、それでもいくつかの値が0にマッピングされていました。を使用して範囲0-769(つまり1023-255)の非線形値を生成しsin()、それを入力レベルに追加して、重複なしで範囲0-1023の出力を取得します。1023のカウンターを使用するようにタイマーを構成し、PWM出力のコンパレーターを、必要な照明レベル(0〜255)に基づいたルックアップテーブルの値に設定します。

ルックアップテーブルの生成に使用したCプログラムは次のとおりです。

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

そして、これが表です。

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

log()これを立ち上げて実行したら、おそらく他の関数(など)を調査します。


弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.