
xEventGroupSetBits()와 xEventGroupWaitBits()의 매개변수 의미와 동작 방식을 정확히 파악한다Queue는 데이터를 전달하고, Semaphore는 단일 이벤트를 신호로 알립니다. Event Group은 여러 이벤트 비트를 하나의 객체에 묶어 AND/OR 조건으로 대기할 수 있는 메커니즘입니다. "센서 A, B, C가 모두 준비됐을 때 처리를 시작한다"는 요구사항에 가장 적합합니다.
FreeRTOS Event Group은 내부적으로 EventBits_t 타입(최소 24비트)의 비트 필드를 유지합니다. 각 비트가 하나의 이벤트를 나타냅니다. 여러 Task 또는 ISR이 서로 다른 비트를 세트할 수 있고, 대기하는 Task는 특정 비트 조합이 충족될 때까지 Blocked 상태를 유지합니다.
/*
* Event Group 비트 정의 예시
*
* EventBits_t (최소 24비트 유효 비트 사용, 비트 24~31은 내부 예약)
*
* Bit 0: 온도 센서 데이터 준비
* Bit 1: 습도 센서 데이터 준비
* Bit 2: 조도 센서 데이터 준비
* Bit 3: UART 전송 완료
* Bit 4: ADC 보정 완료
*
* AND 대기: Bit 0, 1, 2가 모두 세트될 때까지 대기 (모든 센서 준비)
* OR 대기: Bit 0, 1, 2 중 하나라도 세트되면 즉시 처리 (하나씩 처리)
*/
#include "FreeRTOS.h"
#include "event_groups.h"
#include <stdio.h>
#include <stdint.h>
#define EVT_TEMP_READY (1U << 0) /* 온도 센서 준비 */
#define EVT_HUM_READY (1U << 1) /* 습도 센서 준비 */
#define EVT_LUX_READY (1U << 2) /* 조도 센서 준비 */
#define EVT_UART_TX_DONE (1U << 3) /* UART 전송 완료 */
#define EVT_ADC_CALIB (1U << 4) /* ADC 보정 완료 */
#define EVT_ALL_SENSORS (EVT_TEMP_READY | EVT_HUM_READY | EVT_LUX_READY)
static EventGroupHandle_t xSensorEvents;
int main(void)
{
/*
* xEventGroupCreate():
* Heap에 Event Group 제어 블록을 할당합니다.
* 모든 비트는 0으로 초기화됩니다.
* 반환값이 NULL이면 Heap 부족 → configASSERT로 즉시 확인합니다.
*/
xSensorEvents = xEventGroupCreate();
configASSERT(xSensorEvents != NULL);
/* Task 생성 및 스케줄러 시작 */
vTaskStartScheduler();
while (1);
}
/*
* xEventGroupSetBits(xEventGroup, uxBitsToSet):
* 지정한 비트들을 세트합니다.
* 세트 후 해당 비트를 기다리던 Task들의 대기 조건을 검사하고
* 조건이 충족된 Task를 Unblock합니다.
*
* 반환값: 함수 반환 시점의 Event Group 비트 값
* (Unblock된 Task가 비트를 클리어했을 수도 있음)
*
* xEventGroupSetBitsFromISR():
* ISR 안전 버전. portYIELD_FROM_ISR()과 함께 사용합니다.
*/
/* 온도 센서 Task: 측정 완료 후 이벤트 비트 세트 */
void vTempSensorTask(void *pvParameters)
{
while (1)
{
/* 온도 측정 시뮬레이션 (100ms) */
vTaskDelay(pdMS_TO_TICKS(100));
/* 측정 완료: 비트 0 세트 */
xEventGroupSetBits(xSensorEvents, EVT_TEMP_READY);
printf("[Temp] 측정 완료, 이벤트 세트\r\n");
}
}
/* 습도 센서 Task */
void vHumSensorTask(void *pvParameters)
{
while (1)
{
vTaskDelay(pdMS_TO_TICKS(150));
xEventGroupSetBits(xSensorEvents, EVT_HUM_READY);
printf("[Hum] 측정 완료, 이벤트 세트\r\n");
}
}
/* 조도 센서 Task */
void vLuxSensorTask(void *pvParameters)
{
while (1)
{
vTaskDelay(pdMS_TO_TICKS(200));
xEventGroupSetBits(xSensorEvents, EVT_LUX_READY);
printf("[Lux] 측정 완료, 이벤트 세트\r\n");
}
}
/*
* xEventGroupWaitBits(
* xEventGroup, : 대기할 Event Group
* uxBitsToWaitFor, : 대기할 비트 마스크
* xClearOnExit, : pdTRUE = 조건 충족 시 해당 비트 자동 클리어
* xWaitForAllBits, : pdTRUE = AND 대기 (모두 세트), pdFALSE = OR 대기 (하나라도)
* xTicksToWait : 대기 타임아웃
* )
*
* 반환값: 조건 충족 시점의 비트 값 (xClearOnExit 이전)
* 타임아웃 시 현재 비트 값 (조건 미충족일 수 있음)
*/
/* AND 대기: 세 센서 모두 준비될 때까지 대기 */
void vDataProcessTask(void *pvParameters)
{
while (1)
{
/*
* EVT_ALL_SENSORS의 모든 비트가 세트될 때까지 Blocked 상태 유지
*
* xClearOnExit = pdTRUE:
* 조건 충족 후 EVT_ALL_SENSORS 비트들을 자동으로 0으로 클리어합니다.
* 클리어하지 않으면 다음 반복에서도 즉시 조건이 충족됩니다.
*
* xWaitForAllBits = pdTRUE:
* AND 조건: Bit 0 AND Bit 1 AND Bit 2가 모두 세트여야 합니다.
*/
EventBits_t bits = xEventGroupWaitBits(
xSensorEvents,
EVT_ALL_SENSORS, /* 대기할 비트: 0b00000111 */
pdTRUE, /* 조건 충족 시 해당 비트 클리어 */
pdTRUE, /* AND 조건 */
pdMS_TO_TICKS(1000)/* 1초 타임아웃 */
);
if ((bits & EVT_ALL_SENSORS) == EVT_ALL_SENSORS)
{
/* 모든 센서 데이터 준비됨: 처리 시작 */
printf("[Process] 세 센서 모두 준비 → 데이터 처리 시작\r\n");
/* 실제 데이터 읽기 및 처리 */
}
else
{
/* 타임아웃: 일부 센서가 준비되지 않음 */
printf("[Process] 타임아웃 (준비된 비트: 0x%02lX)\r\n",
(uint32_t)bits);
}
}
}
/* OR 대기: 어느 센서든 준비되는 즉시 처리 */
void vRealTimeLogTask(void *pvParameters)
{
while (1)
{
/*
* xWaitForAllBits = pdFALSE:
* OR 조건: Bit 0 OR Bit 1 OR Bit 2 중 하나라도 세트되면 즉시 Unblock
*
* xClearOnExit = pdTRUE:
* 충족된 비트만 클리어됩니다.
* (OR 조건에서는 세트된 비트 전체를 클리어합니다)
*/
EventBits_t bits = xEventGroupWaitBits(
xSensorEvents,
EVT_ALL_SENSORS,
pdTRUE, /* 클리어 */
pdFALSE, /* OR 조건 */
portMAX_DELAY
);
if (bits & EVT_TEMP_READY)
printf("[Log] 온도 데이터 수신\r\n");
if (bits & EVT_HUM_READY)
printf("[Log] 습도 데이터 수신\r\n");
if (bits & EVT_LUX_READY)
printf("[Log] 조도 데이터 수신\r\n");
}
}
/*
* ISR에서는 xEventGroupSetBitsFromISR()을 사용합니다.
* xQueueSendFromISR과 마찬가지로 portYIELD_FROM_ISR()이 필요합니다.
*
* 주의: xEventGroupSetBitsFromISR()은 내부적으로 Timer Daemon Task를 통해
* 비트를 세트합니다. 따라서 configUSE_TIMERS = 1이 필요합니다.
*/
#include "FreeRTOS.h"
#include "event_groups.h"
#include "stm32f4xx_hal.h"
#define EVT_ADC_DONE (1U << 0)
#define EVT_BUTTON_A (1U << 1)
#define EVT_BUTTON_B (1U << 2)
static EventGroupHandle_t xHwEvents;
/* ADC 변환 완료 콜백 (ISR 컨텍스트) */
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc)
{
if (hadc->Instance != ADC1) return;
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
/*
* xEventGroupSetBitsFromISR():
* Timer Daemon Task 큐를 통해 비트 세트를 요청합니다.
* 즉시 세트가 아님 — Daemon Task가 실행될 때 반영됩니다.
* configTIMER_TASK_PRIORITY가 충분히 높아야 지연이 줄어듭니다.
*/
if (xEventGroupSetBitsFromISR(xHwEvents, EVT_ADC_DONE,
&xHigherPriorityTaskWoken) == pdPASS)
{
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
}
/* EXTI 인터럽트 (버튼 A) */
void EXTI0_IRQHandler(void)
{
__HAL_GPIO_EXTI_CLEAR_IT(GPIO_PIN_0);
BaseType_t xWoken = pdFALSE;
xEventGroupSetBitsFromISR(xHwEvents, EVT_BUTTON_A, &xWoken);
portYIELD_FROM_ISR(xWoken);
}
세 개의 센서 Task가 각각 독립적으로 측정을 수행하고, 처리 Task는 모든 센서가 준비될 때까지 대기한 후 데이터를 통합 처리합니다. 동시에 실시간 로그 Task는 개별 이벤트를 즉시 기록합니다.
#include "FreeRTOS.h"
#include "task.h"
#include "event_groups.h"
#include "queue.h"
#include "stm32f4xx_hal.h"
#include <stdio.h>
#include <stdint.h>
/* 이벤트 비트 정의 */
#define EVT_TEMP_READY (1U << 0)
#define EVT_HUM_READY (1U << 1)
#define EVT_LUX_READY (1U << 2)
#define EVT_ERROR_TEMP (1U << 3)
#define EVT_ERROR_HUM (1U << 4)
#define EVT_ALL_SENSORS (EVT_TEMP_READY | EVT_HUM_READY | EVT_LUX_READY)
#define EVT_ANY_ERROR (EVT_ERROR_TEMP | EVT_ERROR_HUM)
/* 센서 데이터: Queue로 실제 값 전달 */
typedef struct
{
float temperature;
float humidity;
uint16_t lux;
uint32_t timestamp_ms;
} EnvSnapshot_t;
static EventGroupHandle_t xSensorEvents;
static QueueHandle_t xSnapshotQueue;
/* 공유 센서 값: 각 Task가 채움, Process Task가 스냅샷 */
static volatile float g_temp = 0.0f;
static volatile float g_hum = 0.0f;
static volatile uint16_t g_lux = 0;
/* ---- 센서 Task들 ---- */
void vTempTask(void *pvParameters)
{
while (1)
{
vTaskDelay(pdMS_TO_TICKS(100));
/* NTC 온도 센서 읽기 시뮬레이션 */
g_temp = 25.0f + (float)(xTaskGetTickCount() % 100) * 0.1f;
xEventGroupSetBits(xSensorEvents, EVT_TEMP_READY);
}
}
void vHumTask(void *pvParameters)
{
while (1)
{
vTaskDelay(pdMS_TO_TICKS(150));
/* SHT31 습도 센서 읽기 시뮬레이션 */
g_hum = 60.0f + (float)(xTaskGetTickCount() % 200) * 0.05f;
xEventGroupSetBits(xSensorEvents, EVT_HUM_READY);
}
}
void vLuxTask(void *pvParameters)
{
while (1)
{
vTaskDelay(pdMS_TO_TICKS(200));
/* BH1750 조도 센서 읽기 시뮬레이션 */
g_lux = (uint16_t)(100U + (xTaskGetTickCount() / 10U) % 900U);
xEventGroupSetBits(xSensorEvents, EVT_LUX_READY);
}
}
/* ---- 통합 처리 Task: AND 대기 ---- */
void vProcessTask(void *pvParameters)
{
EnvSnapshot_t snap;
while (1)
{
/*
* 세 센서 모두 준비될 때까지 최대 500ms 대기
* 조건 충족 시 EVT_ALL_SENSORS 비트 자동 클리어 (다음 사이클 준비)
*/
EventBits_t bits = xEventGroupWaitBits(
xSensorEvents,
EVT_ALL_SENSORS,
pdTRUE, /* 클리어 */
pdTRUE, /* AND */
pdMS_TO_TICKS(500)
);
if ((bits & EVT_ALL_SENSORS) == EVT_ALL_SENSORS)
{
/* 스냅샷 생성 */
snap.temperature = g_temp;
snap.humidity = g_hum;
snap.lux = g_lux;
snap.timestamp_ms = xTaskGetTickCount();
/* Queue로 전달 */
xQueueSend(xSnapshotQueue, &snap, 0);
}
else
{
/* 타임아웃: 미준비 센서 확인 */
if (!(bits & EVT_TEMP_READY)) printf("[Warn] 온도 센서 타임아웃\r\n");
if (!(bits & EVT_HUM_READY)) printf("[Warn] 습도 센서 타임아웃\r\n");
if (!(bits & EVT_LUX_READY)) printf("[Warn] 조도 센서 타임아웃\r\n");
}
}
}
/* ---- 출력 Task: Queue에서 스냅샷 수신 ---- */
void vPrintTask(void *pvParameters)
{
EnvSnapshot_t snap;
while (1)
{
if (xQueueReceive(xSnapshotQueue, &snap, portMAX_DELAY) == pdTRUE)
{
printf("[%5lums] T=%.1f°C H=%.1f%% L=%uLux\r\n",
snap.timestamp_ms,
snap.temperature,
snap.humidity,
snap.lux);
}
}
}
/* ---- main ---- */
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_USART2_UART_Init();
xSensorEvents = xEventGroupCreate();
xSnapshotQueue = xQueueCreate(10, sizeof(EnvSnapshot_t));
configASSERT(xSensorEvents != NULL);
configASSERT(xSnapshotQueue != NULL);
xTaskCreate(vTempTask, "Temp", 256, NULL, 3, NULL);
xTaskCreate(vHumTask, "Hum", 256, NULL, 3, NULL);
xTaskCreate(vLuxTask, "Lux", 256, NULL, 3, NULL);
xTaskCreate(vProcessTask, "Process", 512, NULL, 2, NULL);
xTaskCreate(vPrintTask, "Print", 512, NULL, 1, NULL);
vTaskStartScheduler();
while (1);
}
| 항목 | 내용 |
|---|---|
| xEventGroupCreate | Heap에 Event Group 할당, 모든 비트 0 초기화 |
| xEventGroupSetBits | 비트 세트 후 대기 Task의 조건 자동 검사 |
| xEventGroupWaitBits | AND(pdTRUE) / OR(pdFALSE) 조건 대기, 타임아웃 지원 |
| xClearOnExit = pdTRUE | 조건 충족 시 해당 비트 자동 클리어 (재사용 준비) |
| xEventGroupSetBitsFromISR | ISR 전용, Timer Daemon 경유, configUSE_TIMERS 필요 |
| Queue와 차이 | Event Group은 데이터 없는 신호만, 실제 값은 Queue로 별도 전달 |
vTempTask에 의도적으로 200ms 지연을 추가하여 타임아웃 경고가 출력되는 조건을 만들고, 타임아웃 횟수를 카운터로 기록하여 5번 연속 타임아웃 시 EVT_ERROR_TEMP 비트를 세트하는 로직을 작성하십시오.EVT_ANY_ERROR를 OR 조건으로 대기하는 에러 핸들러 Task를 추가하고, 에러 발생 시 모든 센서 Task를 일시 중단(vTaskSuspend)하고 3초 후 재개(vTaskResume)하는 복구 시퀀스를 구현하십시오.xEventGroupGetBits()로 현재 비트 상태를 읽어 1초마다 비트 상태를 16진수로 출력하는 모니터 Task를 추가하십시오.xEventGroupWaitBits()가 즉시 반환되고 비트가 이미 세트되어 있다면 이전 사이클에서 xClearOnExit = pdTRUE로 클리어되지 않은 것입니다. xClearOnExit 값을 확인합니다.xEventGroupSetBitsFromISR()을 호출했는데 Task가 깨어나지 않으면 configUSE_TIMERS = 1과 Timer Daemon Task 스택 크기(configTIMER_TASK_STACK_DEPTH)를 확인합니다. Daemon Task가 스택 오버플로로 죽었을 수 있습니다.vTaskList()로 Task 상태를 확인합니다. 높은 우선순위 Task에 의해 선점되어 실행 기회를 못 받을 수 있습니다.