멀티태스킹 프로그램을 만들다 보면 태스크 간 또는 태스크와 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 상태를 유지하므로 그 시간에 다른 작업을 더 할 수 있다.
작성자가 댓글을 삭제했습니다.
답글삭제vPBSW Task에서
답글삭제else if ((0==last_state) && (1==cur_state)) { // button released
last_state = 1;
}
로 수정해야 할 것 같습니다. 수정 부탁드려요~!