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

电器工程 引领 脉宽调制
2022-01-29 09:14:08

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

任何人都可以建议将经验法则用作校正因子或其他解决方法吗?

4个回答

理论上它应该是指数的,但我通过使用二次函数得到了最好的衰减结果。

我也认为你把它弄反了。在低占空比下,感知到的亮度增加比在几乎完全占空比下要大得多,在这种情况下,亮度的增加几乎是不可察觉的。

在过去的几天里,我一直在研究这个主题,因为我遇到了同样的问题……尝试使用 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

对于 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;

我正在使用阁楼来照亮我的甲板。使用连接到 ADC 引脚的电位器控制亮度。

尝试过的指数函数和基于此的 PWM 输出似乎可以线性增加感知亮度。

我正在使用这个公式:

out = pow(out_max, in/in_max)

Attiny85 @8MHz 大约需要 210us 来执行上述计算。为了提高性能,做了一个查找表。由于输入来自 10 位 ADC 并且 ATtiny 内存有限,我也想创建一个较短的表。

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

查找表中的计算值仅使用输入范围 32-991,丢弃 ADC 的下限/上限范围,以防电路出现问题。

以下是我现在所拥有的。

// anti_log 测试程序

/*LED 连接到 PIN6(PB1)*/
#定义 LED 1

// Anti-Log(反向)查找表
// 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, 0,026d, 0x027
  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, 0x034
  0x0351, 0x0352, 0x0354, 0x0355, 0x0357, 0x0358, 0x035a, 0x035b, 0x035d, 0x035e, 0x0360, 0x0361, 0x0363, 0x0364, 0x0365, 0x0376
  0x0368, 0x0369, 0x036b, 0x036c, 0x036d, 0x036f, 0x0370, 0x0371, 0x0372, 0x0374, 0x0375, 0x0376, 0x0378, 0x0379, 0x037a, 0x037
  0x037c, 0x037e, 0x037f, 0x0380, 0x0381, 0x0382, 0x0383, 0x0385, 0x0386, 0x0387, 0x0388, 0x0389, 0x038a, 0x038b, 0x038c, 0x038
  0x038f, 0x0390, 0x0391, 0x0392, 0x0393, 0x0394, 0x0395, 0x0396, 0x0397, 0x0398, 0x0399, 0x039a, 0x039b, 0x039c, 0,039d, 0x039
  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, 0x038,
  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;
  诠释 av;
  for(int i=0x40;i>0;i>>=1)
  {
    av = pgm_read_word_near(ANTI_LOG+y);
    如果 ( av > x )
    {
      y -= 我;
    }
    否则如果 (av < x )
    {
      y |= 我;
    }
    别的
    {
      返回 y;
    }
  }
  如果 ( pgm_read_word_near( ANTI_LOG+y ) > x )
  {
    y -= 1;
  }
  返回 y;
}


无效设置()
{
  pinMode(LED,输出);
  数字写入(LED,低);
}

#define MIN_X 0
#define MAX_X 1024

无效循环()
{
  诠释我;
  // antilog_drive
  for(i=MIN_X;i<MAX_X;i++)
  {
    类比写(LED,antilog(我));
    延迟(2);
  }
  for(--i;i>=MIN_X;i--)
  {
    类比写(LED,antilog(我));
    延迟(2);
  }
  延迟(1000);
  // 直线驱动
  for(i=MIN_X;i<MAX_X;i++)
  {
    类比写入(LED,我>>2);
    延迟(2);
  }
  for(--i;i>=MIN_X;i--)
  {
    类比写入(LED,我>>2);
    延迟(2);
  }
  延迟(2000);
}