2016년 1월 29일 금요일

USB Battery Charging (BC) Spec. - 고속 충전기의 비밀

USB 아답터를 보면 고속 충전용 아답터라고 나오는 것들이 있다. 일반 아답터와 동일하게 생겼지만 PC나 일반 아답터에 연결했을 때 보다 더 높은 전압으로 고속으로 디바이스 충전이 가능하다.


하지만 문제는 전류라는 것은 아답터가 아무리 높은 전류를 흘려줄 수 있다고 해도 디바이스가 요구하는 만큼의 전류만 흘려준다는 것이다. 즉 아답터가 5V, 2A 용량이라고 해도 디바이스가 500mA만 쓰면 500mA의 전류만 흐르게 된다.

그럼 디바이스는 어떻게 일반/고속 충전용 아답터를 구분해서 고속일 때 더 많은 전류를 요구해 고속으로 충전이 가능할까?

해답은 USB Battery Charging Spec.에 있다. USB 포트를 원래 목적인 데이터 교환 뿐 아니고 데이터 전송은 없이 충전 목적으로 사용이 늘어남에 따라 2007년에 BC 1.0 스펙이 만들어 졌다.



여기서는 전원 소스 타입을 3가지로 구분하고 있다.

- SDP (Standard Downstream Port)
- CDP (Charging Downstream Port)
- DCP (Dedicated Charging Port)

SDP는 일반적으로 PC나 노트북에 있는 USB 포트이다. 데이터 전송이 기본 목적이고 일반적으로 100mA, 최대로 설정 시 500mA까지 전류를 공급해 줄 수 있다.

CDP는 최신 PC등에 보면 일부 USB 포트가 고출력이라고 표시되는 경우가 있는데 이 포트를 말한다. SDP와 마찬가지로 데이터 전송이 가능하지만 고속으로 장치 충전이 가능하도록 최대 900mA(High speed 인 경우) 또는 1.5A(Low/Fast speed인 경우)까지 전류를 흘릴 수 있다.

DCP는 데이터 전송은 하지 않고 충전 전용으로 사용되는 USB 포트이다. USB 아답터의 포트가 DCP이다. 이 경우 일체의 데이터 전송이 없기 때문에 디바이스 enumeration 과정이 없으므로 D+와 D-의 short 여부로 확인한다.


BC 1.1 스펙에 의하면  RDCHG_DAT의 최대 값은 200오옴이다.


즉 고속 충전 아답터의 경우 USB 단자의 D+핀과 D-핀을 short시켜 디바이스(스마트폰 등)에게 이 포트가 DCP 포트이므로 많은 전류를 흘려줄 수 있다는걸 알려주기 때문에 고속 충전이 가능한 것이다.

BC 1.1에 관심이 있으면 링크에 있는 스펙을 참조하면 된다.


https://drive.google.com/open?id=0B6OG5Mxa-ufZX2NRUXlmNzV2Wjg

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


이번에는 태스크 간 통신에 관한 내용이다.

두개의 태스크가 동시에 실행되면서 서로간에 데이터를 주고 받는 예제 코드를 만들어 보겠다. 태스크 간 통신을 위해서 Queue를 사용한다.
이전 예제 코드에서 Light 태스크는 측정한 조도 값을 시리얼 포트를 통해 출력했는데, 이번에는 시리얼 포트로 출력하는 대신 측정 값을 Print라는 이름의 태스크로 보낸다. Print 태스크는 데이터가 들어오기를 기다렸다가 들어온 내용을 시리얼 포트로 출력한다.

Print 태스크는 UART를 초기화 한 후 큐에 데이터가 들어오기를 기다리는 무한루프를 돌면서 데이터가 들어오면 그 내용을 읽어 출력한다. Light 태스크는 이전 예제와 같이 무한 루프를 돌면서 조도 값을 측정하여 그 값을 큐에 집어 넣는다.


태스크 간 통신은 queue를 사용한다. Queue는 xQueueCreate() 함수로 만들어 진다.

xQueueHandle xQueueCreate ( unsigned portBASE_TYPE uxQueueLength, unsigned portBASE_TYPE uxItemSize );

이 함수는 큐에 필요한 메모리 공간(uxQueueLength * uxItemSize)을 할당하고 그에 대한 queue handle(일종의 포인터)를 리턴 해 준다.

큐에 데이터를 집어 넣기 위해서는 xQueueSendToBack() 함수를 사용하고, 큐에서 데이터를 읽어 오기 위해서는 xQueueReceive() 함수를 사용한다. 

portBASE_TYPE xQueueSendToBack ( xQueueHandle xQueue, const void *pvItemToQueue, portTickType xTicksToWait );
portBASE_TYPE xQueueReceive ( xQueueHandle xQueue, void *pvBuffer, portTickType xTicksToWait );

두 함수 모두 첫번째 파라미터는 queue handle(xQueueCreate에서 리턴된 값)이고, 두번째 파라미터는 집어 넣을 아이템(데이터)에 대한 포인터 또는 읽어 올 아이템을 넣을 버퍼의 포인터이다. 두 함수 모두 데이터를 복사하는 동작을 수행한다. 세번째 파라미터는 동작이 완료될 때 까지 기다리는 시간이다. ‘0’으로 설정하면 함수는 아이템을 큐에 쓰거나 큐에서 읽어 오는 동작이 완료되었는가 여부에 관계 없이 바로 리턴한다. 만일 이 값을 portMAX_DELAY로 지정하면 함수는 동작이 수행될 때 까지 계속 블럭된다. 그 이외 값을 넣으면 동작이 수행되지 못하는 경우 지정된 시간만큼 함수가 블럭된다. 

아래 예제에서는 Light 태스크는 큐가 풀인 경우 기다리지 않지만, Print 태스크는 큐가 비어 있는 경우 데이터가 들어올 때 까지 블럭상태로 기다린다.

#include <FreeRTOS_AVR.h>

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

QueueHandle_t xQueue;

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


static void Print(void* arg) {
  uint16_t light;
  uint16_t sampleCount = 0;

  while (1) {
    if (xQueueReceive(xQueue, &light, portMAX_DELAY)) {
      sampleCount++;
      Serial.print("Sample ");       Serial.print(sampleCount);
      Serial.print(" Light = "); Serial.println(light);
    }
  }
}

static void Light(void* arg) {  
  uint16_t l = 0;
  TickType_t xLastWakeTime;  

  xLastWakeTime = xTaskGetTickCount();  

  while (1) {
    l = analogRead(0);    
    xQueueSendToBack(xQueue, &l, 0);
    vTaskDelayUntil( &xLastWakeTime, ( 2000 / portTICK_PERIOD_MS ) );  
  }
}

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

  xQueue = xQueueCreate(5, sizeof(uint16_t));

  xTaskCreate(Print, NULL, 200, NULL, 1, NULL);
  xTaskCreate(Light, NULL, 200, NULL, 2, NULL);

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

void loop() {
}


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) 너무 넉넉하게 줄 수는 없어도 최소한 필요한 공간보다는 크게 공간을 제공해 줘야만 한다.

2016년 1월 27일 수요일

Arduino Uno에서 digitalWrite()와 직접 레지스터 제어의 속도 차이

먼저 아두이노 우노에서 가장 기본 예제인 blink를 실행시켜 출력파형을 오실로 스코프로 확인해 보면 다음과 같다.

void setup()
{
  pinMode(10, OUTPUT);
}

void loop()
{
  digitalWrite(10, HIGH);
  digitalWrite(10, LOW);
}



대략 digitalWrite()를 한번 실행하는데 6.8us정도의 시간이 걸린다.

위의 코드를 동일한 기능을 하지만 digitalWrite() 함수를 사용하지 않고 ATmega328의 레지스터를 직접 제어하도록 하면 코드는 다음과 같다.

void setup() { 
  DDRB |= (1<<2);}

void loop() {
  PORTB |= (1<<2);
  PORTB &= ~(1<<2);
}

위의 코드를 실행한 결과를 오실로 스코프로 확인한 결과는 다음과 같다.




이 경우 약 120ns정도의 시간이 걸리게 된다. (약 57배 speed up)
단 위의 파형에서 출력이 HIGH인 구간과 LOW인 구간이 비대칭인 이유는 loop함수를 빠져나갔다가 다시 호출되는데 필요한 오버헤드로 인해 LOW->HIGH구간에 포트 조작 이외의 작업이 추가되었기 때문이다.

void setup() { 
  DDRB |= (1<<2);}

void loop() {
  while (1) {
    PORTB |= (1<<2);
    PORTB &= ~(1<<2);
  }
}

코드를 위와 같이 수정하여 loop() 함수 exit/enter에 따른 오버헤드를 없애 준 결과는 다음과 같다.


아직도 아주 약간 비대칭이기는 해도 거의 차이가 없어진 걸 확인할 수 있다.

참고삼아 아래 코드를 실행해 측정해 본 digitalRead() 함수의 실행시간은 다음과 같다.

void setup() {
  DDRB |= (1<<2);
  pinMode(11,INPUT);
}

void loop()
{
  PORTB |= (1<<2); 
  boolean button = digitalRead(11);
  PORTB &= ~(1<<2);
}

digitalRead() 함수를 호출하는 경우 대략 5.2us 정도의 시간이 소요된다.