ProtoCore v1.0.16
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
trace_capture.h
Go to the documentation of this file.
1// ProtoCore v1.0.16 - Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4/**
5 * @file trace_capture.h
6 * @brief Pre/post-trigger sample-window assembler (PROTOCORE_ENABLE_TRACE_CAPTURE) - the v5
7 * high-rate acquisition primitive.
8 *
9 * The consumer sitting downstream of mmgr/dma on a sampling front end (an external
10 * ADC front-end - e.g. an AD9226/AD9238 - draining into the device over SPI or UART DMA,
11 * a benchtop scope's digitizer output, or any other high-rate source): protocore_tc_feed()
12 * is called with every batch of samples as they arrive (from a DMA-complete handler, most
13 * naturally), and a continuously-running **pre-trigger ring** always holds the most recent
14 * @ref protocore_tc_config::pretrigger_samples samples. When protocore_tc_trigger() fires - a GPIO ISR,
15 * a software threshold detector, or an external front-end's own trigger line - the ring's
16 * current content becomes the pre-trigger half of the window and subsequent feeds fill the
17 * post-trigger half, so the emitted window straddles the trigger instant exactly like a
18 * benchtop oscilloscope's pretrigger/posttrigger split, even though the trigger is detected
19 * only after the pre-trigger samples already went by.
20 *
21 * One capture in flight at a time, fail-closed: a trigger while a window is still filling is
22 * rejected and counted (@ref protocore_tc_stats::triggers_dropped), never queued or stomped - the
23 * same determinism contract as mmgr/dma's one-TX-in-flight rule. Storage is static
24 * (PROTOCORE_TC_MAX_WINDOW_SAMPLES samples, zero heap); feed() and trigger() are ISR-safe (no
25 * blocking, no allocation) so the natural wiring is a DMA-complete callback calling feed()
26 * and a GPIO ISR calling trigger(), both posting nothing further themselves - the window
27 * callback fires inline the instant the post-trigger half completes.
28 *
29 * Window-assembly latency (trigger() to the completed callback) is timestamped with
30 * protocore_cycles() (server/clock/clock.h) rather than protocore_micros(): at the sample rates this feeds
31 * from (an SPI-drained ADC burst can complete several feed() calls within a single
32 * microsecond tick) a 1 us clock under-resolves the jitter that matters for sizing
33 * PROTOCORE_DMA_BUF_SIZE / the post-trigger sample count; protocore_cycles_to_ns() converts the
34 * measured cycle delta to nanoseconds once the caller supplies the running CPU frequency.
35 *
36 * @author Douglas Quigg (dstroy0)
37 * @date 2026
38 */
39
40#ifndef PROTOCORE_TRACE_CAPTURE_H
41#define PROTOCORE_TRACE_CAPTURE_H
42
43#include "protocore_config.h" // the entry point: protocore_types.h for the widths
44
45#if PROTOCORE_ENABLE_TRACE_CAPTURE
46
48
49/** @brief One completed pre/post-trigger sample window, handed to the sink inline. */
50typedef struct
51{
52 const uint16_t *samples; ///< pretrigger_samples + posttrigger_samples contiguous codes
53 uint16_t n_samples; ///< total samples in the window (== the configured split's sum)
54 uint16_t pretrigger_samples; ///< how many of @ref samples precede the trigger instant
55 uint32_t trace_id; ///< monotonic capture sequence (wraps), one per completed window
56 uint32_t assembly_cycles; ///< protocore_cycles() delta from trigger() to this callback
57} protocore_tc_window;
58
59/** @brief Sink for one completed window. Called inline from protocore_tc_feed() / protocore_tc_trigger(). */
60typedef void (*protocore_tc_sink_fn)(const protocore_tc_window *win, void *ctx);
61
62/** @brief Capture configuration passed to protocore_tc_begin(). */
63typedef struct
64{
65 uint16_t pretrigger_samples; ///< samples of history kept before the trigger instant
66 uint16_t posttrigger_samples; ///< samples collected after the trigger before the window fires
67 protocore_tc_sink_fn sink; ///< completed-window callback (required)
68 void *ctx; ///< opaque, forwarded to @ref sink
69} protocore_tc_config;
70
71/** @brief Rolling telemetry: never inferred from state, always the ground truth counters. */
72typedef struct
73{
74 uint32_t windows_completed; ///< total windows handed to the sink
75 uint32_t triggers_dropped; ///< trigger() calls rejected because a window was already filling
76 uint32_t samples_dropped; ///< feed() samples rejected because the window buffer was full
77} protocore_tc_stats;
78
79/** @brief The samples one feed carries, and where a stats read lands. */
80typedef struct
81{
82 const uint16_t *samples; ///< the block just sampled
83 uint16_t n; ///< how many
84 protocore_tc_stats *stats; ///< where a stats read copies the tallies
85} TcFeedArgs;
86
87/**
88 * @brief The pre/post-trigger sample capture.
89 *
90 * A caller sets the members a call takes, invokes it through ::TraceCapture, and reads the outcome
91 * off the same handle. The pre-roll ring and the assembled window are behind @ref internal.
92 *
93 * @var TraceCaptureNs::cfg what arming the capture takes
94 * @var TraceCaptureNs::feed the samples one feed carries, and where a stats read lands
95 * @var TraceCaptureNs::ok a call's true/false outcome
96 * @var TraceCaptureNs::accepted samples the feed took
97 * @var TraceCaptureNs::begin arm the capture at its pre/post sample counts
98 * @var TraceCaptureNs::feed_in push samples through the pre-roll ring and the window
99 * @var TraceCaptureNs::trigger latch the pre-roll and start collecting post-trigger samples
100 * @var TraceCaptureNs::get_stats copy the tallies out
101 * @var TraceCaptureNs::capturing a window is being assembled right now
102 * @var TraceCaptureNs::end disarm
103 *
104 * The window completes inside a feed: when the last post-trigger sample lands, the sink is called
105 * with the assembled window before the feed returns.
106 */
107typedef struct
108{
109 const protocore_tc_config *cfg;
110 TcFeedArgs feed;
111 proto_bool ok;
112 uint16_t accepted;
113} TraceCaptureVars;
114
115/** @brief The operands and the outcome. */
116extern TraceCaptureVars TraceCaptureV;
117
118/** @brief The entries. */
119typedef struct
120{
121 void (*const begin)(uint8_t *work);
122 void (*const feed_in)(uint8_t *work);
123 void (*const trigger)(uint8_t *work);
124 void (*const get_stats)(uint8_t *work);
125 void (*const capturing)(uint8_t *work);
126 void (*const end)(uint8_t *work);
127} TraceCaptureNs;
128
129// What the table binds, defined once in the .c and taking one parameter each: everything
130// else an entry needs is an operand in TraceCaptureV or a region of the borrow at a fixed offset.
131void protocore_trace_capture_begin(uint8_t *work);
132void protocore_trace_capture_feed_in(uint8_t *work);
133void protocore_trace_capture_trigger(uint8_t *work);
134void protocore_trace_capture_get_stats(uint8_t *work);
135void protocore_trace_capture_capturing(uint8_t *work);
136void protocore_trace_capture_end(uint8_t *work);
137
138// `static const`, initialised HERE rather than `extern` against a definition in the .c: a
139// const object whose initializer every translation unit can see is a COMPILE-TIME FACT, so
140// `TraceCapture.begin(work)` resolves to a named function and becomes a DIRECT call. An extern table
141// leaves the call indirect and the symbol live at every level, -O2 -flto included.
142static const TraceCaptureNs TraceCapture __attribute__((unused)) = {
143 .begin = protocore_trace_capture_begin,
144 .feed_in = protocore_trace_capture_feed_in,
145 .trigger = protocore_trace_capture_trigger,
146 .get_stats = protocore_trace_capture_get_stats,
147 .capturing = protocore_trace_capture_capturing,
148 .end = protocore_trace_capture_end,
149};
150
151/**
152 * @brief The PROTOCORE_TRACE_CAPTURE_BORROW bytes this module's state lives in.
153 *
154 * Stated beside the namespace rather than on it: an entry takes a borrow, and this is where
155 * that borrow comes from. Taken once from the end of the pool, which no mark and no release
156 * walks, so the state lasts the life of the program.
157 *
158 * @return the span.
159 */
160uint8_t *protocore_trace_capture_span(void);
161
163
164#endif // PROTOCORE_ENABLE_TRACE_CAPTURE
165
166#endif // PROTOCORE_TRACE_CAPTURE_H
#define PROTOCORE_BEGIN_DECLS
Give a header's declarations C linkage, so their symbol names carry no parameter types.
Definition types.h:96
_Bool proto_bool
The truth value.
Definition types.h:64
#define PROTOCORE_END_DECLS
Definition types.h:97