PWM 사용시 LED의 비선형 밝기 보정


33

PWM으로 LED를 구동 할 때 밝기는 (인식 한대로) 듀티 사이클에 따라 선형으로 스케일되지 않습니다. 밝기가 느리게 증가한 다음 듀티 사이클에 따라 기하 급수적으로 증가합니다.

누구나 교정 요소 또는 다른 해결 방법으로 사용할 규칙을 제안 할 수 있습니까?


Knight Rider 커프스 링크를 만들 때 x ^ 10을 사용하여 페이드 오프를 멋지게 보이게해야했습니다!
Rocketmagnet

3
"초기 밝기가 기하 급수적으로 증가한 다음 느리게 증가"하지 않습니까?
Dmitry Grigoryev

1
우리의 눈은 밝기에 대수적으로 반응한다고 생각합니다.
DKNguyen

답변:


13

16 레벨의 경우 "손으로 직접"간단한 룩업 테이블을 수행하고 8 비트 값의 4 비트 값을 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 위치 왼쪽으로 이동합니다. 2n1
stevenvh

17

이론적으로는 기하 급수적이어야하지만 2 차 함수를 사용하여 페이딩에 대한 최상의 결과를 얻었습니다.

나는 또한 당신이 그것을 뒤로했다고 생각합니다. 낮은 듀티 사이클에서 감지 된 밝기 증가는 거의 전체 듀티 사이클에서보다 훨씬 커지며, 여기서 밝기 증가는 거의 이해할 수 없습니다.


감마 보정 도 참조하십시오 .
starblue

17

지난 며칠 동안 동일한 문제가 발생 하여이 주제를 살펴 보았습니다. 가상 선형 방식으로 PWM을 사용하여 LED를 어둡게하려고하지만 전체 256 단계 해상도가 필요합니다. 곡선을 수동으로 생성하기 위해 256 개의 숫자를 추측하는 것은 쉬운 일이 아닙니다!

나는 전문 수학자가 아니지만, 작동 방식을 실제로 몰라도 몇 가지 함수와 수식을 결합하여 기본 곡선을 생성 할만큼 충분히 알고 있습니다. 스프레드 시트 (Excel을 사용)를 사용하면 0에서 255까지의 숫자 집합으로 놀 수 있고 다음 셀에 몇 가지 수식을 넣고 그래프로 만들 수 있습니다.

그림 어셈블러를 사용하여 페이딩을 수행하므로 스프레드 시트에서 수식 ( ="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

이 방정식은 나를 위해 완벽하게 작동했습니다.
Ignacio Vazquez-Abrams


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의 하한 / 상한 범위를 버립니다.

아래는 내가 가진 것입니다.

// 안티 로그 테스트 프로그램

/ * PIN6 (PB1)에 연결된 LED * /
#define LED 1 

// 안티 로그 (역) 룩업 테이블 
// y = 0-255 (펌웨어 출력), 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;
    }
    그렇지 않으면 (av <x) 
    {
      y | = i;
    }
    그밖에
    {
      y를 반환;
    }
  }
  if (pgm_read_word_near (ANTI_LOG + y)> x)
  {
    y-= 1;
  }
  y를 반환;
}


무효 설정 ()
{
  pinMode (LED, 출력);
  digitalWrite (LED, 낮음);
}

# 정의 MIN_X 0
#define MAX_X 1024

무효 루프 ()
{
  int i;
  // antilog_drive
  for (i = MIN_X; i <MAX_X; i ++)
  {
    analogWrite (LED, 안티 로그 (i));
    지연 (2);
  }
  for (--i; i> = MIN_X; i--)
  {
    analogWrite (LED, 안티 로그 (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);
  }
  지연 (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()나면 다른 기능 (예 :)을 조사 할 것입니다 .


당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.