xm_api_freertos.h — RTOS 보조 Task API¶
📄 대상 헤더:
XM10_SDK/Rev2.0/Extension_Module/XM_FW/XM_API/xm_api_freertos.h(Rev1.1 동일 경로에 바이트 단위로 동일한 파일이 있습니다 — 이 페이지에 Rev 전용 항목은 없습니다) 🧭 관련 개념 문서: 09. 보조 task + 데이터 공유 — task 우선순위 영역, 데이터 흐름, 공유변수 패턴 4가지를 먼저 읽어보세요. 🧰 관련 예제: Ex.38 Periodic_Background_Task · Ex.39 Task_Lifecycle · Ex.36 OnDevice_Kinesthetic_Learning (deprecated API → 신규 API 마이그레이션 사례)
Control_Loop() (1 kHz 제어 루프)를 방해하지 않고 별도의 보조 task 를 안전하게 만들고, 그 task 와 데이터를 주고받기 위한 API 입니다.
언제 사용하나¶
Control_Loop 안에서 FFT, 신경망 학습, 큰 행렬 연산처럼 무거운 계산을 그대로 돌리면 1 ms 주기가 깨져서 제어가 불안정해집니다. 이럴 때 XM_Task_CreateOneShot() / XM_Task_CreatePeriodic() 로 별도 RTOS task 를 만들어 계산을 분리하고, XM_Mutex_* 로 Control_Loop 와 안전하게 데이터를 주고받습니다.
- task 우선순위를 어떻게 고를지, Control_Loop 와 어떤 흐름으로 데이터를 주고받는지는 → 09. 보조 task + 데이터 공유 문서를 먼저 확인하세요.
- 이 페이지는 각 함수의 시그니처 / 파라미터 / 반환값 / 호출 컨텍스트만 다룹니다.
함수 목록¶
| 함수 | 한 줄 설명 |
|---|---|
XM_Task_CreateOneShot |
한 번 실행 후 자동 종료되는 task 생성 (예: NN 학습, FFT) |
XM_Task_IsComplete |
OneShot task 가 완료(return)됐는지 확인 |
XM_Task_CreatePeriodic |
주기적으로 실행되는 task 생성 (예: 100 Hz 로깅) |
XM_Task_Suspend |
Task 일시 중단 |
XM_Task_Resume |
중단된 task 재개 |
XM_Task_Delete |
Task 종료 + 메모리(heap) 해제 |
XM_Mutex_Create |
Mutex 생성 |
XM_Mutex_Lock |
Mutex 잠금 시도 |
XM_Mutex_Unlock |
Mutex 잠금 해제 |
XM_Mutex_Delete |
Mutex 삭제 + 메모리 해제 |
XM_Task_GetHeapFreeBytes |
현재 시스템 heap 잔량 조회 |
XM_Task_GetHeapMinEverBytes |
역대 최저 heap 잔량 조회 (누적 누수 감지) |
XM_Task_GetInstanceCount |
현재 내가 만든 task 개수 조회 |
XM_Task_GetBudgetRemainingBytes |
사용자 task heap budget 잔량 조회 |
XM_RTOS_PrintTaskList |
현재 동작 중인 task 목록 출력 (진단용) |
⚠️
XM_BgTask_Create/XM_BgTask_IsDone은 Deprecated 입니다 — 표에서 제외했습니다. 아래 Deprecated API 절을 참고하세요.
함수 상세¶
OneShot Task API¶
XM_Task_CreateOneShot¶
XmTaskHandle_t XM_Task_CreateOneShot(const char* name,
XmTaskOneShotFunc_t func,
void* arg,
XmTaskPrio_t prio_hint);
한 번 실행되고 함수가 return 하면 자동으로 종료되는 task 를 만듭니다. 신경망 학습, FFT 같이 시간이 걸리는 계산을 Control_Loop 밖에서 돌릴 때 사용합니다.
| 이름 | 타입 | 설명 |
|---|---|---|
name |
const char* |
Task 이름 (10자 이내 권장). NULL 이나 빈 문자열은 거부됩니다. 같은 이름의 중복은 검사하지 않으니 task 마다 다른 이름을 지어 주세요. |
func |
XmTaskOneShotFunc_t |
task 로 실행할 함수. return 하면 완료 처리됩니다. |
arg |
void* |
func 에 전달할 인자. static 변수나 heap 포인터만 넘기세요 — 호출한 함수의 지역(stack) 변수 주소는 task 가 실행되는 시점에 이미 사라졌을 수 있어 금지입니다. |
prio_hint |
XmTaskPrio_t |
우선순위 힌트. 특별한 이유가 없다면 XM_PRIO_BACKGROUND 를 사용하세요. |
반환값: 성공 시 유효한 XmTaskHandle_t 핸들, 거부되면 NULL. 거부돼도 메시지는 나오지 않으니 반환값을 꼭 확인하세요.
⚠️ 호출 컨텍스트: Control_Setup()/Control_Loop() 에서 호출하세요. ISR 에서는 호출하지 마세요. 동시에 최대
XM_TASK_MAX_INSTANCES(4개) 까지만 만들 수 있고, 완료된 task 도XM_Task_Delete로 지우기 전까지는 이 개수에 포함됩니다.
static XmTaskHandle_t s_heavy;
if (trigger && s_heavy == NULL) {
s_heavy = XM_Task_CreateOneShot("HeavyCalc", _HeavyCalc, NULL, XM_PRIO_BACKGROUND);
}
참고: XM_Task_IsComplete, XM_Task_Delete, Ex.39 task_lifecycle.c
XM_Task_IsComplete¶
OneShot task 의 함수 본체가 이미 return 해서 완료됐는지 확인합니다.
| 이름 | 타입 | 설명 |
|---|---|---|
handle |
XmTaskHandle_t |
XM_Task_CreateOneShot() 이 반환한 핸들 |
반환값: true — 함수가 return 하여 완료됨 / false — 아직 실행 중이거나 잘못된(또는 이미 삭제된) 핸들.
⚠️ 호출 컨텍스트: Control_Setup()/Control_Loop() 에서 호출하세요. ISR 에서는 호출하지 마세요.
if (s_heavy != NULL && XM_Task_IsComplete(s_heavy)) {
XM_Task_Delete(s_heavy);
s_heavy = NULL; /* dangling pointer 방지 — 반드시 NULL 대입 */
}
참고: XM_Task_CreateOneShot, XM_Task_Delete
Periodic Task API¶
XM_Task_CreatePeriodic¶
XmTaskHandle_t XM_Task_CreatePeriodic(const char* name,
XmTaskPeriodicFunc_t func,
uint32_t period_ms,
XmTaskPrio_t prio_hint);
지정한 주기마다 반복 호출되는 task 를 만듭니다. 100 Hz 로깅, 10 Hz UI 갱신처럼 Control_Loop 와 별도 주기로 계속 돌아야 하는 작업에 사용합니다.
| 이름 | 타입 | 설명 |
|---|---|---|
name |
const char* |
Task 이름 |
func |
XmTaskPeriodicFunc_t |
매 주기 호출될 함수. 인자가 없습니다 — Control_Loop 와는 공유 변수(volatile 또는 Mutex)로 데이터를 주고받으세요. |
period_ms |
uint32_t |
호출 주기 (ms). 1 ms 는 Control_Loop 전용이므로 최소 2 ms 이상을 권장합니다. |
prio_hint |
XmTaskPrio_t |
우선순위 힌트 |
반환값: 성공 시 유효한 핸들, 거부되면 NULL.
⚠️ 호출 컨텍스트: Control_Setup()/Control_Loop() 에서 호출하세요. ISR 에서는 호출하지 마세요.
static XmMutexHandle_t s_buf_mutex;
static XmTaskHandle_t s_summary_task;
void Control_Setup(void) {
s_buf_mutex = XM_Mutex_Create();
s_summary_task = XM_Task_CreatePeriodic("AdcSummary", _SummaryTask,
10U /* 10 ms = 100 Hz */,
XM_PRIO_BACKGROUND);
}
참고: 09. 보조 task + 데이터 공유 §3 공유변수 패턴, Ex.38 periodic_bg_task.c
Task Lifecycle API¶
XM_Task_Suspend¶
task 를 일시 중단합니다. XM_Task_Resume 을 호출하기 전까지 다시 실행되지 않습니다.
| 이름 | 타입 | 설명 |
|---|---|---|
handle |
XmTaskHandle_t |
중단할 task 핸들 |
반환값: 없음 (void).
⚠️ 호출 컨텍스트: Control_Setup()/Control_Loop() 에서 호출하세요. ISR 에서는 호출하지 마세요. 잘못된 핸들을 넘겨도 크래시 없이 무시됩니다(다른 API 와 동일한 방어 패턴).
참고: XM_Task_Resume
XM_Task_Resume¶
XM_Task_Suspend 로 멈춰둔 task 를 다시 실행시킵니다.
| 이름 | 타입 | 설명 |
|---|---|---|
handle |
XmTaskHandle_t |
재개할 task 핸들 |
반환값: 없음 (void).
⚠️ 호출 컨텍스트: Control_Setup()/Control_Loop() 에서 호출하세요. ISR 에서는 호출하지 마세요.
참고: XM_Task_Suspend
XM_Task_Delete¶
task 를 종료하고 관련 메모리(heap)를 회수합니다. task 를 하나 만들었으면 언젠가 반드시 Delete 로 짝을 맞춰야 heap 누수가 생기지 않습니다.
| 이름 | 타입 | 설명 |
|---|---|---|
handle |
XmTaskHandle_t |
삭제할 task 핸들 |
반환값: 없음 (void).
- OneShot:
XM_Task_IsComplete로 완료를 확인한 뒤 호출하는 것을 권장합니다. - Periodic: 완료라는 개념이 없으므로 언제든 호출해서 강제 종료할 수 있습니다.
- 자기 자신 삭제 금지: 현재 실행 중인(자기 자신의) task 를 스스로
Delete하려는 시도는 거부됩니다(헤더 명시 — task 함수 안에서XM_Task_Delete(자기_핸들)을 호출하지 마세요).
⚠️ 호출 컨텍스트: Control_Setup()/Control_Loop() 에서 호출하세요. ISR 에서는 호출하지 마세요.
if (s_heavy != NULL && XM_Task_IsComplete(s_heavy)) {
XM_Task_Delete(s_heavy);
s_heavy = NULL; /* 필수: dangling pointer 방지 */
}
참고: XM_Task_CreateOneShot, XM_Task_CreatePeriodic, Ex.39 task_lifecycle.c
Mutex API¶
XM_Mutex_Create¶
Mutex 를 하나 생성합니다. 배열/구조체처럼 여러 워드로 이루어진 데이터를 Control_Loop 와 보조 task 가 함께 읽고 쓸 때 이 Mutex 로 보호합니다.
파라미터: 없음.
반환값: 성공 시 유효한 XmMutexHandle_t, 실패 시 NULL.
⚠️ 호출 컨텍스트: Control_Setup()/Control_Loop() 에서 호출하세요. ISR 에서는 호출하지 마세요. 보통
Control_Setup()에서 한 번만 만들어 두고 계속 재사용합니다.
참고: XM_Mutex_Lock, 09. 보조 task + 데이터 공유 §3 공유변수 패턴
XM_Mutex_Lock¶
Mutex 잠금을 시도합니다.
| 이름 | 타입 | 설명 |
|---|---|---|
mutex |
XmMutexHandle_t |
잠글 Mutex 핸들 |
timeout_ms |
uint32_t |
잠금 대기 시간(ms). Control_Loop 안에서는 항상 0 을 사용하세요 (헤더 명시 — 절대 블로킹 금지, 1 ms 주기가 깨집니다). 보조 task 안에서도 0 을 권장합니다. |
반환값: true — 잠금 성공 / false — timeout 이거나 잘못된 핸들.
⚠️ 호출 컨텍스트: Control_Setup()/Control_Loop() 에서 호출하세요. ISR 에서는 호출하지 마세요.
timeout_ms > 0을 Control_Loop 안에서 쓰면 최악의 경우 그만큼 1 kHz 주기가 밀립니다 — 반드시0+ 실패 시 skip 패턴을 쓰세요.
/* Control_Loop — 1 kHz Writer, 항상 timeout=0 */
if (XM_Mutex_Lock(s_buf_mutex, 0U)) {
s_adc_buf[s_adc_idx] = XM_AnalogRead(XM_EXT_ADC_1);
s_adc_idx = (s_adc_idx + 1) % ADC_BUF_SIZE;
XM_Mutex_Unlock(s_buf_mutex);
}
/* 실패 시 이번 cycle 의 샘플은 그냥 건너뜁니다 (jitter 보존) */
참고: XM_Mutex_Unlock, Ex.38 periodic_bg_task.c
XM_Mutex_Unlock¶
Mutex 잠금을 해제합니다. 잠갔던 task 만 해제할 수 있습니다.
| 이름 | 타입 | 설명 |
|---|---|---|
mutex |
XmMutexHandle_t |
해제할 Mutex 핸들 |
반환값: 없음 (void).
⚠️ 호출 컨텍스트: Control_Setup()/Control_Loop() 에서 호출하세요. ISR 에서는 호출하지 마세요.
XM_Mutex_Lock()성공 후 early return 하기 전에 반드시Unlock을 호출하세요 — 누락하면 그 Mutex 는 영구히 잠긴 채로 남습니다.
참고: XM_Mutex_Lock
XM_Mutex_Delete¶
Mutex 를 삭제하고 메모리를 회수합니다.
| 이름 | 타입 | 설명 |
|---|---|---|
mutex |
XmMutexHandle_t |
삭제할 Mutex 핸들 |
반환값: 없음 (void).
⚠️ 호출 컨텍스트: Control_Setup()/Control_Loop() 에서 호출하세요. ISR 에서는 호출하지 마세요. 이 Mutex 를 사용하는 task 가 아직 남아있는 상태에서 삭제하면 안 됩니다 — 관련 task 를 먼저
XM_Task_Delete로 정리한 뒤 호출하세요.
참고: XM_Mutex_Create, XM_Task_Delete
HW 제약 진단 API¶
이 네 함수는 모두 파라미터가 없고, 현재 상태를 숫자 하나로 즉시 반환하는 단순 조회(getter) 함수입니다. 09. 보조 task + 데이터 공유 §4 에서 설명하는 사용자 task 한도(최대 4개 task, 32 KB heap budget)를 코드에서 직접 확인하고 싶을 때 사용합니다.
XM_Task_GetHeapFreeBytes¶
현재 시스템 heap 의 잔여 바이트 수를 반환합니다.
반환값: 잔여 heap (bytes).
⚠️ 호출 컨텍스트: Control_Setup()/Control_Loop() 에서 호출하세요. ISR 에서는 호출하지 마세요.
XM_Task_GetHeapMinEverBytes¶
시스템 heap 이 지금까지 가장 적게 남았던 시점의 잔량을 반환합니다. task 생성/삭제를 반복해도 이 값이 계속 줄어든다면 heap 누수(예: XM_Task_Delete 누락)를 의심할 수 있습니다.
반환값: 역대 최저 heap 잔량 (bytes).
⚠️ 호출 컨텍스트: Control_Setup()/Control_Loop() 에서 호출하세요. ISR 에서는 호출하지 마세요. Ex.39 는 버튼을 3회 눌러 OneShot task 생성/삭제를 반복한 뒤 이 값이 더 줄지 않는지로 누수 여부를 검증합니다.
참고: Ex.39 README.md (heap 누수 검증 시나리오)
XM_Task_GetInstanceCount¶
지금까지 만들고 아직 Delete 하지 않은(완료됐지만 Delete 전인 OneShot 포함) 사용자 task 개수를 반환합니다.
반환값: 현재 사용자 task 개수 (0 ~ XM_TASK_MAX_INSTANCES).
⚠️ 호출 컨텍스트: Control_Setup()/Control_Loop() 에서 호출하세요. ISR 에서는 호출하지 마세요. 이 값이 4에 도달하면 이후
XM_Task_CreateOneShot/CreatePeriodic은 모두NULL을 반환합니다 — 새 task 를 만들기 전에 확인해보면 원인 파악에 도움이 됩니다.
XM_Task_GetBudgetRemainingBytes¶
사용자 task 전용 heap budget(총 XM_TASK_HEAP_BUDGET_BYTES, 32 KB) 중 남은 양을 반환합니다.
반환값: budget 잔량 (bytes).
⚠️ 호출 컨텍스트: Control_Setup()/Control_Loop() 에서 호출하세요. ISR 에서는 호출하지 마세요. 이 값이 부족하면 stack 이 큰 task(예:
XM_PRIO_BACKGROUND기본 8 KB)의 생성이 거부될 수 있습니다.
진단 API¶
XM_RTOS_PrintTaskList¶
현재 동작 중인 task 목록을 출력합니다(on-demand 진단용).
파라미터: 없음. 반환값: 없음 (void).
⚠️ 현재 상태: 이 SDK 버전에서는 진단 출력 채널이 연결되어 있지 않아 호출해도 아무 동작이 없는 no-op stub 입니다(헤더 명시).
⚠️ 호출 컨텍스트(미리 대비): 지금은 stub 이라 어디서 불러도 안전하지만, 이 함수에는 "1 kHz Control_Loop 안에서 호출 금지(문자열 포맷팅 누적으로 100 µs 이상 걸릴 수 있음)"라는 제약이 있습니다. 그러니
Control_Setup()이나 버튼 이벤트 핸들러처럼 1 kHz 로 돌지 않는 곳에서만 호출하는 습관을 들이는 것을 권장합니다.
Deprecated API (사용 금지 권장)¶
v1.0 호환을 위해 남아있는 API 입니다. 새 코드에서는 사용하지 마세요. 아래 표만 남기고 함수 상세는 생략합니다.
| 함수 | 대체 API | 비고 |
|---|---|---|
XmBgTaskHandle_t XM_BgTask_Create(const char* name, XmBgTaskFunc_t func, void* arg, uint32_t stack_words) |
XM_Task_CreateOneShot(name, func, arg, XM_PRIO_BACKGROUND) |
컴파일러가 deprecated 경고를 냅니다. 완료 후 XM_Task_Delete 호출이 필수라는 점은 신규 API 와 동일합니다. |
bool XM_BgTask_IsDone(XmBgTaskHandle_t handle) |
XM_Task_IsComplete |
마찬가지로 deprecated 경고 대상입니다. |
Ex.36 (
OnDevice_Kinesthetic_Learning) 이 실제로XM_BgTask_Create→XM_Task_CreateOneShot마이그레이션을 거친 예시입니다 (소스 주석에[2026-05-13] XM_BgTask_Create → XM_Task_CreateOneShot 마이그레이션으로 기록되어 있습니다).
타입 정의¶
XmTaskPrio_t¶
Task 우선순위 힌트 enum 입니다. CMSIS-OS2 의 숫자 우선순위 값을 그대로 노출하는 대신, 의미 단위로 감싸서 제공합니다. 시스템 task 가 이미 점유한 숫자 영역(51~55)은 이 enum 에 존재하지 않습니다 — 실수로라도 시스템 task 와 우선순위가 겹칠 수 없습니다.
typedef enum {
XM_PRIO_IDLE = 8, /* osPriorityLow — 가장 낮은 우선순위 */
XM_PRIO_BACKGROUND = 24, /* osPriorityNormal — 권장 기본값 */
XM_PRIO_BELOW_CONTROL = 32, /* osPriorityAboveNormal — Control_Loop 보다 낮음 */
XM_PRIO_ABOVE_CONTROL = 40, /* osPriorityHigh — Control_Loop 보다 낮음 */
XM_PRIO_NEAR_REALTIME = 48, /* osPriorityRealtime — 주의: 모듈 연결 처리와 경합 가능 */
} XmTaskPrio_t;
| 값 | 숫자 | 기본 stack | 설명 |
|---|---|---|---|
XM_PRIO_IDLE |
8 | 1 KB | 가장 낮은 우선순위 |
XM_PRIO_BACKGROUND |
24 | 8 KB | 권장 기본값 |
XM_PRIO_BELOW_CONTROL |
32 | 4 KB | Control_Loop 보다는 낮음 |
XM_PRIO_ABOVE_CONTROL |
40 | 2 KB | Control_Loop 보다는 낮음 |
XM_PRIO_NEAR_REALTIME |
48 | 2 KB | ⚠️ 주의: 모듈 연결 처리와 경합할 수 있으니 남용하지 마세요. |
우선순위 영역이 시스템 전체에서 어떻게 배치되는지(51~55 가 왜 제외됐는지)는 09. 보조 task + 데이터 공유 §1~2 에서 전체 그림을 확인하세요.
핸들 타입¶
| 타입 | 정의 | 설명 |
|---|---|---|
XmTaskHandle_t |
typedef void* |
Task 핸들. NULL 이면 유효하지 않은 핸들입니다. |
XmMutexHandle_t |
typedef void* |
Mutex 핸들. NULL 이면 유효하지 않은 핸들입니다. |
함수 포인터 타입¶
| 타입 | 시그니처 | 설명 |
|---|---|---|
XmTaskOneShotFunc_t |
void (*)(void* arg) |
XM_Task_CreateOneShot 의 func 파라미터 타입 |
XmTaskPeriodicFunc_t |
void (*)(void) |
XM_Task_CreatePeriodic 의 func 파라미터 타입 (인자 없음 — 공유 변수로 데이터 전달) |
매크로¶
시스템 가드 상수¶
학생이 직접 바꿀 수 없는 값이지만, 코드에서 조건 분기(예: if (XM_Task_GetInstanceCount() < XM_TASK_MAX_INSTANCES)) 에 활용할 수 있도록 헤더에 그대로 노출되어 있습니다.
| 매크로 | 값 | 의미 |
|---|---|---|
XM_TASK_MAX_INSTANCES |
4U |
동시에 만들 수 있는 사용자 task 최대 개수 |
XM_TASK_HEAP_BUDGET_BYTES |
32768U (32 KB) |
사용자 task 전체가 쓸 수 있는 heap 총량 |
XM_TASK_STACK_MAX_WORDS |
2048U (8 KB) |
단일 task 최대 stack (word = 4 byte) |
XM_TASK_STACK_MIN_WORDS |
128U (512 B) |
단일 task 최소 stack |
prio_hint 별 기본 stack¶
XM_Task_CreateOneShot/CreatePeriodic 호출 시 stack 크기를 직접 지정하지 않고, prio_hint 에 따라 아래 값이 자동으로 적용됩니다.
| 매크로 | 값 (words) | 바이트 환산 | 대응 prio_hint |
|---|---|---|---|
XM_TASK_STACK_IDLE_WORDS |
256U |
1 KB | XM_PRIO_IDLE |
XM_TASK_STACK_BACKGROUND_WORDS |
2048U |
8 KB | XM_PRIO_BACKGROUND |
XM_TASK_STACK_BELOW_WORDS |
1024U |
4 KB | XM_PRIO_BELOW_CONTROL |
XM_TASK_STACK_ABOVE_WORDS |
512U |
2 KB | XM_PRIO_ABOVE_CONTROL |
XM_TASK_STACK_NRT_WORDS |
512U |
2 KB | XM_PRIO_NEAR_REALTIME |
XM_TASK_STACK_BACKGROUND_WORDS(8 KB) 는 헤더 주석에 "Ex.36 NN_Train 실측치" 로 기록되어 있습니다 — 일반적인 알고리즘 계산에도 충분한 크기입니다.
흔한 실수¶
자세한 pitfalls 표는 09. 보조 task + 데이터 공유 §6 에 정리되어 있습니다. 이 헤더와 직접 관련된 것만 요약하면:
| 실수 | 결과 | 대응 |
|---|---|---|
Control_Loop 안에서 XM_Mutex_Lock(m, 0 보다 큰 값) |
1 ms 주기 깨짐 | 항상 timeout_ms = 0, 실패 시 skip |
XM_Mutex_Lock 성공 후 XM_Mutex_Unlock 누락 |
mutex 영구 점유 | early return 전에 반드시 Unlock |
XM_Task_Delete 후 handle 재사용 |
이후 호출이 모두 false/NULL 반환 |
Delete 직후 handle = NULL 대입 |
XM_Task_Delete 자체를 누락 |
4개 한도 도달 → 이후 Create 가 NULL |
OneShot 은 IsComplete → Delete 사이클 지키기 |
task 함수 안에서 자기 자신을 XM_Task_Delete |
거부됨(no-op) | 외부(Control_Loop 등)에서 IsComplete 확인 후 Delete |
관련 문서¶
- 09. 보조 task + 데이터 공유 — 우선순위 영역, 데이터 흐름, 공유변수 패턴
- Ex.38 Periodic_Background_Task
- Ex.39 Task_Lifecycle
- Ex.36 OnDevice_Kinesthetic_Learning