xm_api_logging.h — USB-CDC Struct Logging¶
🟢 Rev 2.0 only (new in v2.9.0) — This header and
xm_api_logging_raw.hship only in the XM10 Rev 2.0 SDK. The Rev 1.1 SDK (XM10_SDK/Rev1.1/) has neither header, so including them in a Rev 1.1 project fails to build. To check your board revision, see the board revision comparison.
- Target header:
XM_FW/XM_API/xm_api_logging.h·XM_FW/XM_API/xm_api_logging_raw.h- Related concept doc: 10. Struct Logging
- Related example: Ex.44 CDC Struct Logging — sends two structs to the PC with names, units and types
When to use it¶
Use this API when you send your own struct to the PC together with its field names, units and types. For the big picture — how it differs from the existing way (XM_SetUsbCustomMeta + XM_SendUsbDataWithId), the limits, what you see on the PC, and common mistakes — we recommend reading 10. Struct Logging first. This page only covers the signatures, parameters and return values of every macro, function and type declared in the header.
xm_api.h pulls in this header, so the names XM_LOG_*, XM_FIELD* and XM_Log_* belong to the SDK. Do not create names of your own that match them.
include¶
#include "xm_api.h" // Also pulls in xm_api_logging.h
#include "xm_api_logging_raw.h" // Always include this in a .c file that uses the XM_LOG_* macros
xm_api.h includes xm_api_logging.h but not xm_api_logging_raw.h. The prototypes of the functions that the XM_LOG_REGISTER and XM_LOG_SEND macros call are in that header, so if it is missing you get an implicit declaration of function build error at the macro line.
Basic usage flow¶
#include "xm_api.h"
#include "xm_api_logging_raw.h"
typedef struct {
float hip_angle;
int16_t emg[8];
uint32_t mode;
} MyLog_t; // 24 B — the size is a multiple of 4
XM_LOG_DECLARE(MyLog, MyLog_t); // Token MyLog ↔ struct MyLog_t
XM_LOG_FIELDS(MyLog) = {
XM_FIELD(MyLog, hip_angle, "hip_angle", "deg", 1.0f),
XM_FIELD_ARRAY(MyLog, emg, "emg", "mV", 1.0f),
XM_FIELD(MyLog, mode, "mode", "", 1.0f),
};
void Control_Setup(void)
{
if (!XM_LOG_REGISTER(MyLog, 0xF0, 1000)) {
// Report a registration failure with an LED — in Control_Setup() the USB is usually not ready yet, so no message goes out
XM_SetLedEffect(XM_LED_1, XM_LED_BLINK, 200);
}
}
void Control_Loop(void)
{
MyLog_t s = { 0 };
(void)XM_LOG_SEND(MyLog, &s); // Calling earlier than the registered period sends nothing and returns false
}
Use the four macros (XM_LOG_DECLARE, XM_LOG_FIELDS, XM_LOG_REGISTER, XM_LOG_SEND) in the same .c file. The macros are for C files only. For wrong usage — an unsupported member type, a size that is not a multiple of 4, a pointer to a different struct — the compiler points at that line.
Function and macro list¶
Struct definition · registration · sending (macros)
| Macro | One-line description |
|---|---|
XM_LOG_DECLARE |
Ties a token to a struct type (near the top of the .c file) |
XM_LOG_FIELDS |
Defines the field description array |
XM_FIELD |
One line for a scalar member |
XM_FIELD_ARRAY |
One line for an array member |
XM_LOG_REGISTER |
Registers a struct (Control_Setup()) |
XM_LOG_SEND |
Sends one registered struct (Control_Loop()) |
Settings · diagnostics (functions)
| Function | One-line description |
|---|---|
XM_Log_SetMinStallMs |
Sets the target time (ms) for which data is kept when the PC program pauses briefly |
XM_Log_GetMinStallMs |
The target time (ms) in use — for diagnostics |
XM_Log_EnableLinkHealth |
Turns link status information on / off |
XM_Log_GetAdmittedWireBps |
The share of the send budget already committed (bytes/s) — for diagnostics |
XM_Log_GetStats |
Copies the observation counters |
Support header xm_api_logging_raw.h — do not call directly
| Function | One-line description |
|---|---|
XM_Log_RegisterSchemaRaw |
The function XM_LOG_REGISTER calls |
XM_Log_SendRaw |
The function XM_LOG_SEND calls |
Function and macro details¶
XM_LOG_DECLARE¶
Ties a token to a struct type. Write it once near the top of the .c file. The token becomes the struct name.
Parameters
| Name | Description |
|---|---|
tok |
Token (short name) — at most 11 characters (XM_LOG_MAX_TOKEN_LEN), letters, digits and _ |
Type |
The struct type to send — size is a multiple of 4 and at most 1,008 B |
Return value: none (declaration macro)
In the cases below, you get a compile error at this line.
| Compile error | Cause |
|---|---|
... must be a multiple of 4 - add padding |
The struct size is not a multiple of 4 |
... exceeds XM_LOG_MAX_STRUCT_SIZE |
The struct size exceeds 1,008 B |
token ... longer than 11 chars |
The token is longer than 11 characters |
To fix the size, add a uint8_t array at the end. You do not have to list that array with XM_FIELD.
⚠️ Call context: Write it at file scope (outside any function). Use the same token as the XM_LOG_FIELDS, XM_LOG_REGISTER and XM_LOG_SEND in the same .c file.
Example
typedef struct {
float angle; // [deg]
float torque; // [Nm]
} JointLog_t; // 8 B
XM_LOG_DECLARE(JointLog, JointLog_t);
See also: XM_LOG_FIELDS, XM_LOG_REGISTER
XM_LOG_FIELDS¶
Defines the field description array. Follow it with = { XM_FIELD(...), ... };. Inside the braces, use only XM_FIELD and XM_FIELD_ARRAY of the same token. The compiler cannot check this rule.
Parameters
| Name | Description |
|---|---|
tok |
The token from XM_LOG_DECLARE |
Return value: none (definition macro)
There are 1 to 90 fields. With 0 fields, or more than 90, you get a compile error (field count must be 1..90) at the XM_LOG_REGISTER line.
⚠️ Call context: Write it at file scope (outside any function). It must come before XM_LOG_REGISTER.
Example
XM_LOG_FIELDS(JointLog) = {
XM_FIELD(JointLog, angle, "angle", "deg", 1.0f),
XM_FIELD(JointLog, torque, "torque", "Nm", 1.0f),
};
See also: XM_FIELD, XM_FIELD_ARRAY
XM_FIELD¶
One line for a scalar member. The type and position are decided from the member.
Parameters
| Name | Description |
|---|---|
tok |
The token from XM_LOG_DECLARE |
member |
The struct member name |
nm |
Field name — up to 15 letters, digits and symbols (ASCII) (Korean letters and characters such as ° make the registration false) |
un |
Unit — up to 7 letters, digits and symbols (ASCII); "" if none (e.g., "deg") |
sc |
Scale — physical value = stored value × sc (usually 1.0f) |
Return value: none (an XM_FieldDesc_t initializer expression)
The supported member types are float, double, bool, int, unsigned and int8_t ~ uint64_t. An enumeration member is accepted as the integer type of the same size. Any other type (char, pointer, struct, array, etc.) is a compile error (unsupported member type).
⚠️ Call context: Use it only inside the braces of XM_LOG_FIELDS.
Example
See also: XM_FIELD_ARRAY, XM_FieldType_e
XM_FIELD_ARRAY¶
One line for an array member. The element type and the count (1 ~ 255) are decided from the array. A pointer member or an array of 256 or more elements is a compile error. The arguments are the same as for XM_FIELD.
Parameters: Same as XM_FIELD.
Return value: none (an XM_FieldDesc_t initializer expression)
⚠️ Call context: Use it only inside the braces of XM_LOG_FIELDS.
Example
See also: XM_FIELD
XM_LOG_REGISTER¶
Registers a struct. Call it inside Control_Setup(). The function it actually calls is XM_Log_RegisterSchemaRaw.
Parameters
| Name | Type | Description |
|---|---|---|
tok |
token | The token from XM_LOG_DECLARE (XM_LOG_FIELDS must come before it) |
module_id |
uint8_t |
Module ID to send this struct with — 0xF0 ~ 0xFE |
nominal_rate_hz |
uint16_t |
Send rate (Hz) — even if you call XM_LOG_SEND more often, data does not go out faster than this |
The interval is rounded up to a 1 ms step, so if the rate is not a divisor of 1,000, data goes out less often (e.g., 300 Hz → 250 Hz, 600 Hz → 500 Hz). Data never goes out faster than 1,000 Hz.
Return value: bool — true: registered. false: see the table below.
false when |
Fix |
|---|---|
| This token is already registered | Register each token only once |
The module ID is outside 0xF0 ~ 0xFE or already in use |
Use a free ID |
| More than 4 registrations | Register up to 4 structs |
nominal_rate_hz is 0 |
Set it to 1 or more |
| Field names collide, or two fields point at the same bytes of the struct | Check the field names and members |
| A field name or unit is too long, or the token, a field name or a unit contains a non-ASCII character (Korean, etc.) | Use letters, digits and _ for the token, and at most 15 (name) or 7 (unit) ASCII characters |
scale is 0 or not a number (infinity, NaN) |
Use a non-zero finite value |
| The volume to send exceeds what USB can carry (counted together with structs registered earlier) | Lower the rate or shrink the struct |
Together with structs registered earlier, the volume exceeds what can be kept when the PC program pauses briefly (see XM_Log_SetMinStallMs) |
Lower the rate or shrink the struct |
Called outside Control_Setup() (after Control_Setup() has finished, from another task, or from an interrupt) |
Call it inside Control_Setup() |
- On
falsethe registration state does not change, so you can fix it and call again insideControl_Setup(). Even onfalse, a token that is already registered can still be sent as before. - There are 1 to 90 fields. With 0 fields, or more than 90, you get a compile error.
- A registered module ID belongs to
XM_LOG_SENDonly. CallingXM_SendUsbDataWithId()with the same ID returnsfalse. - Calling it after
Control_Setup()has finished raisesseal_reject_count; calling it from another task or an interrupt raisesowner_violation_count(XM_LogStats_t). - How large a struct and how high a rate still register has not yet been confirmed on the board. (not yet confirmed on the board)
Once the first registration succeeds, the following things change (details in section 7 of 10. Struct Logging).
- Calls to
XM_USB_SetHostProfile()are ignored. XM_Log_SetMinStallMs()returnsfalse.XM_SetUsbCustomMeta()works only insideControl_Setup(). Calling it later returnsfalse.- The registered module ID belongs to that struct only.
- The send volume of
XM_SendUsbDataWithId()andXM_SendUsbDebugMessage()becomes limited. - The board sends the field names, units and types to the PC. They are delivered when a PC program opens the port, and sent again every few seconds after that. (design goal, not yet confirmed on the board)
⚠️ Call context: Call it only inside Control_Setup().
Example
void Control_Setup(void)
{
if (!XM_LOG_REGISTER(JointLog, 0xF0, 500)) { // 500 Hz — every 2 ms
XM_SetLedEffect(XM_LED_3, XM_LED_BLINK, 200);
}
}
See also: XM_LOG_SEND, XM_Log_SetMinStallMs
XM_LOG_SEND¶
Sends one registered struct. Call it inside Control_Loop(). The function it actually calls is XM_Log_SendRaw. The struct is copied inside the call, so a local variable is fine.
Parameters
| Name | Type | Description |
|---|---|---|
tok |
token | The token from XM_LOG_DECLARE |
ptr |
struct pointer | A pointer to that struct — a pointer of another type is a compile error |
Return value: bool — true: sent (it means the struct was queued in the transmit buffer, and says nothing about whether the PC received it). false: one of the following.
- Not registered (including a token whose
XM_LOG_REGISTERreturnedfalse) ptrisNULL- Called earlier than the registered period
- The USB device is not ready
- The transmit buffer is full
- Called outside
Control_Setup()andControl_Loop()(from another task or an interrupt)
You can call it every loop and data goes out only at the registered rate, so a false before the period is due is normal. Even if false was returned because the transmit buffer was full, the next send goes out after the registered period has passed (calling it again right away does not send). Calling it outside Control_Setup() and Control_Loop() raises owner_violation_count (XM_LogStats_t).
⚠️ Call context: Call it inside Control_Loop(). Do not call it from an interrupt or from a background task you created yourself (XM_Task_*).
Example
void Control_Loop(void)
{
JointLog_t s = { XM.status.h10.leftHipAngle, XM.status.h10.leftHipTorque };
(void)XM_LOG_SEND(JointLog, &s); // You can call it every loop — data goes out at 500 Hz only
}
See also: XM_LOG_REGISTER, XM_Log_GetStats
XM_Log_SetMinStallMs¶
Sets the target time (ms) for which the board keeps data without dropping it when the PC program pauses briefly. The default is 50 ms. Registering a struct that sends too much to meet this goal returns false. 50 ms is an initial value that has not yet been measured on the board, so it is a goal, not a guarantee. (design goal)
Parameters
| Name | Type | Description |
|---|---|---|
ms |
uint16_t |
Target time (ms) |
Return value: bool — true: changed. false: called outside Control_Setup(), or a struct has already been registered successfully
⚠️ Call context: Call it inside Control_Setup(), before the first XM_LOG_REGISTER.
Example
void Control_Setup(void)
{
(void)XM_Log_SetMinStallMs(100); // The value is only an example — if the default 50 ms is enough, you do not have to call it
(void)XM_LOG_REGISTER(JointLog, 0xF0, 500);
}
See also: XM_Log_GetMinStallMs, XM_LOG_REGISTER
XM_Log_GetMinStallMs¶
Returns the target time (ms) currently in use. For diagnostics.
Parameters: none
Return value: uint16_t — the target time (ms)
⚠️ Call context: Assume Control_Setup() / Control_Loop() context.
See also: XM_Log_SetMinStallMs
XM_Log_EnableLinkHealth¶
Sets whether link status information (number of failed sends, buffer usage, etc.) is sent every second. The default is not to send it. It is sent only when at least one struct is registered — if you register none, turning it on sends nothing. (design goal, not yet confirmed on the board)
Parameters
| Name | Type | Description |
|---|---|---|
enable |
bool |
true: send, false: do not send |
Return value: bool — true: set. false: called outside Control_Setup()
The xm10 tool does not decode and show this value separately. To look at the logging counters on the board, use XM_Log_GetStats.
⚠️ Call context: Call it inside Control_Setup().
Example
void Control_Setup(void)
{
(void)XM_LOG_REGISTER(JointLog, 0xF0, 500);
(void)XM_Log_EnableLinkHealth(true); // Turn link status sending on
}
See also: XM_Log_GetStats
XM_Log_GetAdmittedWireBps¶
Returns the share of the USB send budget that is already committed (bytes/s). It is the sum of the shares of the registered structs and the share the board reserves in advance.
The reserved share covers unregistered data and debug text, sending of struct information and link status, and — with the default profile (XM_USB_HOST_PHAI_STUDIO) — Total Data. If you registered no struct, only the reserved share is returned. For diagnostics.
Parameters: none
Return value: uint32_t — the committed send share (bytes/s)
⚠️ Call context: Assume Control_Setup() / Control_Loop() context.
See also: XM_LOG_REGISTER
XM_Log_GetStats¶
Copies the observation counters. Each field is read separately, so several fields may not be values from exactly the same instant. For what each field means, see XM_LogStats_t.
Parameters
| Name | Type | Description |
|---|---|---|
out |
XM_LogStats_t* |
Where to put the copy — if NULL, nothing happens |
Return value: none (void) — the result comes back through out.
⚠️ Call context: Call it from Control_Loop().
Example
static XM_LogStats_t s_stats;
void Control_Loop(void)
{
XM_Log_GetStats(&s_stats); // Add s_stats to the debugger's Live Expressions to watch it
}
See also: XM_LogStats_t
Types / constants¶
XM_FieldType_e¶
The field type. XM_FIELD and XM_FIELD_ARRAY decide it from the C type of the member (you do not write it yourself).
| Value | Name | Member C type | Size |
|---|---|---|---|
| 0 | XM_FIELD_F32 |
float |
4 B |
| 1 | XM_FIELD_F64 |
double |
8 B |
| 2 | XM_FIELD_I8 |
int8_t |
1 B |
| 3 | XM_FIELD_U8 |
uint8_t |
1 B |
| 4 | XM_FIELD_I16 |
int16_t |
2 B |
| 5 | XM_FIELD_U16 |
uint16_t |
2 B |
| 6 | XM_FIELD_I32 |
int32_t · int |
4 B |
| 7 | XM_FIELD_U32 |
uint32_t · unsigned |
4 B |
| 8 | XM_FIELD_I64 |
int64_t |
8 B |
| 9 | XM_FIELD_U64 |
uint64_t |
8 B |
| 10 | XM_FIELD_BOOL |
bool |
1 B |
XM_FieldDesc_t¶
One line of a field description. Build it with XM_FIELD or XM_FIELD_ARRAY. If you fill it in by hand, the compiler cannot check the type and position.
| Member | Type | Description |
|---|---|---|
name |
const char* |
Field name (up to 15 letters, digits and symbols, ASCII only) |
unit |
const char* |
Unit (up to 7 letters, digits and symbols, ASCII only; "" if none) |
type |
uint8_t |
Element type — an XM_FieldType_e value. It is 1 byte regardless of compiler options |
offset |
uint16_t |
Position inside the struct (bytes) |
array_len |
uint8_t |
Number of elements (scalar = 1) |
scale |
float |
Physical value = stored value × scale |
XM_LogStats_t¶
The logging observation counters. You receive them with XM_Log_GetStats(). All are totals since boot, and a field with no special note is counted even if you registered no struct.
| Member | Type | Description |
|---|---|---|
rate_gate_suppressed_calls |
uint32_t |
Number of times XM_LOG_SEND was called more often than the registered period and nothing was sent (only occurs when a struct is registered) |
owner_violation_count |
uint32_t |
Number of times XM_LOG_REGISTER, XM_LOG_SEND or a setting function of this header was called outside Control_Setup() and Control_Loop() (from another task or an interrupt) (once a struct is registered, XM_SetUsbCustomMeta() is counted too) |
profile_change_blocked_count |
uint32_t |
Number of times XM_USB_SetHostProfile() was called after registration and ignored |
admission_reject_count |
uint32_t |
Number of times XM_SendUsbDataWithId(), XM_SendUsbDebugMessage() and the like were rejected by the send-volume limit (including something too large to send at once) (counted only when a struct is registered. A call rejected because an argument exceeded the function's maximum length is not counted) |
reserved_id_reject_count |
uint32_t |
Number of times a send was rejected for using a module ID the system uses or the module ID of a registered struct, plus the number of times a system ID passed to XM_SetUsbStreamModuleId() was ignored (the ID of a registered struct is counted every time you send with that ID, not when you set it) |
seal_reject_count |
uint32_t |
Number of times XM_LOG_REGISTER was called after Control_Setup() had finished (once a struct is registered, XM_SetUsbCustomMeta() is counted too) |
admission_attempt_calls |
uint32_t |
Number of times the sends above were checked against the send-volume limit (allowed + rejected, counted only when a struct is registered) |
x20_same_tick_suppressed_calls |
uint32_t |
Number of times the control loop ran again within the same 1 ms and the Total Data send was skipped (counted only when a struct is registered) |
- It is normal for
rate_gate_suppressed_callsto keep growing when you callXM_LOG_SENDevery loop. - Sending Total Data at most once per 1 ms and skipping the rest has not yet been confirmed on the board. (not yet confirmed on the board)
Limit constants¶
| Name | Value | Meaning |
|---|---|---|
XM_LOG_MAX_STRUCT_SIZE |
1008U |
Upper limit of the size of a struct you can register (bytes) |
XM_LOG_MAX_FIELDS |
90U |
Upper limit of the number of fields you can list for one struct |
XM_LOG_MAX_ARRAY_LEN |
255U |
Upper limit of the number of elements of one array field |
XM_LOG_MAX_TOKEN_LEN |
11U |
Upper limit of the token length (characters) — the token becomes the struct name |
XM_LOG_SLOT_NONE |
0xFFU |
The slot value of a token that is not registered |
XM_TYPE_TAG¶
The field type of a member expression. XM_FIELD and XM_FIELD_ARRAY use it internally, so you rarely need it yourself. The supported types are float, double, bool, int, unsigned and int8_t ~ uint64_t. An enumeration member is accepted as the integer type of the same size. Any other type (char, pointer, struct, array, etc.) is a compile error.
Support header — xm_api_logging_raw.h (do not call directly)¶
⚠️ Not a supported interface. It holds the prototypes of the functions that the
XM_LOG_REGISTERandXM_LOG_SENDmacros call. A.cfile that uses the macros must include this header to compile. The functions below skip type and size checks, so the compiler cannot stop you from sending a different struct. Do not call them directly; always use theXM_LOG_*macros.
XM_Log_RegisterSchemaRaw¶
bool XM_Log_RegisterSchemaRaw(uint8_t* slot_io, uint8_t module_id, const char* struct_name, uint16_t struct_size,
uint16_t nominal_rate_hz, const XM_FieldDesc_t* fields, uint8_t field_count);
Called by XM_LOG_REGISTER.
Parameters
| Name | Type | Description |
|---|---|---|
slot_io |
uint8_t* |
The token's slot variable — it registers only when it is XM_LOG_SLOT_NONE, and writes the slot number on success |
module_id |
uint8_t |
Module ID (0xF0 ~ 0xFE) |
struct_name |
const char* |
Struct name (token) |
struct_size |
uint16_t |
Struct size (bytes) |
nominal_rate_hz |
uint16_t |
Send rate (Hz) |
fields |
const XM_FieldDesc_t* |
Field description array |
field_count |
uint8_t |
Number of fields |
Return value: bool — true: registered. false: the slot was already registered or registration failed — *slot_io does not change.
XM_Log_SendRaw¶
Called by XM_LOG_SEND.
Parameters
| Name | Type | Description |
|---|---|---|
slot |
uint8_t |
The registered slot number |
data |
const void* |
The struct to send |
size |
uint16_t |
Struct size (bytes) |
Return value: bool — true: sent. false: the slot is not registered, size differs from the struct size of that slot, data is NULL, called earlier than the registered period, the USB device is not ready, the transmit buffer is full, or called outside Control_Setup() and Control_Loop() (from another task or an interrupt). Even if false was returned because the transmit buffer was full, the next send goes out after the registered period has passed.
Internal-only (do not use)¶
The macros whose names end in _ (XM_LOG_REQUIRE_, XM_FIELD_TYPE_NONE_, XM_TYPE_TAG_, XM_LOG_MEMBER_, XM_LOG_ARRLEN_, XM_LOG_IS_ARRAY_, XM_LOG_FIELD_COUNT_) are helper definitions that other macros use. Do not use them in your code.
Rev1.1 / Rev2.0 Difference Summary¶
| Item | Rev1.1 | Rev2.0 |
|---|---|---|
Struct logging (xm_api_logging.h · xm_api_logging_raw.h) |
❌ Absent | 🟢 Exclusive |
Existing way (XM_SetUsbCustomMeta + XM_SendUsbDataWithId) |
✅ | ✅ |
Related Documents¶
- 10. Struct Logging (concept) — operating principle, limits, common mistakes
xm_api_usb.h— USB-CDC Real-Time Streaming — the existing USB-CDC functions such asXM_SendUsbDataWithIdandXM_SetUsbCustomMeta- Get Data on PC — the
xm10tool