레이블이 아두이노 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년 8월 20일 목요일

Blynk를 사용해 아두이노에서 IoT 맛보기

Blynk (http://www.blynk.cc)를 사용하면 아두이노를 스마트폰이나 PC와 연결하여 매우 쉽게 제어가 가능해진다. 물론 아두이노 뿐 아니고 ESP8266이나 Raspberry Pi등에서도 사용이 가능하다. 현재 사용할 수 있는 하드웨어 목록은 http://community.blynk.cc/t/hardware-supported-by-blynk/16 에서 확인할 수 있다.


여기서는 아두이노 우노에 CC3000 Wifi shield를 사용해 보도록 한다.



먼저 CC3000 Wifi shield를 위해 Adafruit_CC3000 Library를 설치해준다. https://github.com/adafruit/Adafruit_CC3000_Library 에서 다운받을 수 있다. (라이브러리 설치 방법은 이전 포스트 (http://arsviator.blogspot.kr/2015/07/install-library-in-arduino.html)

다음은 Blynk 라이브러리를 설치해 줘야 한다. https://github.com/blynkkk/blynk-library 에서 다운받으면 된다.

정상적으로 설치를 완료했으면 아두이노를 재시동하고 예제 -> Blynk -> BoardsAndShields 에서 CC3000을 선택한다.


코드 구조는 매우 간단하다. 단 코드중에 아래 표시한 부분은 각자 자신에 맞게 변경해 줘야만 한다.


일단 여기까지 진행하였으면 아두이노는 일단 잠시 놔두고 스마트폰을 설정해 준다.



Blynk 홈페이지에 보면 iOS와 안드로이드용 앱을 다운받을 수 있는 링크가 있다. 자신의 폰에 맞는걸 선택해 자신의 폰에 Blynk 앱을 설치해 준다.


앱을 실행해 서비스에 가입해주면 된다. 가입 후 로그인 되면 다음과 같은 초기화면을 볼 수 있을 것이다.


Create New Project를 눌러 새 프로젝트를 생성한다.


프로젝트 이름(여기서는 FirstTest)을 써주고, 사용할 보드(여기서는 아두이노 우노)를 선택한다. 그 아래쪽의 Auth Token이 아두이노 스케치 코드에 넣어줘야 하는 값이다. E-mail 버튼을 누르면 가입시 사용한 메일주소로 이 토큰값이 메일로 보내지기 때문에 메일에서 복사해 붙이면 된다. 이제 Create Project 버튼을 눌러주면 프로젝트가 만들어진다.


이제 화면을 디자인해야 하므로 화면 검은색 부분중에 원하는 부분을 터치한다.


그럼 위와 같이 설치할 수 있는 위젯 목록이 나타난다. 여기서는 단순히 아두이노에 있는 LED on/off를 제어하기 위해 Button을 선택한다.


원하는 위치에 버튼이 추가되었다. 이제 버튼의 설정을 변경해 주기 위해 버튼을 터치한다.


버튼 설정 화면이다. 여기서 버튼의 이름(여기서는 LED)을 지정하고, 이 버튼이 아두이노의 어떤 핀과 연동될 것인지 알려준다. (아두이노 보드에 붙어있는 LED는 D13에 연결되어 있으므로 Digital에 D13을 선택)


설정이 끝나면 스위치의 이름과 연동된 핀 번호가 스위치에 표시된다. 디자인이 다 끝나고 실제 동작을 시키려면 맨 오른쪽 위의 삼각형 버튼을 선택해주면 된다.


아두이노 코드에 Auth Token 값을 넣어주고, setup() 함수에 pinMode(13, OUTPUT); 을 추가한 다음 아두이노에 업로드 하면 된다.

업로드 후 시리얼 모니터를 열어봤을 때 다음과 같이 출력되면 된다.


이제 스마트폰의 버튼을 눌러보면 그에 따라 아두이노의 LED가 on/off 되는걸 볼 수 있을 것이다.

Blynk에는 단순히 스위치 뿐 아니고 슬라이드, 조이스틱, 그래프등의 다양한 위젯이 있어 스마트폰에서 아두이노에 연결된 액츄에이터를 제어하거나, 반대로 아두이노에 연결되어 있는 센서들의 상태를 다양한 방법으로 확인이 가능하다. 또한 Push notification이나 Twitter 위젯도 있어 아두이노가 특정 상태가 되면 폰으로 push 메시지를 보내거나 트위터에 메시지를 트윗하는것도 가능하다. 다음 포스트에서 blynk를 좀 더 다양하게 사용하는걸 다뤄보도록 하겠다.