레이블이 NeoPixel인 게시물을 표시합니다. 모든 게시물 표시
레이블이 NeoPixel인 게시물을 표시합니다. 모든 게시물 표시

2015년 4월 15일 수요일

WS2812 color LED 사용하기

칼라 LED는 LED 내부에 빛의 3원색에 해당하는 Red, Green, Blue LED가 들어있어 각각의 밝기를 조절하면 원하는 색을 만들어 낼 수 있다. 일반적인 형태의 칼라 LED는 아래 사진과 같은 형태로 4개의 다리가 나와 있다. Common Anode 타입의 LED인 경우 가장 긴 다리가 common anode로 이 다리는 +에 연결되어야 하고 나머지 3개의 다리는 프로세서의 I/O에 연결해 LED를 제어한다. 각 IO핀이 LOW면 해당 LED가 켜지고, HIGH면 LED가 꺼지게 된다. (PWM의 경우 duty가 0%면 가장 밝게 켜지고 100%면 꺼짐)



반대로 common cathode 타입의 LED인 경우, common cathode는 GND에 연결하고 나머지 3개 다리는 프로세서의 I/O에 연결한다. 이 경우 common anode와 반대로 IO핀이 HIGH이면 해당 LED가 켜지고, LOW면 LED가 꺼지게 된다. (PWM의 경우 duty가 100%면 가장 밝게 켜지고 0%면 꺼짐)

아래 그림은 common cathode타입의 컬러 LED를 사용할 때의 연결이다. 



보통 위와 같이 전류제한 용으로 저항이 필요하다. 

이제 LED가 연결되면 컬러 LED를 제어하는 2가지 방법이 있다. 첫번째는 GPIO를 이용하는 것으로, 이 경우 각각 IO핀은 2개의 상태, LOW(0V)/HIGH(Vcc),를 가질 수 있으므로 총 8개의 다른 색(검은색 포함해서)을 낼 수 있다.



다른 방법으로는 각 색깔별 LED의 밝기를 조절하는 것이다. PWM을 사용해 각각의 밝기를 조절하면 매우 다양한 색을 만들어 낼 수가 있게 된다.
아두이노의 경우 6개의 8-bit PWM 채널을 가지고 있기 때문에, 각 PWM은 0~255 사이의 값을 가질 수 있다. 그러므로 칼라 LED에 3개의 PWM 채널이 연결되기 때문에 총 256*256*256=16,777,216가지의 조합이 만들어 질 수 있다. 



이 방법은 원하는 색을 만들어 낼 수 있지만, 단점은 칼라 LED 하나당 3개씩의 PWM 채널이 필요하다는 것이다. 아두이노 우노의 경우 6개, 메가의 경우 14개의 PWM을 가지고 있기 때문에 각각 2개, 4개의 칼라 LED밖에 연결할 수 없다. 물론 쉬프트 레지스터나 멀티플렉서등을 사용해서 더 많은 칼라 LED를 연결하는 방법이 있긴 해도 금새 매우 복잡해진다.

WS2812-based LED

Worldsemi라는 회사에서 이런 문제점을 완전히 해결해주는 새로운 칩을 만들었다. 처음에 만든것은 WS2811이라는 SMD IC로 내부에 시리얼 통신, 3개의 8-bit PWM 채널, 전류제한회로등을 가지고 있다.
다음으로 만든것은 WS2812로 5mm*5mm 정사각형 패키지 안에 WS2811에 추가로 고휘도 RGB LED를 다 집어 넣어 버렸다. 



이 칩의 가장 좋은 점은 단지 4개의 핀(GND, Power(5V), Data In, Data Out)만 있으면 된다는 것이다. 즉 이 칩은 서로 daisy-chain으로 여러개를 연결해 줄 수 있다. 프로세서가 체인의 첫번째 칩의 Data-In을 구동하고, 첫번째 칩의 Data-Out이 두번째 칩의 Data-In을 다시 구동하는 식이다. 다음 그림을 보면 좀 더 이해하기 쉬울 것이다.



이런식으로 체인으로 연결하는데 특별히 칩 갯수 제한은 없다. 
프로세서는 24-bit 값을 연속으로 보내게 되는데, 각 24-bit 값은 3개의 8-bit RGB 값을 나타낸다. 프로세서가 매번 24-bit 값을 보낼때마다 이 값은 체인의 첫번째 LED에 로드된다. 동시에 첫번째 LED는 자신이 가지고 있던 24-bit값을 두번째 LED로 전달한다. 두번째 LED는 세번째 LED로, 세번째 LED는 4번째 LED로 전달해 결국 값은 체인의 맨 마지막까지 전달되게 된다. 이 모든 작업이 매우 빠르게 진행되기 때문에 사람 눈에는 순식간에 일어난 것으로 보이게 된다. 
결과적으로 프로세서의 IO 핀 1개(PWM이 아닌 일반 GPIO핀)만으로 수백개의 칼라 LED를 제어할 수 있게 된다.
여러 회사들이 이 Worldsemi의 WS2812를 사용해 제품을 만들어 판매하고 있다. Adafruit의 경우 NeoPixels라는 이름의 제품군을 만들었다. 이 NeoPixels에는 다양한 형태의 패키지가 있다. 아래는 NeoPixels Ring이다.



위의 NeoPixel Ring은 16개의 WS2812를 가지고 있지만 12, 24, 60개짜리도 판매하고 있다. 

또한 aliexpress에 보면 아래와 같이 띠 형태의 WS2812 LED도 판매하고 있다. 아래 사진은 1m당 30개씩의 WS2812가 붙어있는 제품인데, 1m당 60개 또는 1m당 144개의 WS2812가 붙어있는 제품들도 구할 수 있다.




Feel the power!

보통 LED는 상대적으로 적은 전류를 소모한다고 생각하기 쉽지만, 여러개의 LED를 구동하려면 얼마나 많은 전류가 필요한지 알게되면 놀랄것이다. 각 NeoPixel은 최대 60mA(3개의 LED가 최대밝기일때, 즉 밝은 흰색인 경우)를 소모한다. 즉 프로세서의 전원을 컴퓨터의 USB포트에서 뽑아오거나 작은 아답터를 사용하는 경우 몇개의 WS2812를 구동할 수 있는가가 제한되게 된다. 만일 256개의 WS2812를 사용한다면 최대 15A를 전류를 사용할 수 있기 때문에 전원도 그에 맞게 준비해 줘야만 한다. 

여기서는 두가지 매우 중요한 포인트를 이야기하겠다. 첫번째는 NeoPixel에 전원을 공급하는 파워서플라이의 +와 GND 단자 사이에 1000uF의 전해콘덴서를 연결해 줘야 한다는 것이다. 두번째는 프로세서와 NeoPixel의 첫번째 data in 사이에 300~500오옴 저항을 직렬로 연결해 줘야 한다.  (보통 390오옴을 사용) 

스트립에 WS2812가 몇개 안되는 경우는 다음과 같이 USB에서 전원을 공급받아도 충분하다.


하지만 WS2812의 갯수가 많아지거나 (5~6개 이상), 아두이노 미니 3.3V등을 사용하는 경우는 아래와 같이 별도의 5V 아답터를 사용해 줘야 한다. 아답터의 용량은 충분한 전류를 흘려줄 수 있는 것을 사용해야 한다. (LED 개당 최대 60mA를 사용하므로 WS2812 갯수 * 0.06A 보다 조금 더 큰 용량을 사용한다. 즉 WS2812가 16개 붙어있다고 하면 16*0.06A = 0.96A 가 되므로 최소한 1A 이상의 아답터를 사용하는것이 좋다.)



Timing is everything

WS2812 기반의 LED를 사용하려면 가장 쉬운 방법은 믿을만한 라이브러리를 사용하고 그 중 다른 사람에 의해 테스트 된 함수를 사용하는 것이다. Adafruit NeoPixel 라이브러리를 권장한다.

중요한점은 이 라이브러리는 아두이노 우노와 메가에서 사용될 수 있도록 하드코딩 되었다는 것이다. 여기서 ‘하드코딩’의 믜미는 이 라이브러리 함수는 타이밍을 정확하게 맞추기 위해 어셈블리 코드를 사용하고 있다는 것이다. 그 결과 매우 사용하기 쉬운 라이브러리가 만들어졌지만, 아두이노 패밀리의 다른 보드에 바로 사용할 수 없을수도 있다. 또한 이 라이브러리 함수는 한가지 목적을 가지고 만들어졌기 때문에 NeoPixel 스트링에 새 값을 보내기 위한 함수를 호출하면 가장 먼저 모든 인터럽트를 비활성화 시킨다. 인터럽트를 사용하지 않는 경우는 문제가 없지만, 코드에서 인터럽트를 많이 활용한다면 큰 문제가 될 수도 있다.

Example programs using the Adafruit Library

Ex1) Lighting the pixels one after the other

#include <Adafruit_NeoPixel.h>

#define pinPix 12 // WS2812에 연결하는데 사용하는 pin 번호
#define numPix 16 // 링에 연결되어 있는 WS2812 LED 갯수

// Parameter 1 = 링에 연결되어 있는 WS2812 LED 갯수
// Parameter 2 = WS2812에 연결하는데 사용하는 pin 번호
// Parameter 3 = pixel type flags, add together as needed:
// NEO_KHZ800 800 KHz bitstream (most NeoPixel products w/WS2812 LEDs)
// NEO_KHZ400 400 KHz (classic 'v1' (not v2) FLORA pixels, WS2811 drivers)
// NEO_GRB Pixels are wired for GRB bitstream (most NeoPixel products)
// NEO_RGB Pixels are wired for RGB bitstream (v1 FLORA pixels, not v2)

Adafruit_NeoPixel myLeds = Adafruit_NeoPixel(numPix, pinPix, NEO_GRB + NEO_KHZ800);

void setup() {
  myLeds.begin(); // Initialize the NeoPixel array in the Arduino's memory,
  myLeds.show(); // turn all pixels off, and upload to ring or string
}

void loop() {
  int pause = 100;

  for (int i=0; i<numPix; i++) { 
    myLeds.setPixelColor(i,255,255,255);
    myLeds.show();
    delay(pause);
  }

  for (int i=0; i<numPix; i++) {
    myLeds.setPixelColor(i,0,0,0);
    myLeds.show();
    delay(pause);
  }
}

코드의 맨 처음은 Adafruit NeoPixel 라이브러리를 include 하는걸로 시작한다.

#include <Adafruit_NeoPixel.h>

다음은 WS2812에 데이터를 보내기 위해 사용하는 핀 번호와, 링이나 스트립에 몇개의 WS2812 LED가 붙어있는가를 정의한다. 여기서는 각각 12번 핀과 16개의 LED를 사용한다.

#define pinPix 12
#define numPix 16

정의가 끝나면 WS2812 LED 오브젝트를 인스턴스화 해 줘야 한다. 여기서는 인스턴스의 이름을 myLeds로 한다.

Adafruit_NeoPixel myLeds = Adafruit_NeoPixel(numPix, pinPix, NEO_GRB + NEO_KHZ800);

첫번째 파라미터(numPix)는 링/스트립에 붙어있는 WS2812 LED 갯수이다. 두번째 파라미터(pinPix)는 WS2812의 Data In에 연결된 핀 번호이다. 일단 여기서 세번째 파라미터는 신경쓰지 말고 넘어간다.

setup() 함수에서 가장 먼저 begin() 함수를 호출해 아두이노에 스트링을 위한 메모리를 초기화 한다. 그리고 show()를 호출해 스트링을 초기화 해 준다. 

void setup() {
  myLeds.begin();
  myLeds.show();
}

이제 잠시 쉬면서 지금까지의 내용을 정리해 보자. 인스턴스화와 초기화 단계의 일부분으로 Adafruit 라이브러리는 아두이노 메모리에 배열을 만든다. 이 배열은 링/스트링의 WS2812 LED 갯수와 같은 크기가 된다. 아래 그림은 위의 예제에서 16개를 사용한 경우의 배열을 도식화 한 것이다. 



각 24-bit 항목은 3개의 8-bit 서브필드로 구성되어 있고, 각 서브필드가 항목의 R, G, B 값을 나타낸다. Adafruit 라이브러리는 4개의 파라미터를 받아들이는 setPixelColor()라는 함수를 가지고 있다. 첫번째 파라미터는 값을 변경하길 원하는 WS2812 LED의 인덱스(여기서는 0~15 사이의 값)이고 나머지 3개의 파라미터는 원하는 R,G,B 값이다. 또한 중요한 것은 setPixelColor() 함수는 단지 아두이노 메모리에 있는 배열의 값만을 바꿀 뿐이라는 것이다. 값을 변경한 후 show() 함수를 호출해 배열에 들어있는 값들을 실제 WS2812 링/스트립에 전달해 주지 않으면 LED의 색은 바뀌지 않는다.

void loop() {
  int pause = 100;

  for (int i=0;i<numPix;i++) {
    myLeds.setPixelColor(i, 255,255,255);
    myLeds.show();
    delay(pause);
  }

  for (int i=0;i<numPix;i++) {
    myLeds.setPixelColor(i, 0,0,0);
    myLeds.show();
    delay(pause);
  }
}

첫번째 for 루프에서는 각 LED를 100ms 간격으로 하나씩 흰색(255,255,255)으로 켜 준다. setPixelColor로 색을 변경한 후에 show()를 호출하는걸 잊으면 안된다. 
두번째 for 루프에서는 각 LED를 100ms 간격으로 하나씩 검은색(0,0,0)으로 바꿔준다.

Ex2) Lighting all the pixels simultaneosly

#include <Adafruit_NeoPixel.h> // Library for NeoPixels

#define pinPix 12 // Pin driving NeoPixel Ring or String
#define numPix 16 // Number of NeoPixels in the Ring or Strip

Adafruit_NeoPixel myLeds = Adafruit_NeoPixel(numPix, pinPix, NEO_GRB + NEO_KHZ800);

void setup() {
  myLeds.begin(); // Initialize the NeoPixel array in the Arduino's memory,  
  myLeds.show(); // turn all pixels off, and upload to ring or string
}

void loop() {
  for (int i=0; i<numPix; i++) {
    myLeds.setPixelColor(i,255,255,255);
  }
  myLeds.show();
  delay(pause);

  for (int i=0; i<numPix; i++) {
    myLeds.setPixelColor(i,0,0,0);
  }
  myLeds.show();
  delay(pause);
}

1번 예제와 거의 유사하지만 이번에는 show()와 delay() 함수를 for 루프 바깥으로 빼 냈다. 즉 for 루프에서 setPixelColor() 함수로 아두이노 메모리에 있는 모든 배열의 값을 변경한 다음에 show()를 호출해 변경된 값을 한꺼번에 LED에 반영시키는 것이다.