Binary Semaphore는 0 또는 1의 값만 가지는 세마포어입니다. Mutex와 동일한 내부 구조체를 사용하지만, 소유권 개념이 없으며 Priority Inheritance를 지원하지 않습니다.
| 항목 | 설명 |
|---|---|
| 초기 상태 | 사용 불가 (0) |
| 최대값 | 1 |
| 소유권 | 없음 |
| Priority Inheritance | 미지원 |
| ISR에서 Give | 가능 (xSemaphoreGiveFromISR) |
| 주요 용도 | Task 간 또는 ISR-Task 간 이벤트 동기화 |
Binary Semaphore의 초기 상태는 0(unavailable)입니다. 따라서 생성 직후
xSemaphoreTake()를 호출하면 다른 Task 또는 ISR이xSemaphoreGive()를 호출하기 전까지 차단됩니다. 이 특성이 이벤트 신호 전달에 적합한 이유입니다.
/* FreeRTOSConfig.h */
#define configUSE_COUNTING_SEMAPHORES 1 /* Binary Semaphore는 별도 설정 불필요 */
#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"
SemaphoreHandle_t xBinarySemaphore;
int main(void) {
/* Binary Semaphore 생성: 초기 상태 = 0 (unavailable) */
xBinarySemaphore = xSemaphoreCreateBinary();
configASSERT(xBinarySemaphore != NULL);
/* ... */
vTaskStartScheduler();
while(1);
}
Binary Semaphore의 가장 일반적인 사용 패턴은 ISR에서 이벤트를 감지하고 Task에 신호를 전달하는 것입니다.
[ISR-Task 동기화 흐름]
ISR 발생
|
v
xSemaphoreGiveFromISR() -> Binary Semaphore 값 0 -> 1
|
v
portYIELD_FROM_ISR() -> 컨텍스트 전환 요청
|
v
Task: xSemaphoreTake() -> Binary Semaphore 값 1 -> 0, 실행 재개
|
v
이벤트 처리
SemaphoreHandle_t xButtonSemaphore;
/* GPIO 인터럽트 서비스 루틴 */
void EXTI0_IRQHandler(void) {
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
/* ISR 전용 Give 함수 사용 */
xSemaphoreGiveFromISR(xButtonSemaphore, &xHigherPriorityTaskWoken);
/* 더 높은 우선순위의 Task가 깨어났다면 즉시 컨텍스트 전환 */
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
/* HAL 인터럽트 플래그 클리어 */
__HAL_GPIO_EXTI_CLEAR_IT(GPIO_PIN_0);
}
/* 이벤트 처리 Task */
void vButtonTask(void *pvParameters) {
while(1) {
/* ISR이 Give를 호출하기 전까지 차단 상태 유지 */
if(xSemaphoreTake(xButtonSemaphore, portMAX_DELAY) == pdTRUE) {
/* 버튼 이벤트 처리 */
process_button_event();
}
}
}
주의 1: 연속 신호 누락 가능성
Binary Semaphore는 최대값이 1이므로, Task가 처리하기 전에 ISR이 두 번 이상 발생하면 두 번째 신호부터는 인터럽트 접근이 누락됩니다.
[신호 누락 시나리오]
ISR 1회 발생 -> Give -> 세마포어 값 = 1
ISR 2회 발생 -> Give -> 세마포어 값 = 1 (변화 없음, 신호 누락)
Task 처리 -> Take -> 세마포어 값 = 0 (1회 처리만 수행)
해결 방법: 빠른 신호 처리가 필요한 경우 Counting Semaphore 또는 Queue 사용
주의 2: ISR에서 일반 Give 함수 사용 금지
/* 오류: ISR 내에서 일반 xSemaphoreGive() 사용 금지 */
void EXTI0_IRQHandler(void) {
xSemaphoreGive(xBinarySemaphore); /* 오류: ISR에서 사용 불가(Task 환경 전용)*/
}
/* 올바름: ISR 전용 함수 사용 */
void EXTI0_IRQHandler(void) {
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
xSemaphoreGiveFromISR(xBinarySemaphore, &xHigherPriorityTaskWoken);
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
Counting Semaphore는 0 이상의 정수 값을 가지는 세마포어입니다. 최대값은 생성 시 지정하며, 값이 0이 되면 xSemaphoreTake() 호출이 차단됩니다.
| 항목 | 설명 |
|---|---|
| 초기 상태 | 생성 시 지정 (0 또는 최대값) |
| 최대값 | 생성 시 지정 (1 이상의 정수) |
| 소유권 | 없음 |
| Priority Inheritance | 미지원 |
| ISR에서 Give | 가능 (xSemaphoreGiveFromISR) |
| 주요 용도 | 이벤트 카운팅, 자원 풀 관리 |
SemaphoreHandle_t xSemaphoreCreateCounting(
UBaseType_t uxMaxCount, /* 세마포어의 최대값 */
UBaseType_t uxInitialCount /* 초기값 */
);
/* FreeRTOSConfig.h */
#define configUSE_COUNTING_SEMAPHORES 1 /* Counting Semaphore 사용 시 필수 */
초기값 설정 기준:
[용도에 따른 초기값 선택]
이벤트 카운팅 용도:
uxInitialCount = 0
의미: 처음에는 처리할 이벤트 없음, Give 호출 시마다 카운트 증가
자원 풀 관리 용도:
uxInitialCount = uxMaxCount
의미: 처음에는 모든 자원이 사용 가능, Take 호출 시마다 사용 가능 자원 감소
ISR이 빠르게 반복 발생하여 이벤트가 누적될 수 있는 경우에 사용합니다.
#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"
#define MAX_EVENT_COUNT 10
SemaphoreHandle_t xEventSemaphore;
/* ISR: 센서 데이터 수신 인터럽트 */
void USART1_IRQHandler(void) {
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
/* 수신 이벤트 카운트 증가 */
xSemaphoreGiveFromISR(xEventSemaphore, &xHigherPriorityTaskWoken);
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
/* 이벤트 처리 Task */
void vUartProcessTask(void *pvParameters) {
while(1) {
/*
* 세마포어 값이 0이면 차단
* ISR이 연속으로 발생한 경우 카운트만큼 반복 처리
*/
if(xSemaphoreTake(xEventSemaphore, portMAX_DELAY) == pdTRUE) {
process_uart_data();
}
}
}
int main(void) {
/* 초기값 0: 처음에는 처리할 이벤트 없음 */
xEventSemaphore = xSemaphoreCreateCounting(MAX_EVENT_COUNT, 0);
configASSERT(xEventSemaphore != NULL);
xTaskCreate(vUartProcessTask, "UartProc", 256, NULL, 2, NULL);
vTaskStartScheduler();
while(1);
}
DMA 채널, 통신 슬롯 등 동시에 사용 가능한 자원의 수를 제한하는 경우에 사용합니다.
#define DMA_CHANNEL_COUNT 4
SemaphoreHandle_t xDmaChannelSemaphore;
/*
* DMA 채널 획득
* 사용 가능한 채널이 없으면 차단
*/
BaseType_t dma_channel_acquire(TickType_t xTimeout) {
return xSemaphoreTake(xDmaChannelSemaphore, xTimeout);
}
/*
* DMA 채널 반납
* 전송 완료 후 반드시 호출
*/
void dma_channel_release(void) {
xSemaphoreGive(xDmaChannelSemaphore);
}
void vDmaUserTask(void *pvParameters) {
while(1) {
/* 사용 가능한 DMA 채널 확보 (최대 100ms 대기) */
if(dma_channel_acquire(pdMS_TO_TICKS(100)) == pdTRUE) {
/* DMA 전송 수행 */
start_dma_transfer();
wait_dma_complete();
/* 채널 반납 */
dma_channel_release();
} else {
/* 타임아웃: 채널 획득 실패 처리 */
handle_dma_busy();
}
vTaskDelay(pdMS_TO_TICKS(50));
}
}
int main(void) {
/* 초기값 = 최대값: 처음에는 모든 채널 사용 가능 */
xDmaChannelSemaphore = xSemaphoreCreateCounting(DMA_CHANNEL_COUNT, DMA_CHANNEL_COUNT);
configASSERT(xDmaChannelSemaphore != NULL);
/* 4개의 Task가 DMA 채널을 경쟁하여 사용 */
for(int i = 0; i < 4; i++) {
xTaskCreate(vDmaUserTask, "DmaUser", 256, NULL, 2, NULL);
}
vTaskStartScheduler();
while(1);
}
/*
* uxSemaphoreGetCount(): 현재 세마포어 값 조회
* 자원 풀에서 남은 자원 수 확인 등에 활용
*/
UBaseType_t uxRemaining = uxSemaphoreGetCount(xDmaChannelSemaphore);
printf("사용 가능한 DMA 채널: %u / %d\n", uxRemaining, DMA_CHANNEL_COUNT);
| 특성 | Binary Semaphore | Counting Semaphore |
|---|---|---|
| 값 범위 | 0 또는 1 | 0 ~ 지정한 최대값 |
| 연속 이벤트 처리 | 누락 가능 | 카운트 누적 가능 |
| 자원 풀 관리 | 불가 | 가능 |
| 단일 이벤트 동기화 | 적합 | 가능하나 불필요한 오버헤드 |
| 생성 함수 | xSemaphoreCreateBinary() | xSemaphoreCreateCounting() |
| 특성 | Binary Semaphore | Mutex |
|---|---|---|
| 주요 목적 | 이벤트 동기화 | 공유 자원 보호 |
| 소유권 | 없음 | 있음 (Take한 Task만 Give) |
| Priority Inheritance | 미지원 | 지원 |
| ISR Give | 가능 | 불가 |
| 초기 상태 | unavailable (0) | available (1) |
[세마포어 선택 흐름도]
Task 간 신호 전달이 목적인가?
YES -> 단일 이벤트인가?
YES -> Binary Semaphore
NO -> 이벤트 누적이 발생할 수 있는가?
YES -> Counting Semaphore
NO -> Binary Semaphore
NO -> 공유 자원을 보호하는가?
YES -> Mutex (Priority Inheritance 필요 여부 고려)
NO -> 여러 자원을 동시에 제한하는가?
YES -> Counting Semaphore (자원 풀)
NO -> EventGroup 또는 Queue 고려
Binary Semaphore를 사용해야 하는 경우:
/* 1. ISR -> Task 단방향 이벤트 전달 */
void ADC_IRQHandler(void) {
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
xSemaphoreGiveFromISR(xAdcDoneSemaphore, &xHigherPriorityTaskWoken);
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
/* 2. 특정 Task가 다른 Task의 완료를 기다리는 경우 */
void vWorkerTask(void *pvParameters) {
perform_heavy_computation();
xSemaphoreGive(xWorkDoneSemaphore); /* 완료 신호 */
vTaskDelete(NULL);
}
void vControllerTask(void *pvParameters) {
xTaskCreate(vWorkerTask, "Worker", 256, NULL, 1, NULL);
xSemaphoreTake(xWorkDoneSemaphore, portMAX_DELAY); /* 완료 대기 */
use_computation_result();
}
Counting Semaphore를 사용해야 하는 경우:
/* 1. 빠른 ISR이 반복 발생하여 이벤트가 누적되는 경우 */
/* 2. 동시 접근 수를 N개로 제한하는 자원 풀 */
#define MAX_CONNECTIONS 5
SemaphoreHandle_t xConnectionSlot;
BaseType_t network_connect(void) {
/* 슬롯이 없으면 차단 */
return xSemaphoreTake(xConnectionSlot, pdMS_TO_TICKS(200));
}
void network_disconnect(void) {
xSemaphoreGive(xConnectionSlot); /* 슬롯 반납 */
}
생산자 Task가 원형 버퍼에 데이터를 쓰고, 소비자 Task가 Counting Semaphore로 데이터 가용 여부를 추적하여 읽는 구조를 구현합니다.
#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"
#include <stdio.h>
#include <string.h>
#define BUFFER_SIZE 8
#define PRODUCER_DELAY 200 /* ms */
#define CONSUMER_DELAY 350 /* ms */
typedef struct {
uint32_t data;
uint32_t timestamp;
} BufferItem;
static BufferItem ringBuffer[BUFFER_SIZE];
static uint8_t writeIdx = 0;
static uint8_t readIdx = 0;
/*
* xItemsAvailable: 소비 가능한 항목 수 (초기 0)
* xSpaceAvailable: 생산 가능한 빈 슬롯 수 (초기 BUFFER_SIZE)
*/
SemaphoreHandle_t xItemsAvailable;
SemaphoreHandle_t xSpaceAvailable;
SemaphoreHandle_t xBufferMutex; /* 버퍼 인덱스 보호용 Mutex */
/* 생산자 Task */
void vProducerTask(void *pvParameters) {
uint32_t producedCount = 0;
while(1) {
vTaskDelay(pdMS_TO_TICKS(PRODUCER_DELAY));
/* 빈 슬롯 확보 대기 */
if(xSemaphoreTake(xSpaceAvailable, pdMS_TO_TICKS(500)) != pdTRUE) {
printf("[Producer] 버퍼 포화, 슬롯 대기 타임아웃\n");
continue;
}
/* 버퍼 쓰기: Mutex로 인덱스 보호 */
xSemaphoreTake(xBufferMutex, portMAX_DELAY);
ringBuffer[writeIdx].data = producedCount;
ringBuffer[writeIdx].timestamp = xTaskGetTickCount();
printf("[Producer] 항목 %lu 쓰기 (인덱스 %u)\n", producedCount, writeIdx);
writeIdx = (writeIdx + 1) % BUFFER_SIZE;
producedCount++;
xSemaphoreGive(xBufferMutex);
/* 소비 가능한 항목 수 증가 */
xSemaphoreGive(xItemsAvailable);
}
}
/* 소비자 Task */
void vConsumerTask(void *pvParameters) {
BufferItem item;
while(1) {
vTaskDelay(pdMS_TO_TICKS(CONSUMER_DELAY));
/* 소비 가능한 항목 대기 */
if(xSemaphoreTake(xItemsAvailable, pdMS_TO_TICKS(1000)) != pdTRUE) {
printf("[Consumer] 버퍼 비어있음, 대기 타임아웃\n");
continue;
}
/* 버퍼 읽기: Mutex로 인덱스 보호 */
xSemaphoreTake(xBufferMutex, portMAX_DELAY);
item = ringBuffer[readIdx];
printf("[Consumer] 항목 %lu 읽기 (인덱스 %u, 지연 %lu ms)\n",
item.data,
readIdx,
xTaskGetTickCount() - item.timestamp);
readIdx = (readIdx + 1) % BUFFER_SIZE;
xSemaphoreGive(xBufferMutex);
/* 빈 슬롯 수 증가 */
xSemaphoreGive(xSpaceAvailable);
}
}
/* 버퍼 상태 모니터링 Task */
void vMonitorTask(void *pvParameters) {
while(1) {
vTaskDelay(pdMS_TO_TICKS(2000));
UBaseType_t items = uxSemaphoreGetCount(xItemsAvailable);
UBaseType_t space = uxSemaphoreGetCount(xSpaceAvailable);
printf("\n--- 버퍼 상태 ---\n");
printf(" 사용 중: %u / %d\n", (unsigned)(BUFFER_SIZE - space), BUFFER_SIZE);
printf(" 가용 슬롯: %u\n", (unsigned)space);
printf(" 소비 대기 항목: %u\n\n", (unsigned)items);
}
}
int main(void) {
/* 항목 카운터: 초기 0 (비어있음) */
xItemsAvailable = xSemaphoreCreateCounting(BUFFER_SIZE, 0);
/* 슬롯 카운터: 초기 BUFFER_SIZE (모두 비어있음) */
xSpaceAvailable = xSemaphoreCreateCounting(BUFFER_SIZE, BUFFER_SIZE);
xBufferMutex = xSemaphoreCreateMutex();
configASSERT(xItemsAvailable != NULL);
configASSERT(xSpaceAvailable != NULL);
configASSERT(xBufferMutex != NULL);
printf("=== Counting Semaphore 버퍼 동기화 실습 ===\n\n");
xTaskCreate(vProducerTask, "Producer", 256, NULL, 2, NULL);
xTaskCreate(vConsumerTask, "Consumer", 256, NULL, 2, NULL);
xTaskCreate(vMonitorTask, "Monitor", 256, NULL, 1, NULL);
vTaskStartScheduler();
while(1);
}
여러 생산자와 여러 소비자가 동일 버퍼에 접근하는 구조로 확장합니다.
#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"
#include <stdio.h>
#define BUFFER_SIZE 16
#define NUM_PRODUCERS 2
#define NUM_CONSUMERS 3
static uint32_t ringBuffer[BUFFER_SIZE];
static uint8_t writeIdx = 0;
static uint8_t readIdx = 0;
SemaphoreHandle_t xItemsAvailable;
SemaphoreHandle_t xSpaceAvailable;
SemaphoreHandle_t xWriteMutex; /* 쓰기 인덱스 보호 */
SemaphoreHandle_t xReadMutex; /* 읽기 인덱스 보호 */
void vProducerTask(void *pvParameters) {
uint32_t taskId = (uint32_t)(uintptr_t)pvParameters;
uint32_t producedVal = taskId * 1000;
while(1) {
vTaskDelay(pdMS_TO_TICKS(100 + taskId * 50));
if(xSemaphoreTake(xSpaceAvailable, pdMS_TO_TICKS(300)) != pdTRUE) {
printf("[Producer %lu] 슬롯 없음, 스킵\n", taskId);
continue;
}
xSemaphoreTake(xWriteMutex, portMAX_DELAY);
ringBuffer[writeIdx] = producedVal;
printf("[Producer %lu] 값 %lu 쓰기 (인덱스 %u)\n",
taskId, producedVal, writeIdx);
writeIdx = (writeIdx + 1) % BUFFER_SIZE;
producedVal++;
xSemaphoreGive(xWriteMutex);
xSemaphoreGive(xItemsAvailable);
}
}
void vConsumerTask(void *pvParameters) {
uint32_t taskId = (uint32_t)(uintptr_t)pvParameters;
uint32_t value;
while(1) {
vTaskDelay(pdMS_TO_TICKS(150 + taskId * 30));
if(xSemaphoreTake(xItemsAvailable, pdMS_TO_TICKS(500)) != pdTRUE) {
printf("[Consumer %lu] 항목 없음, 대기\n", taskId);
continue;
}
xSemaphoreTake(xReadMutex, portMAX_DELAY);
value = ringBuffer[readIdx];
printf("[Consumer %lu] 값 %lu 읽기 (인덱스 %u)\n",
taskId, value, readIdx);
readIdx = (readIdx + 1) % BUFFER_SIZE;
xSemaphoreGive(xReadMutex);
xSemaphoreGive(xSpaceAvailable);
}
}
int main(void) {
xItemsAvailable = xSemaphoreCreateCounting(BUFFER_SIZE, 0);
xSpaceAvailable = xSemaphoreCreateCounting(BUFFER_SIZE, BUFFER_SIZE);
xWriteMutex = xSemaphoreCreateMutex();
xReadMutex = xSemaphoreCreateMutex();
configASSERT(xItemsAvailable != NULL);
configASSERT(xSpaceAvailable != NULL);
configASSERT(xWriteMutex != NULL);
configASSERT(xReadMutex != NULL);
printf("=== 다중 생산자-소비자 실습 ===\n\n");
for(uint32_t i = 0; i < NUM_PRODUCERS; i++) {
xTaskCreate(vProducerTask, "Producer",
256, (void *)(uintptr_t)i, 2, NULL);
}
for(uint32_t i = 0; i < NUM_CONSUMERS; i++) {
xTaskCreate(vConsumerTask, "Consumer",
256, (void *)(uintptr_t)i, 2, NULL);
}
vTaskStartScheduler();
while(1);
}
ADC DMA 전송 완료 인터럽트를 Binary Semaphore로 Task에 전달하고, 수집된 데이터를 처리하는 구조를 구현합니다.
#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"
#include <stdio.h>
#define ADC_SAMPLE_COUNT 64
static uint16_t adcBuffer[ADC_SAMPLE_COUNT];
SemaphoreHandle_t xAdcDoneSemaphore; /* ADC DMA 완료 신호 */
SemaphoreHandle_t xResultMutex; /* 처리 결과 보호 */
static float lastAverage = 0.0f;
static float lastPeakV = 0.0f;
/* ADC DMA 완료 콜백 (HAL 인터럽트 컨텍스트) */
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc) {
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
/* DMA 전송 완료: Task에 처리 신호 전달 */
xSemaphoreGiveFromISR(xAdcDoneSemaphore, &xHigherPriorityTaskWoken);
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
/* ADC 데이터 처리 Task */
void vAdcProcessTask(void *pvParameters) {
while(1) {
/* DMA 완료 인터럽트 대기 */
xSemaphoreTake(xAdcDoneSemaphore, portMAX_DELAY);
/* 평균 및 피크 전압 계산 */
uint32_t sum = 0;
uint16_t peak = 0;
for(uint16_t i = 0; i < ADC_SAMPLE_COUNT; i++) {
sum += adcBuffer[i];
if(adcBuffer[i] > peak) peak = adcBuffer[i];
}
float average = (float)sum / ADC_SAMPLE_COUNT;
float peakV = peak * 3.3f / 4095.0f;
float avgV = average * 3.3f / 4095.0f;
/* 결과 저장: Mutex 보호 */
xSemaphoreTake(xResultMutex, portMAX_DELAY);
lastAverage = avgV;
lastPeakV = peakV;
xSemaphoreGive(xResultMutex);
printf("[ADC] 평균: %.3fV, 피크: %.3fV\n", avgV, peakV);
/* 다음 DMA 전송 시작 */
/* HAL_ADC_Start_DMA(&hadc1, (uint32_t*)adcBuffer, ADC_SAMPLE_COUNT); */
}
}
/* 결과 표시 Task */
void vDisplayTask(void *pvParameters) {
while(1) {
vTaskDelay(pdMS_TO_TICKS(1000));
xSemaphoreTake(xResultMutex, portMAX_DELAY);
printf("[Display] 최신 평균: %.3fV, 피크: %.3fV\n",
lastAverage, lastPeakV);
xSemaphoreGive(xResultMutex);
}
}
int main(void) {
xAdcDoneSemaphore = xSemaphoreCreateBinary();
xResultMutex = xSemaphoreCreateMutex();
configASSERT(xAdcDoneSemaphore != NULL);
configASSERT(xResultMutex != NULL);
printf("=== ADC DMA Binary Semaphore 동기화 실습 ===\n\n");
xTaskCreate(vAdcProcessTask, "AdcProc", 256, NULL, 3, NULL);
xTaskCreate(vDisplayTask, "Display", 256, NULL, 1, NULL);
/* ADC DMA 첫 번째 전송 시작 */
/* HAL_ADC_Start_DMA(&hadc1, (uint32_t*)adcBuffer, ADC_SAMPLE_COUNT); */
vTaskStartScheduler();
while(1);
}
ADC 데이터 수집(Binary Semaphore), 처리 큐(Counting Semaphore), 결과 보호(Mutex)를 조합한 완전한 파이프라인을 구현합니다.
#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"
#include <stdio.h>
#include <math.h>
#define RAW_BUF_SIZE 4
#define RESULT_BUF_SIZE 8
/* 동기화 객체 */
SemaphoreHandle_t xAdcTrigger; /* Binary: ADC 트리거 */
SemaphoreHandle_t xRawDataAvailable; /* Counting: 처리할 원시 데이터 수 */
SemaphoreHandle_t xRawDataSpace; /* Counting: 원시 데이터 버퍼 빈 슬롯 */
SemaphoreHandle_t xResultMutex; /* Mutex: 결과 버퍼 보호 */
/* 버퍼 */
static uint16_t rawBuffer[RAW_BUF_SIZE];
static float resultBuffer[RESULT_BUF_SIZE];
static uint8_t rawWriteIdx = 0;
static uint8_t rawReadIdx = 0;
static uint8_t resultIdx = 0;
/* ADC 트리거 시뮬레이션 Task (실제: 타이머 ISR) */
void vAdcTriggerTask(void *pvParameters) {
uint16_t adcVal = 0;
while(1) {
vTaskDelay(pdMS_TO_TICKS(100));
adcVal = (uint16_t)(2048 + (xTaskGetTickCount() % 512));
/* 원시 버퍼 빈 슬롯 확보 */
if(xSemaphoreTake(xRawDataSpace, pdMS_TO_TICKS(10)) == pdTRUE) {
rawBuffer[rawWriteIdx] = adcVal;
rawWriteIdx = (rawWriteIdx + 1) % RAW_BUF_SIZE;
xSemaphoreGive(xRawDataAvailable);
/* 수집 완료 신호 */
xSemaphoreGive(xAdcTrigger);
}
}
}
/* 데이터 처리 Task */
void vProcessTask(void *pvParameters) {
while(1) {
/* 원시 데이터 수신 대기 */
xSemaphoreTake(xAdcTrigger, portMAX_DELAY);
if(xSemaphoreTake(xRawDataAvailable, pdMS_TO_TICKS(50)) == pdTRUE) {
uint16_t raw = rawBuffer[rawReadIdx];
rawReadIdx = (rawReadIdx + 1) % RAW_BUF_SIZE;
xSemaphoreGive(xRawDataSpace);
/* 전압 변환 */
float voltage = raw * 3.3f / 4095.0f;
/* 결과 저장 */
xSemaphoreTake(xResultMutex, portMAX_DELAY);
resultBuffer[resultIdx % RESULT_BUF_SIZE] = voltage;
resultIdx++;
xSemaphoreGive(xResultMutex);
printf("[Process] Raw: %u -> %.3fV\n", raw, voltage);
}
}
}
/* 통계 출력 Task */
void vStatsTask(void *pvParameters) {
while(1) {
vTaskDelay(pdMS_TO_TICKS(3000));
xSemaphoreTake(xResultMutex, portMAX_DELAY);
uint8_t count = (resultIdx < RESULT_BUF_SIZE)
? resultIdx
: RESULT_BUF_SIZE;
if(count == 0) {
xSemaphoreGive(xResultMutex);
continue;
}
float sum = 0.0f;
float minV = 3.3f, maxV = 0.0f;
for(uint8_t i = 0; i < count; i++) {
sum += resultBuffer[i];
if(resultBuffer[i] < minV) minV = resultBuffer[i];
if(resultBuffer[i] > maxV) maxV = resultBuffer[i];
}
xSemaphoreGive(xResultMutex);
printf("\n--- 통계 (최근 %u 샘플) ---\n", count);
printf(" 평균: %.3fV, 최솟값: %.3fV, 최댓값: %.3fV\n\n",
sum / count, minV, maxV);
}
}
int main(void) {
xAdcTrigger = xSemaphoreCreateBinary();
xRawDataAvailable = xSemaphoreCreateCounting(RAW_BUF_SIZE, 0);
xRawDataSpace = xSemaphoreCreateCounting(RAW_BUF_SIZE, RAW_BUF_SIZE);
xResultMutex = xSemaphoreCreateMutex();
configASSERT(xAdcTrigger != NULL);
configASSERT(xRawDataAvailable != NULL);
configASSERT(xRawDataSpace != NULL);
configASSERT(xResultMutex != NULL);
printf("=== 데이터 수집 파이프라인 종합 실습 ===\n\n");
xTaskCreate(vAdcTriggerTask, "AdcTrig", 256, NULL, 3, NULL);
xTaskCreate(vProcessTask, "Process", 256, NULL, 2, NULL);
xTaskCreate(vStatsTask, "Stats", 256, NULL, 1, NULL);
vTaskStartScheduler();
while(1);
}
/*
* 증상: 생성 직후 xSemaphoreTake()가 즉시 성공함 (차단 없음)
* 원인: xSemaphoreCreateBinary() 대신 xSemaphoreCreateMutex()를 사용하거나
* 초기 상태를 잘못 이해한 경우
*
* Binary Semaphore: 초기 상태 = 0 (unavailable) -> 최초 Take는 차단됨
* Mutex: 초기 상태 = 1 (available) -> 최초 Take는 즉시 성공
*/
/* 이벤트 동기화에 Mutex를 잘못 사용한 예 */
SemaphoreHandle_t xWrongSem = xSemaphoreCreateMutex();
/* 생성 직후 Take가 차단 없이 성공 -> 의도하지 않은 동작 */
xSemaphoreTake(xWrongSem, portMAX_DELAY);
/* 올바른 Binary Semaphore 사용 */
SemaphoreHandle_t xCorrectSem = xSemaphoreCreateBinary();
/* 생성 직후 Take는 Give 호출 전까지 차단됨 */
xSemaphoreTake(xCorrectSem, portMAX_DELAY); /* ISR Give 대기 */
/*
* 증상: xSemaphoreGive()가 pdFALSE를 반환
* 원인: 세마포어 값이 이미 최대값에 도달한 상태에서 Give 호출
*
* 자원 풀 패턴에서 Take 없이 Give를 중복 호출하거나,
* Take와 Give 횟수가 맞지 않을 때 발생
*/
#define MAX_SLOTS 4
SemaphoreHandle_t xSlotSem = xSemaphoreCreateCounting(MAX_SLOTS, MAX_SLOTS);
/* 오류: Take 없이 Give 호출 -> 최대값 초과 시도 */
BaseType_t result = xSemaphoreGive(xSlotSem); /* pdFALSE 반환 */
if(result == pdFALSE) {
printf("[오류] 세마포어 최대값 초과 Give 시도\n");
}
/*
* 진단: Take/Give 호출 횟수를 전역 카운터로 추적하여
* 불균형 여부를 로그로 확인
*/
/*
* 증상: 인터럽트 발생 시 HardFault 또는 시스템 재시작
* 원인: ISR 내에서 FreeRTOS 일반 API 호출 (컨텍스트 전환 불가 컨텍스트에서 차단 시도)
*/
/* 오류: ISR에서 일반 API 사용 */
void TIM2_IRQHandler(void) {
xSemaphoreGive(xTimerSemaphore); /* 오류: ISR에서 사용 불가 */
}
/* 올바름: FromISR 접미사가 붙은 전용 API 사용 */
void TIM2_IRQHandler(void) {
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
xSemaphoreGiveFromISR(xTimerSemaphore, &xHigherPriorityTaskWoken);
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
/*
* 증상: xSemaphoreCreateCounting() 호출 시 NULL 반환
* 원인: FreeRTOSConfig.h에 configUSE_COUNTING_SEMAPHORES가 설정되지 않음
*/
/* FreeRTOSConfig.h 확인 */
#define configUSE_COUNTING_SEMAPHORES 1 /* 반드시 1로 설정 */
/*
* 추가 확인: 힙 메모리 부족으로 NULL이 반환될 수도 있으므로
* configTOTAL_HEAP_SIZE 값을 점검
*/
configASSERT(xCountingSemaphore != NULL); /* 생성 직후 반드시 검증 */
Binary Semaphore
xSemaphoreGiveFromISR() 및 portYIELD_FROM_ISR() 사용Counting Semaphore
configUSE_COUNTING_SEMAPHORES=1 필수사용 시나리오 구분
버퍼 동기화 설계 원칙
xItemsAvailable(초기 0)과 xSpaceAvailable(초기 최대값)을 쌍으로 사용| 개념 | 설명 |
|---|---|
| Binary Semaphore 초기 상태 | unavailable (0), 최초 Take는 Give 이후 성공 |
| Counting Semaphore 초기값 | 용도에 따라 0 또는 최대값으로 설정 |
| xSemaphoreGiveFromISR() | ISR에서 세마포어 Give 시 사용하는 전용 함수 |
| portYIELD_FROM_ISR() | ISR 종료 전 컨텍스트 전환 요청 함수 |
| uxSemaphoreGetCount() | 현재 세마포어 값 조회 함수 |
| 이벤트 카운팅 패턴 | 초기값 0, ISR에서 Give, Task에서 Take |
| 자원 풀 패턴 | 초기값 = 최대값, 자원 사용 전 Take, 반납 시 Give |
| 생산자-소비자 패턴 | xItemsAvailable + xSpaceAvailable 쌍으로 구성 |
1ms 주기 타이머 인터럽트를 Binary Semaphore로 Task에 전달하고, Counting Semaphore로 1초 내 이벤트 누적 수를 측정하십시오.
요구사항:
고정 크기 메모리 블록 N개를 Counting Semaphore로 관리하는 간단한 메모리 풀을 구현하십시오.
요구사항:
pool_alloc(): Counting Semaphore Take 후 사용 가능한 블록 주소 반환pool_free(): 블록 반납 후 Counting Semaphore GiveADC DMA를 핑퐁(Ping-Pong) 이중 버퍼로 운용하고, 각 버퍼 완료 시 Binary Semaphore를 통해 처리 Task에 신호를 전달하는 구조를 구현하십시오.
요구사항:
/*
* 증상: 생산자와 소비자가 모두 차단된 상태로 진행 불가
* 원인: xSpaceAvailable과 xItemsAvailable의 초기값 또는 최대값 설정 오류
*
* 점검 순서:
* 1. xSpaceAvailable 초기값 = BUFFER_SIZE 인지 확인
* 2. xItemsAvailable 초기값 = 0 인지 확인
* 3. 생산 후 xItemsAvailable Give, 소비 후 xSpaceAvailable Give 순서 확인
*/
/*
* 증상: ISR의 xSemaphoreGiveFromISR() 반환값이 pdFALSE
* 원인: Task의 처리 속도보다 ISR 발생 빈도가 높아 세마포어가 포화
* 해결:
* - 최대값을 더 크게 설정하거나
* - Task 우선순위를 높이거나
* - Queue를 사용하여 데이터 자체를 전달
*/
UBaseType_t uxMaxCount = 32; /* 처리 지연을 감안하여 여유 있게 설정 */
xEventSemaphore = xSemaphoreCreateCounting(uxMaxCount, 0);
/*
* 증상: 고우선순위 Task가 ISR 종료 후 즉시 실행되지 않고 다음 틱까지 지연
* 원인: portYIELD_FROM_ISR() 호출 누락
*/
/* 오류: 컨텍스트 전환 요청 없음 */
void DMA1_IRQHandler(void) {
BaseType_t xWoken = pdFALSE;
xSemaphoreGiveFromISR(xDmaDoneSem, &xWoken);
/* portYIELD_FROM_ISR(xWoken) 누락 */
}
/* 올바름 */
void DMA1_IRQHandler(void) {
BaseType_t xWoken = pdFALSE;
xSemaphoreGiveFromISR(xDmaDoneSem, &xWoken);
portYIELD_FROM_ISR(xWoken); /* 반드시 포함 */
}