ProtoCore v0.0.1
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
trace_capture.h
Go to the documentation of this file.
1// 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 (PC_ENABLE_TRACE_CAPTURE) - the v5
7 * high-rate acquisition primitive.
8 *
9 * The consumer sitting downstream of services/system/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): pc_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 pc_tc_config::pretrigger_samples samples. When pc_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 pc_tc_stats::triggers_dropped), never queued or stomped - the
23 * same determinism contract as services/system/dma's one-TX-in-flight rule. Storage is static
24 * (PC_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 * pc_cycles() (services/system/clock.h) rather than pc_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 * PC_DMA_BUF_SIZE / the post-trigger sample count; pc_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"
44
45#if PC_ENABLE_TRACE_CAPTURE
46
47#include <stddef.h>
48#include <stdint.h>
49
50/** @brief One completed pre/post-trigger sample window, handed to the sink inline. */
51struct pc_tc_window
52{
53 const uint16_t *samples; ///< pretrigger_samples + posttrigger_samples contiguous codes
54 uint16_t n_samples; ///< total samples in the window (== the configured split's sum)
55 uint16_t pretrigger_samples; ///< how many of @ref samples precede the trigger instant
56 uint32_t trace_id; ///< monotonic capture sequence (wraps), one per completed window
57 uint32_t assembly_cycles; ///< pc_cycles() delta from trigger() to this callback
58};
59
60/** @brief Sink for one completed window. Called inline from pc_tc_feed() / pc_tc_trigger(). */
61typedef void (*pc_tc_sink_fn)(const pc_tc_window *win, void *ctx);
62
63/** @brief Capture configuration passed to pc_tc_begin(). */
64struct pc_tc_config
65{
66 uint16_t pretrigger_samples; ///< samples of history kept before the trigger instant
67 uint16_t posttrigger_samples; ///< samples collected after the trigger before the window fires
68 pc_tc_sink_fn sink; ///< completed-window callback (required)
69 void *ctx; ///< opaque, forwarded to @ref sink
70};
71
72/** @brief Rolling telemetry: never inferred from state, always the ground truth counters. */
73struct pc_tc_stats
74{
75 uint32_t windows_completed; ///< total windows handed to the sink
76 uint32_t triggers_dropped; ///< trigger() calls rejected because a window was already filling
77 uint32_t samples_dropped; ///< feed() samples rejected because the window buffer was full
78};
79
80/**
81 * @brief Configure the assembler and reset all state.
82 * @return true if configured; false on a null config/sink, a zero split, or a split that
83 * exceeds PC_TC_MAX_WINDOW_SAMPLES.
84 */
85bool pc_tc_begin(const pc_tc_config *cfg);
86
87/**
88 * @brief Feed @p n newly-arrived samples (oldest first). Always refreshes the pre-trigger
89 * ring; while a window is filling, also appends into it - completing and firing the
90 * sink the instant @ref pc_tc_config::posttrigger_samples have arrived since
91 * trigger(). ISR-safe: no blocking, no allocation, bounded work proportional to @p n.
92 * @return @p n once configured; 0 (all @p n counted into @ref pc_tc_stats::samples_dropped,
93 * fail-closed) if called before pc_tc_begin() / after pc_tc_end().
94 */
95uint16_t pc_tc_feed(const uint16_t *samples, uint16_t n);
96
97/**
98 * @brief Declare the trigger instant now: the pre-trigger ring's current content becomes the
99 * window's pre-trigger half, and subsequent pc_tc_feed() calls fill the post-trigger
100 * half. ISR-safe.
101 * @return true if armed; false (fail-closed, counted) if a window was already filling.
102 */
103bool pc_tc_trigger(void);
104
105/** @brief Rolling telemetry snapshot (see @ref pc_tc_stats). */
106void pc_tc_get_stats(pc_tc_stats *out);
107
108/** @brief True while a window is between trigger() and the post-trigger count completing. */
109bool pc_tc_capturing(void);
110
111/** @brief Tear down: no further sink calls until pc_tc_begin() again. */
112void pc_tc_end(void);
113
114#endif // PC_ENABLE_TRACE_CAPTURE
115
116#endif // PROTOCORE_TRACE_CAPTURE_H
User-facing configuration for ProtoCore.