Target-side C API. For a high-level overview and measured performance, see the top-level README. For the on-wire layout of the control block and ring buffers, see wire-protocol.md.
- Configuration macros
- Initialization
- Writing to a channel
- Typed numeric channels
- Reading from a channel
- Modes
- Compile-out kill switch
Header: target/probestream.h. Implementation: target/probestream.c.
Defined in target/probestream_conf.h. Override any of these by defining them in your build system or by replacing the file. All sizes are in bytes; all counts are integer limits.
| Macro | Default | Meaning |
|---|---|---|
PS_ENABLED |
1 |
Master switch. Set to 0 to compile every ProbeStream call to a no-op. |
PS_MAX_UP_CHANNELS |
3 |
Maximum number of target → host channels the control block can hold. |
PS_MAX_DOWN_CHANNELS |
3 |
Maximum number of host → target channels. |
PS_ENABLE_PRINTF |
1 |
If 0, drops PS_Printf (removes the dependency on vsnprintf). |
PS_PRINTF_BUFFER_SIZE |
128 |
Stack buffer used by PS_Printf. Strings longer than this are truncated. |
PS_DEFAULT_MODE |
0 (PS_MODE_SKIP) |
Default mode for newly created channels. |
typedef struct {
void* pBuffer; // RAM region you provide (must outlive PS_Init)
uint32_t bufferSize; // size of pBuffer in bytes
uint8_t numUpChannels; // 1..PS_MAX_UP_CHANNELS
uint8_t numDownChannels; // 0..PS_MAX_DOWN_CHANNELS
uint8_t defaultMode; // PS_MODE_SKIP | PS_MODE_TRIM | PS_MODE_BLOCK
} PS_Config_t;
void PS_Init(const PS_Config_t* config);PS_Init lays out the control block at the start of pBuffer, then allocates the remainder equally between up- and down-channel ring buffers. The magic ID is written last so a partial init can never be observed by the host.
Constraints:
pBuffermust be in RAM (the host needs to read and write its descriptors). Static__attribute__((aligned(4)))allocation is recommended.- Minimum useful
bufferSizedepends onnumUpChannels + numDownChannels. The control block header is32 + (PS_MAX_UP_CHANNELS + PS_MAX_DOWN_CHANNELS) * 20bytes; the rest is split evenly. Each ring needs at least 32 bytes. PS_Initshould be called once, early inmain, before any otherPS_*call.
uint32_t PS_Write(uint8_t channel, const void* data, uint32_t numBytes);
uint32_t PS_WriteString(uint8_t channel, const char* str);
uint32_t PS_WriteInt(uint8_t channel, int32_t value);
uint32_t PS_WriteUInt(uint8_t channel, uint32_t value);
uint32_t PS_WriteFloat(uint8_t channel, float value);
uint32_t PS_WriteDouble(uint8_t channel, double value);
int PS_Printf(uint8_t channel, const char* fmt, ...); // if PS_ENABLE_PRINTF- Returns the number of bytes actually committed to the ring (may be 0 or less than requested depending on mode and free space).
- These calls are interrupt-safe on the writer side as long as only one context writes a given channel at a time. Concurrent writers to the same channel need external locking. The host (reader) side is always safe regardless.
PS_Printfuses an internal stack buffer ofPS_PRINTF_BUFFER_SIZE. Output is truncated to fit.
void PS_SetChannelType(uint8_t channel, uint8_t type);Channel type lives in the channel descriptor and tells host tools how to interpret a channel. The type applies to the whole channel, so do not mix text logs and numeric samples on the same channel if you want graphing or stats.
| Type | Constant | Payload format |
|---|---|---|
| Raw bytes | PS_CHANNEL_TYPE_RAW |
Untyped bytes; not graphable. |
| Text | PS_CHANNEL_TYPE_TEXT |
Human-readable text; not graphable. |
| ASCII number | PS_CHANNEL_TYPE_ASCII_NUMBER |
Strict numeric ASCII tokens such as 12, 12.4, -0.23. |
| Signed 32-bit | PS_CHANNEL_TYPE_INT32 |
Little-endian int32_t samples. |
| Unsigned 32-bit | PS_CHANNEL_TYPE_UINT32 |
Little-endian uint32_t samples. |
| 32-bit float | PS_CHANNEL_TYPE_FLOAT32 |
Little-endian float samples. |
| 64-bit float | PS_CHANNEL_TYPE_FLOAT64 |
Little-endian double samples. |
PS_WriteInt, PS_WriteUInt, PS_WriteFloat, and PS_WriteDouble mark the up-channel with the matching type before writing the sample. Use PS_SetChannelType(channel, PS_CHANNEL_TYPE_ASCII_NUMBER) when you want to emit numeric ASCII with PS_WriteString or PS_Printf.
PS_WriteFloat(0, temperature_c);
PS_SetChannelType(1, PS_CHANNEL_TYPE_ASCII_NUMBER);
PS_Printf(1, "%.3f\n", current_ma);PS_SetMode preserves channel type bits, and PS_SetChannelType preserves the current full-buffer mode.
uint32_t PS_Read(uint8_t channel, void* data, uint32_t maxBytes);
uint32_t PS_HasData(uint8_t channel);PS_Readconsumes bytes from down-channelchanneland returns how many it copied (0..maxBytes). Call it from your main loop or a low-priority task there's no callback, just polling.PS_HasDatareturns the number of currently-buffered bytes without consuming them.
void PS_SetMode(uint8_t channel, uint8_t mode); // up-channels onlyBehavior when an up-channel ring is full and a write arrives:
| Mode | Constant | Behavior |
|---|---|---|
| Skip | PS_MODE_SKIP |
Drop the entire write. Return value is 0. |
| Trim | PS_MODE_TRIM |
Write as many bytes as fit, drop the rest. Return value is what was committed. |
| Block | PS_MODE_BLOCK |
Busy-wait until the host advances the read pointer. Return value equals numBytes. |
PS_MODE_BLOCK is convenient for never-lose-a-byte streams but will stall your firmware indefinitely if the host stops reading. Use it only when you know the host is alive.
#define PS_ENABLED 0Every public function becomes ((void)0) / returns 0. No symbols are emitted, no buffer is allocated. Useful for shipping a single binary that supports both debug and release without #ifdef clutter at every call site.