레이블이 Inter-process communication인 게시물을 표시합니다. 모든 게시물 표시
레이블이 Inter-process communication인 게시물을 표시합니다. 모든 게시물 표시

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월 29일 금요일

아두이노에서 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() {
}