고급 예제

다양한 도구들을 가지고 마음껏 응용해보세요.

FreeRTOS라이브러리(멀티 스레드)

2016-03-22 10:28:58

개요

 

 

아두이노를 사용하다보면 머릿속에서 구현한 코드와 실질적으로 아두이노와 아두이노IDE에서 지원하는 부분과의 모순때문에 막히는 부분이 상당부분 있습니다.

그 중에서도 대표적인 것이 Delay()함수로 인한 코드 전체가 밀리는 현상인데요.

아두이노는 기본적으로 싱글스레드로 동작하기 때문에 하나의 코드에서 막히거나 멈추는 부분이 있으면 코드 전체가 영향을 받아 멈추거나 잠시 중단됩니다.

 

이 문제는 여러개의 LED를 동작시키거나 문자를 띄우면서 센싱 작업을 할 때, delay()가 필수적으로 들어가는 모듈과 주기적으로 동작을 요하는 모듈을 같이 사용할 때 등 여러 방면에서 충돌하게 됩니다.

물론 아두이노 자체에서 이러한 문제를 편법으로 해결하기 위한 방법은 BlinkWithoutDelay라는 예시로 제공하고 있습니다.

코코아팹에도 예제에 대한 튜토리얼을 제공합니다! (BlinkWithoutDelay사용하기 - http://kocoafab.cc/tutorial/view/505)

 

위의 방법은 사용하기 쉽고 잘만 이용한다면 Delay()의 많은 문제를 해결할 수 있습니다. 하지만 어디까지나 편법이기 때문에 근본적으로 이런 문제를 해결하려는 사람들에게는 정확한 해답이 되지 못합니다.

 

이번 글에서 쓰려는 FreeRTOS도 아두이노에서 공식으로 지원하는 방법이 아니기 때문에 정공법은 아닙니다.

하지만 스레드를 사용하는 방법은 기존의 프로그래밍의 방식과 유사하기 때문에 복잡한 프로그램 구현에 있어서는 이 방법이 효율적인 프로그램으로 나올 수 있습니다.

 

이번글에서는 FreeRTOS를 사용하여 아두이노를 멀티 스레드로 사용하는 법에 대해 알아보겠습니다.

 

 

 

필요한 부품 목록

 

 

NO 부품명 수량 상세설명
1 오렌지보드 1 아두이노UNO호환

 

부품명 오렌지보드
부품사진

 

 

FreeRTOS 설치하기

 

 

※ FreeRTOS라이브러리는 낮은 버전의 아두이노 IDE에서 컴파일 오류를 일으킬 수 있으니 만약 컴파일 오류가 일어난다면 아두이노IDE의 버전을 바꿔보세요.

 

먼저 아래의 주소로 들어가서 FreeRTOS라이브러리를 받습니다.

FreeRTOS라이브러리 받기 - https://github.com/greiman/FreeRTOS-Arduino

 

받은 파일을 압축을 풀면 libraries폴더안에 아래와 같이 3개의 폴더가 보입니다. 

(FreeRTOS_ARM의 경우에는 ARM cortex-M3를 사용하는 arduino DUE를 사용할 경우 사용하고 나머지 Atmel칩을 사용하는 AVR 보드는 FreeRTOS_AVR을 사용합니다.)

이 3개의 폴더를 아두이노의 libraries폴더로 옮깁니다.

 

 

 

 

위와 같이 라이브러리 폴더를 옮겨놓고 아두이노를 재시작합니다.

파일 - 예제에 아래와 같이 두개의 예제폴더가 보인다면 FreeRTOS라이브러리 설치가 끝납니다.

 

 

 

 

스레드(Thread)와 세마포어(Semaphore)

 

코드를 이해하기 전에 위의 두개의 개념을 간단히 알고 가야 예제 코드를 이해하는데 도움이 됩니다.

고급 프로그래밍이 아니면 배울일도 없고 쓸일도 없기 때문에 사실 모르고 넘어가도 되지만 멀티쓰레드를 사용하기 위해서는 간단히 알아야 합니다.

 

스레드

 

사실 스레드(Thread) 영단어의 뜻은 실, 가닥, 줄기라는 뜻입니다.

 

 

 

 

프로그래밍에서 말하는 스레드는 영단어의 의미 중 (이야기 등의)가닥(문맥)에 어울립니다.

프로그래밍에서의 스레드는 프로세서를 수행하는 하나의 일의 흐름을 말합니다. 

 

 

 

 

컴퓨터 CPU의 스펙을 살펴보면 2코어 2스레드, 4코어 4스레드, 4코어 8스레드 등을 보신적이 있으실텐데

위에서 얘기 했듯이 스레드는 프로세서(프로그램)를 수행하는 일의 흐름을 말하기 때문에 스레드가 많으면 많을 수록 같은 시간동안 동시에 수행할 수 있는 일의 갯수가 많아지게 됩니다.

간단히 설명하면 2스레드가 한 번에 2개의 일을 처리할 수 있다면 4스레드는 같은 시간동안 4개의 일을 처리할 수 있습니다.

 

아두이노는 기본적으로 싱글스레드이기 때문에 같은 시간동안 하나의 명령어만 처리할 수 있습니다. 그렇기 때문에 delay()가 코드 중간에 걸릴 경우 다수의 모듈을 사용할 때 상당히 애매해지는데

이때 스레드를 사용하면 delay()에 영향을 받는 다른 모듈을 스레드를 추가하여 작동시킨다면 delay()의 영향없이 동시에 두가지 모듈을 작동시킬 수 있게 됩니다.

 

 

 

세마포어

 

멀티스레드를 사용하다보면 서로 다른 스레드에서 하나의 데이터를 동시에 써야하는 경우가 생깁니다.

이럴때 문제가 생길 수 있는데 누가 먼저 공유 데이터에 접근하느냐에 따라 데이터가 변할 수 있습니다. 

 

쉽게 예를 들면 집에 화장실(공유 데이터)이 1개가 있을때 A라는 사람이 먼저 들어가서 사용하고 있습니다.

근데 B라는 사람이 A가 사용하는 도중에 화장실로 무작정 들어와 같이 사용하게 되면 정말로 난감한 상황이 아닐 수 없겠죠? 

이때는 화장실 열쇠(세마포어)를 만들어서 A가 사용할 때는 B가 화장실에 들어올 수 없고 앞에서 기다릴 수 있게 만들 수 있습니다.

B는 화장실(공유 데이터) 앞에서 기다리다가 A가 사용하고 나오면 그제서야 열쇠(세마포어)를 인수받고 들어갈 수 있게 됩니다.

 

프로그래밍에서는 세마포어를 사용하여 공유데이터에 접근하는 스레드를 조절할 수 있습니다.

 

아래 사진은 세마포어를 Task1이 Task2로 건네주는 모식도 입니다.

Task1이 공유 데이터을 사용한 뒤 Task2에게 Semaphore Give를 사용하여 세마포어를 Task2로 넘겨줍니다. 

그러면 Task2에서는 Semaphore Take를 통해 세마포어를 받고 공유 데이터에 접근할 수 있는 권한이 생겨 공유자원을 사용할 수 있습니다.

 

 

 

 

세마포어는 1개가 될 수도 있고 2개가 될 수도 있고, 여러개가 될 수도 있습니다. 

세마포어는 단지 공유 데이터에 접근할 수 있는 권한을 부여할 뿐입니다.

 

멀티 스레드를 사용할 때는 이처럼 다른 스레드가 동시에 사용할 수 있는 공유 데이터가 존재할 수 있으니 유의해야 합니다.

 

 

 

소프트웨어 coding

#include <FreeRTOS_AVR.h>

// LED 13번핀을 사용합니다.
const uint8_t LED_PIN = 13;

// 세마포어 핸들을 선언합니다.
SemaphoreHandle_t sem;
//------------------------------------------------------------------------------


//스레드1
static void Thread1(void* arg) {
  while (1) {

    //세마포어를 받을때까지 스레드1은 계속 기다리게 됩니다.
    xSemaphoreTake(sem, portMAX_DELAY);

    //세마포어를 받을 경우 LED를 끕니다.
    digitalWrite(LED_PIN, LOW);
  }
}
//------------------------------------------------------------------------------


//스레드2
static void Thread2(void* arg) {

  pinMode(LED_PIN, OUTPUT);

  while (1) {
    //13번 LED를 켭니다
    digitalWrite(LED_PIN, HIGH);

    //200ms를 대기합니다.
    vTaskDelay((200L * configTICK_RATE_HZ) / 1000L);

    //세마포어를 전달합니다.
    xSemaphoreGive(sem);

    //다시 200ms 대기합니다.
    vTaskDelay((200L * configTICK_RATE_HZ) / 1000L);
  }
}
//------------------------------------------------------------------------------
void setup() {
  portBASE_TYPE s1, s2;

  Serial.begin(9600);
  
  //세마포어를 초기화 합니다. 세마포어는 동시에 1개만 생성됩니다.
  sem = xSemaphoreCreateCounting(1, 0);

  //우선순위가 2인 thread1을 생성합니다.
  s1 = xTaskCreate(Thread1, NULL, configMINIMAL_STACK_SIZE, NULL, 2, NULL);

  //우선순위가 1인 thread2를 생성합니다.
  s2 = xTaskCreate(Thread2, NULL, configMINIMAL_STACK_SIZE, NULL, 1, NULL);

  // 세마포어,스레드 생성에 에러가 생길 경우 에러메시지 출력
  if (sem== NULL || s1 != pdPASS || s2 != pdPASS ) {
    Serial.println(F("Creation problem"));
    while(1);
  }
  //스케쥴러를 시작합니다.
  vTaskStartScheduler();
  Serial.println(F("Insufficient RAM"));
  while(1);
}

//loop는 사용하지 않습니다.
void loop() {
  // Not used.
}

 

 

 

 

위 코드는 예제에서 볼 수 있는 frBlink입니다.

 

위 소스를 실행시키면 13번 LED가 200ms마다 깜빡이는 것을 볼 수 있습니다. (깜빡임 주기 = 400ms)

 

thread2에서는 13번 LED를 켜고 200ms기다린 뒤에 세마포어를 thread1로 넘겨주게 됩니다.

세마포어를 기다리다가 세마포어를 받은 thread1은 13번 LED를 끄는 작업을 수행하고 while(1)에 의해 세마포어를 기다리는 형태가 됩니다.

 

한편 thread2에서는 세마포어를 전달하고 200ms를 기다린뒤에 다시 13번 LED를 킨 다음 200ms뒤에 세마포어를 thread1에 전달하는 작업을 반복합니다.

이렇게 2개의 스레드의 반복적인 작업을 통해 13번 LED는 깜빡임을 반복하게 됩니다.

 

스레드를 사용하는 코드에서는 setup()까지만 사용하고 loop()은 사용하지 않습니다. (그 대신 thread내부에서 while()문을 사용하여 무한 반복 작업하게 됩니다.)

코드가 어려워 보이지만 막상 이해하면 별거 아닌 코드가 되버리니 잘 사용해보세요~!

 

kocoafab

안녕하세요. 코코아팹 운영자입니다.

FreeRTOS, 아두이노, 오렌지보드