레이블이 손에 잡히는 IoT인 게시물을 표시합니다. 모든 게시물 표시
레이블이 손에 잡히는 IoT인 게시물을 표시합니다. 모든 게시물 표시

2016년 2월 2일 화요일

아두이노에서 FreeRTOS 사용하기 - 4. Mutual Exclusion (상호배제)


이번 주제인 mutual exclusion(상호 배제)는 여러 태스크가 같은 자원에 동시에 억세스 하지 못하도록 보장해 주는 메커니즘이다. 

두개의 태스크가 동시에 같은 자원(여기서는 가상의 프린터 )를 사용하는 경우 태스크가 실행되는 도중에 RTOS에 의해 선점되면 출력 결과가 아래와 같은 식으로 되어 버린다.

Printer 1: +++++++++++Printer 2: -----------------------------
++++++++++++++++++
Printer 1: Printer 2: -----------------------------
+++++Printer 2: -----------------------------
++++++++++++++++++++++++
Printer 2: -----------------------------
PrintePrinter 2: -----------------------------
r 1: ++++++++Printer 2: -----------------------------
+++++++++++++++++++++
Printer 1: Printer 2: -----------------------------
+++++++++++++++++++++++++++++
Printer 1: +++++++++++++++++++++++++++++
Printer 2: -----------------------------
Printer 1: +++++++++++++++++++++++++++++
Printer 2: -----------------------------
Printer 1: +++++++++++++++Printer 2: -----------------------------
++++++++++++++
Printer 1: ++++++Printer 2: -----------------------------
+++++++++++++++++++++++
Printer 2: -----------------------------

위의 결과가 나온 코드이다.

#include <FreeRTOS_AVR.h>

#define MS(x) ((unsigned long)(x)/portTICK_PERIOD_MS)

void vPrint(void *);

void setup()
{
  Serial.begin(115200);

  xTaskCreate(vPrint, "Printer1", 200, (void *)"Printer 1: +++++++++++++++++++++++++++++", 1, NULL);
  xTaskCreate(vPrint, "Printer2", 200, (void *)"Printer 2: -----------------------------", 2, NULL);

  vTaskStartScheduler();
  while (1) ;
}

void loop()
{
}

void vPrint(void *pvParameters)
{
  unsigned char led_state = 0;

  while (1) {
    printer((char *)pvParameters);
    vTaskDelay(MS(random(100)));
  }
}

void printer(char *p)
{
  while (*p != '\0') {
    Serial.print(*p++);
    delay(1);
  }
  Serial.println(" ");
}

하지만 원하는 것은 한 태스크가 프린터로 출력하는 동안 다른 태스크가 방해하지 못하게 하는 것이다.

Printer 1: +++++++++++++++++++++++++++++
Printer 1: +++++++++++++++++++++++++++++
Printer 2: -----------------------------
Printer 2: -----------------------------
Printer 1: +++++++++++++++++++++++++++++
Printer 2: -----------------------------
Printer 1: +++++++++++++++++++++++++++++
Printer 1: +++++++++++++++++++++++++++++
Printer 2: -----------------------------
Printer 1: +++++++++++++++++++++++++++++
Printer 2: -----------------------------
Printer 2: -----------------------------
Printer 1: +++++++++++++++++++++++++++++
Printer 2: -----------------------------
Printer 2: -----------------------------
Printer 1: +++++++++++++++++++++++++++++
Printer 2: -----------------------------
Printer 1: +++++++++++++++++++++++++++++
Printer 2: -----------------------------
Printer 1: +++++++++++++++++++++++++++++
Printer 1: +++++++++++++++++++++++++++++

이런식으로 출력되게 하기 위해 한 태스크가 프린터를 사용하는 동안 다른 태스크는 프린터에 접근하지 못하고 대기하게 하기 위해 MUTEX를 사용한다.

#include <FreeRTOS_AVR.h>

#define MS(x) ((unsigned long)(x)/portTICK_PERIOD_MS)

SemaphoreHandle_t xMutex;

void vPrint(void *);

void setup()
{
  Serial.begin(115200);

  xMutex = xSemaphoreCreateMutex();
  if (NULL != xMutex) {
    xTaskCreate(vPrint, "Printer1", 200, (void *)"Printer 1: +++++++++++++++++++++++++++++", 1, NULL);
    xTaskCreate(vPrint, "Printer2", 200, (void *)"Printer 2: -----------------------------", 2, NULL);

    vTaskStartScheduler();
  } else {
    Serial.println("Create mutex failed.");
  }
  while (1) ;
}

void loop()
{
}

void vPrint(void *pvParameters)
{
  unsigned char led_state = 0;

  while (1) {
    xSemaphoreTake( xMutex, portMAX_DELAY);       // block forever until the semaphore is given
    // Critical section start
    printer((char *)pvParameters);
    // Critical section end
    xSemaphoreGive( xMutex );
    vTaskDelay(MS(random(500)));
  }
}

void printer(char *p)
{
  while (*p != '\0') {
    Serial.print(*p++);
    delay(1);
  }
  Serial.println(" ");
}

위의 코드에서 빨간색으로 표시된 부분을 Critical Section(임계영역)이라고 한다. 여러 태스크가 공유하지만 동시에 억세스하면 안되는 자원을 억세스 하는건 critical section 내에서만 해야 한다. C.S.는 xSemaphoreTake()로 시작하고 xSemaphoreGive()로 끝난다. 한 태스크가 C.S. 내에 있는 코드를 실행하고 있는 동안 다른 태스크가 C.S. 안으로 들어가려고 하면 xSemaphoreTake()에서 블럭되어 현재 C.S. 내에서 실행중이 태스크가 빠져 나오면서 xSemaphoreGive()를 호출할 때 까지 sleep 상태로 대기하게 되므로 동시에 두개 이상의 태스크가 C.S. 내로 진입할 수 없게 된다.  
 
 
1. CS에 아무 태스크도 진입하지 않은 상태

 2.태스크1이 xSemaphoreTake() 를 호출해 키를 가지고 CS에 진입

 3. 태스크1이 CS 내부에서 공유자원인 프린터를 사용

 4. 태스크1이 아직 CS 내에 있는데 태스크2도 CS에 진입하기 위해 xSemaphoreTake()를 호출했으나 키가 없어 CS에 진입하지 못하고 sleep 상태로 키가 돌아오기를 대기


 5. 태스크1이 CS에서 빠져나오며 xSemaphoreGive()를 호출해 키를 돌려줌

 6. Sleep상태로 기다리던 태스크2가 키가 돌아왔으므로 깨어나 CS에 진입
 
 
위의 예제에서는 태스크간에 공유하는 자원으로 프린터를 예로 들었지만 그 이외에 서로 공유하는 전역변수들 역시 억세스 할 때는 C.S. 내에서 해야 한다. 
 
그리고 태스크가 C.S.  내에 들어 있는 동안 그 C.S.에 진입하려고 하는 태스크는 블럭되어 실행되지 못하므로 ISR과 마찬가지로 C.S. 내에서도 가능한 빨리 작업을 마치고 빠져 나와야 한다.

2016년 2월 1일 월요일

아두이노에서 FreeRTOS 사용하기 - 3. 태스크 동기화


멀티태스킹 프로그램을 만들다 보면 태스크 간 또는 태스크와 ISR 간에 동기화가 필요한 경우가 있다. 

이 예제에는 LED라는 이름의 태스크가 실행되는데, 이 태스크는 외부에 연결된 Push button이 눌린 이벤트가릴 발생할 때 마다 led의 상태를 토글한다. 또 하나의 태스크인 PBSW는 스위치의 상태를 감시하다 버튼이 눌리면 그 이벤트를 LED 태스크에 통보한다.

LED 태스크는 버튼이 눌린 이벤트가 발생할 때 까지 대기해야 하는데 이를 위해 세마포어를 사용하게 된다. 여기서는 binary semaphore를 사용한다.



세마포어는 다음 함수로 만들 수 있다.

vSemaphoreCreateBinary ( xSemaphoreHandle xSemaphore );

세마포어에 대한 동작은 ‘take’ 또는 ‘give’ 두가지 밖에 없다.

xSemaphoreTake ( vSemaphoreHandle xSemaphore, portTickType xBlockTime );
xSemaphoreGive ( vSemaphoreHandle xSemaphore );

위의 두 매크로 모두 정상적으로 실행되었으면 pdTrue를 리턴하고, 에러가 발생하면 pdFalse를 리턴한다. xBlockTime 파라미터는 semaphore가 available할 때 까지 대기 시간이다. Queue 함수에서 사용된 것과 동일한 역할을 한다.

만일 Queue 또는 semaphore등을 ISR에서 사용해야 하는 경우에는 특별한 함수나 매크로를 사용해야만 한다. 이 함수나 매크로들은 이름의 마지막이 FromISR 로 끝난다. 예) xSemaphoreTakeFromISR, xSemaphoreGiveFromISR 

#include <FreeRTOS_AVR.h>

#define MS(x) ((unsigned long)(x)/portTICK_PERIOD_MS)

#define LED_PIN 13
#define SW_PIN 3

SemaphoreHandle_t xSemaphore;

void vLED(void *);
void vPBSW(void *);

void setup()
{
  Serial.begin(115200);

  vSemaphoreCreateBinary( xSemaphore );

  xTaskCreate(vLED, "LED", 200, NULL, 1, NULL);
  xTaskCreate(vPBSW, "PBSW", 200, NULL, 1, NULL);

  vTaskStartScheduler();
  while (1) ;
}

void loop()
{
}

void vLED(void *pvParameters)
{
  unsigned char led_state = 0;

  Serial.print("Task ");
  Serial.print((char *)pvParameters);
  Serial.println(" started...");

  pinMode(LED_PIN, OUTPUT);          // init LED
  while (1) {
    xSemaphoreTake( xSemaphore, portMAX_DELAY);       // block forever until the semaphore is given
    led_state =! led_state;                          // toggle LED state
    digitalWrite(LED_PIN, led_state);         // apply LED state
  }
}

void vPBSW(void *pvParameters)
{
  unsigned char last_state = 1;
  unsigned char cur_state;

  Serial.print("Task ");
  Serial.print((char *)pvParameters);
  Serial.println(" started...");

  pinMode(SW_PIN, INPUT_PULLUP);  // init Pushbutton switch
  while (1) {
    cur_state = digitalRead(SW_PIN);
    if ((1==last_state) && (0==cur_state)) {    // button pressed
      xSemaphoreGive( xSemaphore );
      last_state = 0;
    } else if ((1==last_state) && (0==cur_state)) {    // button released
      last_state = 1;
    }
    vTaskDelay(10/portTICK_PERIOD_MS);
  }
}

위의 코드에서 vLED 태스크는 xSemaphoreTake() 함수에서 실행을 중단하고 vPBSW 태스크에서 xSemaphoreGive() 함수를 호출해 줄 때 까지는 잠자는 상태로 대기하게 된다. 물론 세마포어를 사용하지 않고 전역변수를 이용해서도 유사하게 동작하게 할 수 있지만 그 경우는 busy waiting이 되기 때문에 상당히 비효율적이다.

void LED()
{
  ...
  while (1) {
    if (1 == gFlag) {
    led_state =! led_state;                          // toggle LED state
    digitalWrite(LED_PIN, led_state);         // apply LED state
    gFlag = 0; 
  }

void PBSW()
{
  ...
  while (1) {    
    if ((1==last_state) && (0==cur_state)) {    // button pressed
      gFlag = 1;
      last_state = 0;
    } else if ((1==last_state) && (0==cur_state)) {    // button released
      last_state = 1;
    }
    ...
  }
}

이 경우 LED 태스크는 쉬지 않고 계속 gFlag 변수값이 1이 되는지를 확인해야 한다. 그에 비해 세마포어를 사용하면 스위치가 눌리지 않는 한 LED 테스크는 별도로 CPU 자원을 사용하지 않고 sleep 상태를 유지하므로 그 시간에 다른 작업을 더 할 수 있다.



2016년 1월 28일 목요일

아두이노에서 FreeRTOS 사용하기 - 1


RTOS를 사용하는 가장 큰 목적이 멀티태스킹이므로 어떻게 멀티태스크 환경을 설정하고, 태스크를 만들고, 선점 스케줄러를 시작시키는가를 보여주기 위해 두개의 독립적인 태스크가 동시에 실행되는 프로그램을 만들어 보겠다. 

(아두이노에 FreeRTOS를 설치하는 방법은 이전 포스트를 참고 - http://arsviator.blogspot.kr/2015/08/rtos-freertos-in-arduino.html?q=freertos)

이 예제에는 두개의 독립적인 태스크가 있다. 첫번째는 “LED”라는 이름으로 LED를 일정한 시간 간격으로 깜빡이게 하는 것이고, 두번째는 “Temperature”라는 이름으로 주기적으로 외부 온도 센서의 측정값을 읽어 시리얼 포트로 결과를 출력하는 것이다. 두 태스크는 같은 우선순위로 실행되고, 스케줄러는 다른 태스크가 실행될 수 있도록 실행중인 태스크를 선점한다. 

#include <FreeRTOS_AVR.h>

#define MS(x) (((unsigned long)(x)*configTICK_RATE_HZ)/1000L)

static void Led(void* arg);
static void Light(void* arg);

static void Led(void* arg) {
  boolean ledState = false;

  pinMode(13, OUTPUT); 
  while (1) {
    digitalWrite(13, ledState);
    ledState = !ledState;
    vTaskDelay(MS(500));
  }
}

static void Light(void* arg) {  
  short l = 0;
  TickType_t xLastWakeTime = xTaskGetTickCount();
  const TickType_t xWakePeriod = 2000/portTICK_PERIOD_MS;    // 2 sec

  while (1) {
    l = analogRead(0);
    Serial.print("Light: ");
    Serial.println(l);
    vTaskDelayUntil(&xLastWakeTime, xWakePeriod);
  }
}

void setup() {
  portBASE_TYPE s1, s2;

  Serial.begin(115200);

  s1 = xTaskCreate(Led, NULL, 200, NULL, 1, NULL);
  s2 = xTaskCreate(Light, NULL, 200, NULL, 1, NULL);

  vTaskStartScheduler();     // start scheduler
  while(1);
}

void loop() {
}


LED 태스크는 먼저 LED가 연결된 (여기서는 아두이노에 기본으로 연결되어 있는 13번) 핀을 출력모드로 설정한 후 일정한 시간 간격으로 LED를 깜빡이게 한다. 

Light 태스크 역시 일정한 시간 간격으로 A0 핀에 연결된 CDS값을 AD변환하여 조도를 측정해 결과 값을 시리얼 포트로 출력해 준다.

태스크는 무한 루프를 포함한 함수로 구성되고, 태스크 함수의 프로토타입은 다음과 같다.

void TaskFunction (void *pvParameters);

위의 코드를 보면 두개의 태스크에서 시간을 delay하기 위해 서로 다른 함수를 사용한 것을 알 수 있다. LED task에서는 vTaskDelay() 함수를 사용한데 비해 Light task에서는 vTaskDelayUntil() 함수를 사용하고 있다.

void vTaskDelay ( portTickType xTicksToDelay );
void vTaskDelayUntil ( portTickType *pxPreviousWakeTime, portTickType xTimeIncrement );

두 함수 모두 스케줄러에 일정 시간동안 delay를 요청하는건 동일하나 vTaskDelay()는 함수가 호출된 시점부터 일정한 tick 동안 태스크 실행을 블럭하는데 비해 vTaskDelayUntil() 함수는 이전에 블럭에서 깨어난 시간인 pxPreviousWakeTime 의 값 부터 xTimeIncrement 만큼만 태스크의 실행을 블럭한다. vTaskDelayUntil() 함수를 사용하면 루프의 내용을 한번 실행하는데 얼마의 시간이 거리건 상관 없이 일정한 시간 간격으로 루프를 실행할 수 있다. 또한 pxPreviousWakeTime 변수는 태스크가 블럭에서 깨어나는 순간 값이 자동으로 업데이트 된다.

만일 다음과 같은 두개의 태스크가 있는 경우를 생각해 보자. 여기서 foo() 함수는 호출되면 실행되는데 0.5초의 시간이 걸린다고 가정한다.

void task1(…)
{
  while (1) {
    foo();
    vTaskDelay(1000/portTICK_PERIOD_MS);
  }
}

void task2(…)
{
  portTICK_TYPE xLastWakeTime = xTaskGetTickCount();

  while (1) {
    foo();
    vTaskDelayUntil(&xLastWakeTime, 1000/portTICK_PERIOD_MS);
  }
}

두 코드는 비슷해 보이지만 task1의 경우 0초에 foo() 함수를 호출하고 0.5초에 vTaskDelay() 함수를 호출해서 1초를 블럭한 뒤 다시 1.5초에 foo()를 호출하게 된다. 즉 foo() 함수는 1.5초에 한번씩 호출되는 것이다. 그에 비해 task2의 경우 foo() 함수의 실행시간이 얼마가 걸리건 상관 없이 foo() 함수는 정확히 1초에 한번씩 호출되게 된다. (물론 foo() 함수의 실행시간은 delay 시간 (여기서는 1초) 보다는 짧아야 한다) 그림으로 보면 좀 더 이해하기 쉬울 것이다. 


태스크를 생성했으면 스케줄러를 실행시켜 주면 이제부터 생성된 태스크들이 동시에 실행을 시작하게 된다. 스케줄러가 시작되면 모든 통제권을 RTOS가 가지게 되므로 vTaskStartScheduler()를 호출한 뒤에는 무한루프를 넣어주고, loop() 함수는 사용하면 안된다.

또하나 주의할 것은 태스크를 생성할 때 태스크가 사용할 스택의 크기를 지정해줘야 하는데 이 스택 크기를 너무 작게 잡으면 프로그램의 동작이 완전 멈춰버릴 수 있으므로 충분한 공간을 확보해 줘야만 한다.FreeRTOS 예제 코드에 보면 태스크 생성시 xTaskCreate(Thread1, NULL, configMINIMAL_STACK_SIZE, NULL, 2, NULL); 같이 스택 크기를 configMINIMAL_STACK_SIZE로 지정해 주는데 이 경우 태스크의 코드가 조금만 커져도 (로컬 변수를 몇개 추가하거나 다른 함수를 중복호출 하거나 등) 프로그램 실행이 완전히 멈춰버린다. 아두이노 우노같은 경우 SRAM 공간이 충분하지 않으므로 (SRAM 2KB) 너무 넉넉하게 줄 수는 없어도 최소한 필요한 공간보다는 크게 공간을 제공해 줘야만 한다.

2015년 6월 7일 일요일

Arduino IDE에서 ESP8266를 사용할 수 있도록 설정하기

아두이노가 1.6.4로 업데이트 되면서 다른 종류 프로세서 추가 방법이 바뀌었다. 아두이노에서 ESP8266을 프로그래밍 할 수 있는 방법을 설명한다.

먼저 아두이노를 실행한 후 메뉴에서 'Preferences...' 를 선택한다.


Preferences 윈도우 아래쪽의 'Additional Boards Manager URLs' 필드에 'http://arduino.esp8266.com/pacakge_esp8266com_index.json' 을 넣어준다.


메뉴에서 Tools -> Board를 선택하면 서브메뉴가 열리는데 가장 위쪽에 있는 'Boards Manager...'를 선택한다.


Boards Manager에서 esp8266 항목을 클릭하면 오른쪽 아래 부분에 Install 버튼이 나온다. 'Install' 버튼을 눌러 패키지를 설치해주면 된다.


패키지가 설치되면 Tools -> Board의 서브메뉴인 보드 목록에 ESP8266 Modules가 보일 것이다. 원하는 보드를 선택해 사용하면 된다.









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에 반영시키는 것이다. 






2015년 4월 10일 금요일

ESP8266 모듈/보드들 (Various kinds of ESP8266 module/board)

ESP8266을 사용해 보려면 여러 종류의 모듈들이 나와 있다.

가장 먼저 나온게 ESP-01(보통 ESP8266 모듈이라고 하면 이걸 말함)인데 시리얼 핀 이외에 GPIO핀이 연결된게 몇개 없어 Wifi-to-Serial 모듈 이외 용도로 사용하기는 좀 불편(?)하다. 

그래서 그 후 GPIO핀이 패드로 많이 나와 있는 모듈들이 나왔다. ESP-12/07같은 모듈이다.





핀헤더 소켓을 땜질할 수 있는 스루홀까지 가지고 있는 센스 덕에 사용하기 편리하다. 

하지만 핀 피치가 일반적으로 만능기판이나 빵판에 사용되는 2.54mm가 아니라 헤더소켓을 뗌질해도 바로 꼽을 수가 없는 단점이 있다. 그래서 이런 컨버젼 기판도 판매되고 있다.




기판 뒤쪽에 땜질은 되어 있지 않지만 3.3V 레귤레이터를 붙일 수 있는 패드까지 나와 있어 레귤레이터를 붙이면 5V에도 바로 사용할 수 있게 되어 있다.

Aliexpress에서 구매하면 ESP-12 모듈이 개당 $2.6~3 정도, 컨버젼 기판은 개당 $1 이하로 구매 가능하다. 

그리고 땜질도 귀찮고 어짜피 실험하려면 빵판에 꼽은 다음 LED라도 연결해야 하는데 그것도 싫으면 이런 테스트 모듈도 판매되고 있다. (대략 $5~6정도)





각 GPIO핀에 LED가 연결되어 있고 3핀에는 PWM 테스트용으로 컬러LED도 연결되어 있다. 그리고 ADC 테스트용 CDS(조도센서)도 붙어있고, 배터리 소켓도 같이 들어있어 AA배터리 3개를 넣어주면 저 자체로 독립적인 센서 모듈로 사용할수도 있다.

ESP8266에 대한 소개는 http://arsviator.blogspot.kr/2015/01/iot-esp8266.html, 테스트 보드에 대한 상세한 사항은 http://arsviator.blogspot.kr/2015/03/esp8266-test-board.html 를 참고하면 된다.