본문 바로가기

이론/AVR

무이메이커스_[arduino]아두이노에서 멀티스레드 사용하기 / FreeRTOS를 이용한 멀티스레드 (1/2)

안녕하세요 허니컴의 무이메이커스입니다. 

오늘은 아두이노를 사용하면서 Delay()로 인해 발생할 수 있는 문제점과 해결책을 알아보겠습니다. 

 

아두이노는 싱글스레드로 동작합니다. 스레드는 프로세서를 수행하는 하나의 흐름을 말합니다.

스레드가 하나라는 것은 한번에 한가지 일만 할 수 있다는것을 의미합니다.

그렇기 때문에 delay()를 사용하게되면 코드전체가 뒤로 밀리는 현상이 발생합니다. 

또, delay()가 사용되어야만 하는 모듈과 다른주기적인 동작이 병행되어야 할 때에도 문제가 발생합니다. 

 

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

 

< 아두이노 멀티스레드 회로도 >

- 사용부품 : 360도 Servo motor1개, LED2개

< 아두이노 멀티스레드 연결방법 >

5V 2개 servo motor의 +
GND 2개 servo motor의 -, 2개 LED의 -
13 LED1의 +
12 LED2의 +
9 servo motor2의 신호선

 

< 아두이노 멀티스레드 소스 코드 >

https://github.com/greiman/FreeRTOS-Arduino

 

greiman/FreeRTOS-Arduino

FreeRTOS 8.2.3 Arduino Libraries. Contribute to greiman/FreeRTOS-Arduino development by creating an account on GitHub.

github.com

위 주소에 들어가게되면 오늘사용할 <FreeRTOS_AVR.h>파일을 다운받을 수 있습니다.

 

#include <FreeRTOS_AVR.h>

#include <Servo.h>

Servo servo;

//LED를 연결하는 핀 번호

const uint8_t LED_PIN = 13;

const uint8_t LED_PIN2 = 12;

//세마포어 핸들을 선언

SemaphoreHandle_t sem;

SemaphoreHandle_t sem2;

static void Thread1(void* arg)

{

pinMode(LED_PIN2, OUTPUT);

while (1)

{

xSemaphoreTake(sem2, portMAX_DELAY); //세마포어를 받을 때 까지 기다린다.

for(int i=0; i<10; i++)

{

digitalWrite(LED_PIN2,LOW);

digitalWrite(LED_PIN, HIGH);

servo.write(20);

vTaskDelay((200L * configTICK_RATE_HZ) / 1000L); //200밀리세컨드 대기한다.

digitalWrite(LED_PIN, LOW);

vTaskDelay((200L * configTICK_RATE_HZ) / 1000L);

}

servo.write(90);

xSemaphoreGive(sem); //세마포어를 주어서 Thread2의 동작을 수행할 수 있게한다.

}

}

//------------------------------------------------------------------------------

static void Thread2(void* arg)

{

pinMode(LED_PIN, OUTPUT);

while (1)

{

xSemaphoreTake(sem, portMAX_DELAY); //세마포어를 받을 때 까지 기다린다.

for(int j=0; j<10; j++)

{

digitalWrite(LED_PIN,LOW);

digitalWrite(LED_PIN2, HIGH);

servo.write(150);

vTaskDelay((200L * configTICK_RATE_HZ) / 1000L); //200밀리세컨드 대기한다.

digitalWrite(LED_PIN2, LOW);

vTaskDelay((200L * configTICK_RATE_HZ) / 1000L);

}

servo.write(90);

xSemaphoreGive(sem2); //세마포어를 주어서 Thread1의 동작을 수행할 수 있게한다.

}

}

//------------------------------------------------------------------------------

void setup()

{

servo.attach(9);

portBASE_TYPE s1, s2;

Serial.begin(9600);

//카운팅 세마포어를 생성한다.

sem = xSemaphoreCreateCounting(1, 1); //세마포어를 주고 시작함

sem2 = xSemaphoreCreateCounting(1, 0);

//우선순위 1로 LED끄는 태스크를 생성한다.

s1 = xTaskCreate(Thread1, NULL, configMINIMAL_STACK_SIZE, NULL, 1, NULL);

//우선순위 1로 LED켜는 태스크를 생성한다.

s2 = xTaskCreate(Thread2, NULL, configMINIMAL_STACK_SIZE, NULL, 1, NULL);

//생성중 에러 발생했는지 체크

if ((sem== NULL||sem2==NULL) || (s1 != pdPASS || s2 != pdPASS )) {

Serial.println(F("Creation problem"));

while(1);

}

//스케줄러를 시작한다.

vTaskStartScheduler();

//여기가 실행된다면 메모리 부족 상태

Serial.println(F("Insufficient RAM"));

while(1);

}

//------------------------------------------------------------------------------

void loop()

{

// Not used.

}

< 세마포어란? >

위 코드를 보면 "세마포어"라는 용어가 사용됩니다.

멀티스레드를 사용중에 서로 다른 스레드가 하나의 데이터를 사용해야 할 상황이 생길 수 있습니다.

이때 공유데이터에 접근할 수 있는 권한을 부여하는 것을 "세마포어"라고 합니다.

<세마포어의 흐름>

위 그림처럼 Task1이 공유데이터를 사용하고 난 후 Semaphore Give를 통해 세마포어를 Task2에게 넘겨줍니다. Task2는 Semaphore Take를 이용해 세마포어를 받아 공유데이터에 접근할 수 있게됩니다.

< 소스&동작 설명 >

 

위 그림처럼 Thread1이 Thread2에게 세마포어를 넘겨주면

Thread2가 실행되면서 LED2를 blink하고, 360도 서보모터는 왼쪽으로 회전 합니다.

또, Thread2가 Thread1에게 세마포어를 넘겨주면

Thread1이 실행되면서 LED1를 blink하고, 360도 서보모터는 오른쪽으로 회전 합니다.

(360도 서보모터는 90을 기준으로 180에 가까운 숫자를 servo.write()에 입력하면 왼쪽으로 회전하고,

0에 가까운 숫자를 servo.write()에 입력하면 오른쪽으로 회전합니다.)

 

< 동작영상 >

 

 

참고 : <FreeRTOS_AVR.h> 함수설명

https://www.freertos.org/CreateCounting.html

 

FreeRTOS - A FREE Open Source RTOS for small real time embedded systems

semphr. h Creates a counting semaphore and returns a handle by which the newly created semaphore can be referenced. configSUPPORT_DYNAMIC_ALLOCATION must be set to 1 in FreeRTOSConfig.h, or left undefined (in which case it will default to 1), for this RTOS

www.freertos.org

 

 

........

시제품 제작 문의