기술 가이드

FreeRTOS 주기 작업: xTaskDelayUntil·지연 누적·마감 초과 확인

센서 태스크의 상대 지연과 고정 주기 차이를 FreeRTOS 공식 API와 호스트 C 계산 예제로 확인합니다.

목차

센서 수집 태스크를 10틱마다 실행하려는데, 반복문 끝에서 vTaskDelay(10)을 부르면 센서 처리 시간까지 주기에 더해집니다. 처리 시간이 매번 달라지면 시작 시각이 계속 밀립니다. FreeRTOS의 xTaskDelayUntil() 공식 API는 이전 기준 시각에서 일정 간격을 더해 다음 깨움 시각을 정합니다. 이 글은 두 방법의 차이와 작업이 주기보다 오래 걸릴 때의 판단을 설명합니다.

상대 지연과 고정 주기

vTaskDelay()의 대기 시간은 호출한 순간부터 시작합니다. 예를 들어 10틱 주기에 첫 센서 읽기가 2틱 걸리면 다음 시작은 대략 12틱입니다. 다음 읽기가 7틱 걸리면 그다음 시작은 29틱입니다. 스케줄러의 다른 태스크와 인터럽트가 끼면 실제 시작은 더 늦을 수 있습니다.

xTaskDelayUntil()에는 기준 틱과 주기 틱을 전달합니다. 기준값을 처음에 xTaskGetTickCount()로 초기화하고, 이후 같은 변수의 주소를 계속 넘깁니다. 0틱에 시작해 2틱을 썼다면 10틱까지 기다리고, 10틱에 시작해 7틱을 썼다면 20틱까지 기다리는 식입니다. API는 태스크를 지정 시각에 Ready 상태로 옮길 수 있지만, 높은 우선순위 태스크가 CPU를 쓰면 실행 시작은 늦어질 수 있습니다. 고정 주기 API가 하드웨어 수준의 정확한 실행 시각을 보장하지는 않습니다.

상황 vTaskDelay(10) xTaskDelayUntil(..., 10)
작업이 2틱 완료 후 10틱 대기 다음 기준 시각까지 약 8틱 대기
작업이 7틱 완료 후 10틱 대기 다음 기준 시각까지 약 3틱 대기
작업이 15틱 완료 후 10틱 대기 이미 지난 마감 시각에는 대기하지 않음

FreeRTOS 태스크에 적용하기

다음은 구조를 보여 주는 코드입니다. 실제 프로젝트에서는 FreeRTOS.h, task.h, 센서 드라이버, 오류 기록 함수를 포함하고 해당 포트에서 컴파일해야 합니다. pdMS_TO_TICKS(10)이 0이 되지 않도록 틱 속도를 확인하세요. INCLUDE_xTaskDelayUntil=1 설정도 필요합니다.

TickType_t last_wake = xTaskGetTickCount();
const TickType_t period = pdMS_TO_TICKS(10);
configASSERT(period > 0);
for (;;) {
    bool ok = read_sensor_once(); /* 자체 타임아웃을 가진 함수 */
    if (!ok) record_sensor_error();
    BaseType_t blocked = xTaskDelayUntil(&last_wake, period);
    if (blocked == pdFALSE) record_deadline_miss();
}

공식 API에 따르면 xTaskDelayUntil()은 실제로 대기한 경우 pdTRUE, 이미 다음 시각을 지나 대기하지 않은 경우 pdFALSE를 반환합니다. 센서 읽기가 15틱 걸린 것을 API가 자동으로 줄여 주지는 않습니다. 초과가 반복되면 센서 호출 시간과 스케줄러 지연을 측정하고 주기, 태스크 우선순위, 데이터 수집 방식을 다시 설계하세요. 오래 걸리는 I/O에는 별도 타임아웃을 둬야 합니다. 마감 초과를 단순히 vTaskDelay()로 감추면 누적 지연의 원인을 찾기 어렵습니다.

PC에서 계산 확인하기

아래 C 프로그램은 FreeRTOS 커널 없이 시간 계산만 모형화합니다. periodic_schedule.c로 저장하고 gcc -std=c11 -Wall -Wextra -Werror periodic_schedule.c -o periodic_schedule로 빌드하세요. Linux는 ./periodic_schedule, Windows PowerShell은 .\periodic_schedule.exe로 실행합니다. 실제 센서 처리와 우선순위 동작을 재현하는 시험은 아닙니다.

#include <inttypes.h>
#include <stdint.h>
#include <stdio.h>

/* Host model only: no FreeRTOS kernel or MCU is used. All values are ticks. */
int main(void) {
const uint64_t period = 10;
const uint64_t work[] = {2, 7, 15, 2};
uint64_t relative_start = 0;
uint64_t absolute_start = 0;
uint64_t deadline = 0;
unsigned missed = 0;

puts("sample relative absolute deadline missed");
for (unsigned i = 0; i < sizeof work / sizeof work[0]; ++i) {
printf("%u %" PRIu64 " %" PRIu64 " %" PRIu64 " %u\n",
i + 1, relative_start, absolute_start, deadline, missed);

/* vTaskDelay(period) begins the wait only after the work finishes. */
relative_start += work[i] + period;

/* Fixed release points: 10, 20, 30, 40 ... */
deadline += period;
const uint64_t finished = absolute_start + work[i];
if (finished > deadline) {
++missed;
absolute_start = finished; /* no negative sleep after an overrun */
} else {
absolute_start = deadline;
}
}
if (relative_start != 66 || absolute_start != 40 || missed != 1) {
return 1;
}
puts("PASS: relative drift, fixed releases, one overrun");
return 0;
}

예상 출력:

sample relative absolute deadline missed
1 0 0 0 0
2 12 10 10 0
3 29 20 20 0
4 54 35 30 1
PASS: relative drift, fixed releases, one overrun

상대 지연은 시작 시각이 0 → 12 → 29 → 54로 밀립니다. 고정 기준은 0 → 10 → 20으로 진행하지만 세 번째 작업이 15틱 걸려 30틱 마감을 넘으므로 네 번째는 35틱에 시작합니다. 출력의 missed=1은 모형에서 한 번 놓친 마감을 뜻합니다. 실행 환경의 실제 틱, 선점, 인터럽트 지연은 별도로 측정해야 합니다.

실기에 옮기기 전 점검

  1. 센서 처리 최악 시간과 주기, CPU 여유를 측정합니다. 평균만으로 주기를 정하지 않습니다.
  2. pdMS_TO_TICKS() 변환 결과가 0이 아닌지 확인하고, MCU 포트의 틱 폭과 래핑 동작을 해당 FreeRTOS 버전으로 시험합니다.
  3. pdFALSE 횟수와 실제 처리 시간을 함께 기록합니다. 반복 초과를 오류 예산으로 다룹니다.
  4. 태스크가 Ready가 된 시각과 실제 CPU 실행 시각을 구분합니다. 엄격한 샘플링 시각이 필요하면 하드웨어 타이머와 ISR에서 시각을 확보하는 설계를 검토합니다.

공식 참고: FreeRTOS xTaskDelayUntil() API, FreeRTOS 커널 task.h.

관련 글

댓글

첫 댓글을 남겨 보세요.

관리자 확인 후 게시됩니다.

처리 중입니다...

잠시만 기다려주세요.