使用PWM时校正LED的非线性亮度


33

当使用PWM驱动LED时,亮度(如我所知)不会随占空比线性变化。亮度缓慢上升,然后随占空比呈指数增加。

谁能建议将经验法则用作校正因子或其他解决方法?


当我制作一对Knight Rider袖扣时,我不得不使用x ^ 10来使淡出效果看起来不错!
Rocketmagnet 2013年

3
您确定不是“亮度最初呈指数增长,然后缓慢上升”吗?
德米特里·格里戈里耶夫

1
我相信我们的眼睛对亮度做出对数响应。
DKNguyen

Answers:


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个级别,这真的很容易。
Axeman

1
@ ajs410- 在我看来更像是:每走一步,第一位或多或少地向左移1个位置。2ñ1
stevenvh 2011年

17

从理论上讲,它应该是指数级的,但是通过使用二次函数,我获得了最佳的淡入效果。

我也认为您倒退了。在低占空比下,感觉到的亮度增加要比几乎在整个占空比下几乎看不到亮度增加得多。


另请参见伽玛校正
starblue

17

在过去的几天里,我一直在研究这个问题,因为我遇到了同样的问题...尝试以可见的线性方式使用PWM调光LED,但我想要完整的256步分辨率。尝试猜测256个数字以手动创建曲线绝非易事!

我不是专业的数学家,但我了解足够多,可以通过合并一些函数和公式来生成一些基本曲线,而实际上并不知道它们是如何工作的。我发现使用电子表格(我使用Excel)可以处理一组从0到255的数字,在下一个单元格中放置一些公式,然后对它们进行绘图。

我正在使用pic汇编程序进行淡入淡出,因此您甚至可以获取电子表格以使用公式(="retlw 0x" & DEC2HEX(A2))生成汇编程序代码。这样可以非常轻松快捷地尝试一条新曲线。

经过一些LOG和SIN函数,两者的平均值以及其他一些操作之后,我无法真正找到正确的曲线。发生的情况是,淡入淡出的中间部分比较高和较低的水平慢。同样,如果在淡入淡出后立即进行淡入淡出,则强度会出现明显的尖峰。(我认为)需要一条S曲线。

Wikipedia上快速搜索了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存储器受到限制,因此我也想创建一个较短的表。

在程序存储器(PGMEM)中创建了具有256个条目(512字节)的反向查找表,而不是创建具有1024个条目的查找表。编写了一个函数来对该表执行二进制搜索。每次查找仅需28uS。如果使用直接查找表,它将需要2kb的内存,但是查找只需要4uS左右。

查找表中的计算值仅使用输入范围32-991,如果电路有问题,则放弃ADC的较低/较高范围。

以下是我现在所拥有的。

// anti_log测试程序

/ * LED连接到PIN6(PB1)* /
#定义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
程序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);
    如果(av> x)
    {
      y-= i;
    }
    否则(av <x) 
    {
      y | = i;
    }
    其他
    {
      返回y;
    }
  }
  如果(pgm_read_word_near(ANTI_LOG + y)> x)
  {
    y-= 1;
  }
  返回y;
}


无效setup()
{
  pinMode(LED,输出);
  digitalWrite(LED,LOW);
}

#定义MIN_X 0
#定义MAX_X 1024

无效循环()
{
  我
  // antilog_drive
  for(i = MIN_X; i <MAX_X; i ++)
  {
    AnalogWrite(LED,antilog(i));
    延迟(2);
  }
  for(--i; i> = MIN_X; i--)
  {
    AnalogWrite(LED,antilog(i));
    延迟(2);
  }
  延迟(1000);
  //直线驱动
  for(i = MIN_X; i <MAX_X; i ++)
  {
    AnalogWrite(LED,i >> 2);
    延迟(2);
  }
  for(--i; i> = MIN_X; i--)
  {
    AnalogWrite(LED,i >> 2);
    延迟(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-255级使用生成0-769范围(即1023减去255)范围内的非线性值sin(),然后将其添加到输入级别以得到0-1023范围内的输出(无重复)。我将配置一个计时器以使用1023计数器,并根据我想要的照明级别(0-255)将PWM输出的比较器设置为查找表中的值。

这是我用来生成查询表的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()一旦启动并运行它,我可能会研究其他功能(例如)。


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.