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 상태를 유지하므로 그 시간에 다른 작업을 더 할 수 있다.



댓글 2개:

  1. 작성자가 댓글을 삭제했습니다.

    답글삭제
  2. vPBSW Task에서
    else if ((0==last_state) && (1==cur_state)) { // button released
    last_state = 1;
    }
    로 수정해야 할 것 같습니다. 수정 부탁드려요~!

    답글삭제