xm_api_user_custom.h — Total Data Packet 안 28바이트 사용자 슬롯 API¶
대상 헤더:
XM_FW/XM_API/xm_api_user_custom.h(Rev1.1 · Rev2.0 헤더 동일 — 기능 차이 없음, 파일 상단@date주석만 하루 차이) 📚 관련 개념 문서: 05. USB 시리얼 통신 (Total Data Packet 0x20 / User Custom Channel 0xF0~0xFE 구분), 02. KIT H10 제어 + 데이터 (Control_Setup/Control_LoopIPO 주기) 🧪 관련 예제: 이 API를 직접 호출하는 예제는 아직 없습니다 (v2.2.0 부터 들어 있는 API). 같은 Total Data Packet(0x20) 을 다루는 Ex.09 CDC Stream 을 먼저 읽으면 이 슬롯이 어떤 스트림에 실리는지 이해하기 쉽습니다.
언제 사용하나¶
XM10 은 Control_Loop() 가 끝날 때마다 365바이트짜리 Total Data Packet(Module ID 0x20)을 1kHz 로 USB 에 자동 전송합니다. 이 패킷의 마지막 28바이트는 User_Custom 영역으로, 필터링한 EMG 신호나 FSM 상태 같은 사용자 값을 그대로 실을 수 있게 비워 둔 슬롯입니다. 본 헤더의 함수로 이 28바이트를 채워 두기만 하면, 별도의 전송 코드 없이 PhAI Studio 실시간 그래프와 녹화 파일에 자동으로 함께 기록됩니다.
이름이 비슷한 User Custom Channel (0xF0~0xFE, XM_SetUsbCustomMeta/XM_SendUsbDataWithId) 과는 다른 기능이니 주의하세요. 그쪽은 패킷 크기와 개수를 자유롭게 늘릴 수 있는 별도 채널이고, 본 API 는 이미 항상 전송되는 0x20 패킷 안의 고정된 28바이트에 값만 채워 넣는 훨씬 가벼운 방법입니다. 슬롯 4개(float) + 4개(int16) + 16비트(flags) + 2개(uint8) 만으로 충분하다면 이 API 를, 더 크거나 가변적인 데이터를 보내야 한다면 05번 문서의 커스텀 채널을 사용하세요. (PhAI Studio 는 아직 개발 중이라, 0xF0~0xFE 채널은 우선 xm10 도구로 보고 저장하세요.)
기본 사용 흐름¶
void Control_Setup(void) {
/* 따로 등록할 것이 없습니다 — 슬롯 이름은 정해져 있습니다 (아래 참고). */
}
void Control_Loop(void) {
XM_UserCustom_SetFloat(0, envelope_uV); // user_f[0]
XM_UserCustom_SetI16 (0, pf3_raw); // user_i16[0]
XM_UserCustom_SetFlag (0, is_fsm_active); // user_flags 의 bit0
}
이 28바이트 슬롯의 이름은 정해져 있습니다 — user_f[0]~user_f[3], user_i16[0]~user_i16[3], user_flags, user_u8[0]~user_u8[1]. PhAI Studio 그래프에는 이 이름으로 나타납니다. xm10 도구는 0x20 을 그래프로 그리지 않고(Show 0x20 tab 을 켜면 이 이름이 5 Hz 표로 보입니다), 0x20 CSV 를 뽑을 때 열 이름으로 씁니다. 이름을 XM_SetUsbCustomMeta 로 바꿀 수는 없습니다.
함수 목록¶
| 함수 | 한 줄 설명 |
|---|---|
XM_UserCustom_SetFloat |
Float 슬롯(4개) 중 하나에 값을 기록합니다 |
XM_UserCustom_SetI16 |
Int16 슬롯(4개) 중 하나에 값을 기록합니다 |
XM_UserCustom_SetU8 |
uint8 슬롯(2개) 중 하나에 값을 기록합니다 |
XM_UserCustom_SetFlags |
16비트 flags 워드 전체를 한 번에 덮어씁니다 |
XM_UserCustom_SetFlag |
flags 중 비트 하나만 set/clear 합니다 |
XM_UserCustom_Reset |
User_Custom 슬롯 전체를 0으로 초기화합니다 |
XM_UserCustom_GetBlock |
현재 User_Custom 블록 값을 한 번에 스냅샷으로 읽어옵니다 |
내부 전용([Internal]) 함수는 없습니다 — 7개 함수 모두 공개 API 입니다. 다만 XM_UserCustom_GetBlock 은 시스템이 매 tick 자동으로 호출하므로 일반 사용자가 직접 호출할 일은 거의 없습니다 (아래 상세 참고).
함수 상세¶
XM_UserCustom_SetFloat¶
user_f[] 슬롯 중 하나에 float 값을 기록합니다. 필터링/가공된 신호(예: EMG envelope)를 실어 보낼 때 사용합니다.
| 이름 | 타입 | 설명 |
|---|---|---|
idx |
uint8_t |
슬롯 인덱스 (0 ~ XM_USER_CUSTOM_FLOAT_COUNT-1 = 0~3). 범위를 벗어나면 silent ignore |
value |
float |
기록할 값 |
반환값: 없음 (void)
⚠️ 호출 컨텍스트: 기다리지 않고 바로 돌아오는(non-blocking) 함수입니다. Control_Setup()/Control_Loop() 에서 호출하세요. ISR 에서는 호출하지 마세요.
void Control_Loop(void) {
float envelope_uV = ComputeEmgEnvelope();
XM_UserCustom_SetFloat(0, envelope_uV); // user_f[0] 에 기록
}
참고: XM_UserCustom_GetBlock 으로 되읽기 가능 · 슬롯 레이아웃 표
XM_UserCustom_SetI16¶
user_i16[] 슬롯 중 하나에 int16 값을 기록합니다. raw ADC 값이나 카운터처럼 정수로 충분한 값에 사용합니다.
| 이름 | 타입 | 설명 |
|---|---|---|
idx |
uint8_t |
슬롯 인덱스 (0 ~ XM_USER_CUSTOM_I16_COUNT-1 = 0~3). 범위를 벗어나면 silent ignore |
value |
int16_t |
기록할 값 |
반환값: 없음 (void)
⚠️ 호출 컨텍스트: XM_UserCustom_SetFloat 과 동일 — non-blocking, Control_Setup()/Control_Loop() 컨텍스트 기준.
참고: XM_UserCustom_SetU8 (더 작은 범위면 uint8 슬롯 고려)
XM_UserCustom_SetU8¶
user_u8[] 슬롯 중 하나에 uint8 값을 기록합니다. FSM state 처럼 작은 enum 값에 적합합니다.
| 이름 | 타입 | 설명 |
|---|---|---|
idx |
uint8_t |
슬롯 인덱스 (0 ~ XM_USER_CUSTOM_U8_COUNT-1 = 0~1). 범위를 벗어나면 silent ignore |
value |
uint8_t |
기록할 값 |
반환값: 없음 (void)
⚠️ 호출 컨텍스트: XM_UserCustom_SetFloat 과 동일 — non-blocking, Control_Setup()/Control_Loop() 컨텍스트 기준.
참고: XM_UserCustom_SetFlag (bool 하나만 필요하면 flags 비트가 더 저렴)
XM_UserCustom_SetFlags¶
user_flags 16비트 워드 전체를 한 번에 덮어씁니다. 여러 비트를 동시에 바꿀 때 SetFlag 를 여러 번 호출하는 것보다 효율적입니다.
| 이름 | 타입 | 설명 |
|---|---|---|
flags |
uint16_t |
새로 기록할 16비트 워드 (기존 값 전부 덮어씀) |
반환값: 없음 (void)
⚠️ 호출 컨텍스트: XM_UserCustom_SetFloat 과 동일 — non-blocking, Control_Setup()/Control_Loop() 컨텍스트 기준.
참고: XM_UserCustom_SetFlag (비트 하나만 바꿀 때)
XM_UserCustom_SetFlag¶
user_flags 중 비트 하나만 set/clear 합니다. 다른 비트는 그대로 유지됩니다.
| 이름 | 타입 | 설명 |
|---|---|---|
bit |
uint8_t |
비트 인덱스 (0 ~ XM_USER_CUSTOM_FLAG_COUNT-1 = 0~15). 범위를 벗어나면 silent ignore |
value |
bool |
true = 1 로 set, false = 0 으로 clear |
반환값: 없음 (void)
⚠️ 호출 컨텍스트: XM_UserCustom_SetFloat 과 동일 — non-blocking, Control_Setup()/Control_Loop() 컨텍스트 기준.
참고: XM_UserCustom_SetFlags (여러 비트를 한 번에 바꿀 때)
XM_UserCustom_Reset¶
User_Custom 슬롯 전체(float 4개, int16 4개, flags, uint8 2개)를 0으로 초기화합니다.
파라미터 없음.
반환값: 없음 (void)
⚠️ 호출 컨텍스트: 부팅 시 시스템이 자동으로 1회 호출하므로 일반 사용자는 보통 호출할 필요가 없습니다. 값을 명시적으로 리셋하고 싶을 때만 Control_Setup()/Control_Loop() 컨텍스트 기준으로 호출하세요.
참고: 타입/매크로 (전체 슬롯 구성)
XM_UserCustom_GetBlock¶
현재 User_Custom 블록(28바이트)의 값을 out 이 가리키는 버퍼로 한 번에 복사합니다.
| 이름 | 타입 | 설명 |
|---|---|---|
out |
XM_UserCustomBlock_t* |
28바이트 결과를 받을 출력 버퍼. NULL 전달 금지 |
반환값: 없음 (void) — 결과는 out 포인터를 통해 반환됩니다.
⚠️ 호출 컨텍스트: 시스템이 매 tick 자동으로 호출해 Total Data Packet 을 채우는 함수이며, 일반 사용자 코드에서 직접 호출할 일은 거의 없습니다. 값을 직접 확인하고 싶을 때만 Control_Setup()/Control_Loop() 컨텍스트 기준으로 호출하세요.
out 에는 NULL 이 아닌 유효한 버퍼를 항상 넘기세요.
XM_UserCustomBlock_t snapshot;
XM_UserCustom_GetBlock(&snapshot);
char buf[64];
snprintf(buf, sizeof(buf), "f0=%.2f, flags=0x%04X\r\n", snapshot.f[0], snapshot.flags);
XM_SendUsbDebugMessage(buf); /* 터미널로 볼 때만 쓰세요 — 스트림을 받는 중이면 패킷 1개가 함께 사라집니다 */
참고: XM_UserCustomBlock_t (필드 레이아웃)
타입 / 매크로¶
XM_UserCustomBlock_t¶
Total Data Packet(365B, Module ID 0x20) 끝의 28바이트 User_Custom 영역을 그대로 거울처럼 옮겨 담는 구조체입니다. XM_UserCustom_GetBlock() 의 출력 타입으로 쓰입니다.
typedef struct {
float f[XM_USER_CUSTOM_FLOAT_COUNT]; /**< 16B */
int16_t i16[XM_USER_CUSTOM_I16_COUNT]; /**< 8B */
uint16_t flags; /**< 2B */
uint8_t u8[XM_USER_CUSTOM_U8_COUNT]; /**< 2B */
} XM_UserCustomBlock_t;
| 필드 | 타입 | 크기 | Total Data Packet 오프셋 | 대응 함수 |
|---|---|---|---|---|
f[4] |
float |
16B | 337 ~ 352 | XM_UserCustom_SetFloat |
i16[4] |
int16_t |
8B | 353 ~ 360 | XM_UserCustom_SetI16 |
flags |
uint16_t |
2B | 361 ~ 362 | XM_UserCustom_SetFlags / XM_UserCustom_SetFlag |
u8[2] |
uint8_t |
2B | 363 ~ 364 | XM_UserCustom_SetU8 |
위 오프셋은
user_f/user_i16/user_flags/user_u8순서대로 놓인 값입니다. 총 28바이트로, 365바이트 Total Data Packet 의 마지막 구간(offset 337~364)에 해당합니다.XM_UserCustomBlock_t자체에는packed속성이 없지만, 필드 순서(float→int16→uint16→uint8) 상 컴파일러의 자연 정렬만으로도 패딩 없이 정확히 28바이트가 됩니다.
매크로¶
| 매크로 | 값 | 의미 |
|---|---|---|
XM_USER_CUSTOM_FLOAT_COUNT |
4U |
user_f[] 슬롯 수 |
XM_USER_CUSTOM_I16_COUNT |
4U |
user_i16[] 슬롯 수 |
XM_USER_CUSTOM_U8_COUNT |
2U |
user_u8[] 슬롯 수 |
XM_USER_CUSTOM_FLAG_COUNT |
16U |
user_flags 비트 수 |
인덱스/비트 하드코딩 대신 이 매크로로 반복문 상한을 잡으면, 이후 SDK 버전에서 슬롯 수가 바뀌어도 코드를 그대로 재사용할 수 있습니다.