ProtoCore v0.0.2
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
trace_capture.cpp
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.cpp
6 * @brief Pre/post-trigger sample-window assembler - implementation.
7 *
8 * All state is one static instance (internal linkage) - zero heap, fixed capacity
9 * PC_TC_MAX_WINDOW_SAMPLES. The pre-trigger ring is sized to the *configured*
10 * pretrigger_samples (<= the compile-time max) and indexed with a running write cursor;
11 * pc_tc_trigger() reads it out oldest-first into the front of the window buffer. No
12 * dynamic memory, no locks: feed() and trigger() are each a single bounded pass with no
13 * blocking, so both are safe to call from an ISR (a DMA-complete callback and a GPIO
14 * trigger ISR respectively).
15 */
16
18
19#if PC_ENABLE_TRACE_CAPTURE
20
21#include "services/system/clock.h" // pc_cycles()
22#include <string.h> // memset
23
24namespace
25{
26struct TcCtx
27{
28 uint16_t pre_ring[PC_TC_MAX_WINDOW_SAMPLES];
29 uint16_t window[PC_TC_MAX_WINDOW_SAMPLES];
30 pc_tc_sink_fn sink;
31 void *ctx;
32 uint16_t pretrigger_samples;
33 uint16_t posttrigger_samples;
34 uint16_t pre_head; // next pre_ring write index [0, pretrigger_samples)
35 uint16_t post_count; // post-trigger samples collected since trigger() [0, posttrigger_samples]
36 uint32_t trace_id;
37 uint32_t trigger_cycles;
38 pc_tc_stats stats;
39 bool capturing;
40 bool configured;
41};
42TcCtx s_tc;
43
44void ring_push(uint16_t sample)
45{
46 if (s_tc.pretrigger_samples == 0)
47 {
48 return; // no pre-roll configured - nothing to keep
49 }
50 s_tc.pre_ring[s_tc.pre_head] = sample;
51 s_tc.pre_head++;
52 if (s_tc.pre_head >= s_tc.pretrigger_samples)
53 {
54 s_tc.pre_head = 0;
55 }
56}
57} // namespace
58
59bool pc_tc_begin(const pc_tc_config *cfg)
60{
61 if (!cfg || !cfg->sink)
62 {
63 return false;
64 }
65 if (cfg->pretrigger_samples == 0 && cfg->posttrigger_samples == 0)
66 {
67 return false;
68 }
69 uint32_t total = (uint32_t)cfg->pretrigger_samples + (uint32_t)cfg->posttrigger_samples;
70 if (total > PC_TC_MAX_WINDOW_SAMPLES)
71 {
72 return false;
73 }
74
75 memset(&s_tc, 0, sizeof(s_tc));
76 s_tc.sink = cfg->sink;
77 s_tc.ctx = cfg->ctx;
78 s_tc.pretrigger_samples = cfg->pretrigger_samples;
79 s_tc.posttrigger_samples = cfg->posttrigger_samples;
80 s_tc.configured = true;
81 return true;
82}
83
84uint16_t pc_tc_feed(const uint16_t *samples, uint16_t n)
85{
86 if (!s_tc.configured || !samples)
87 {
88 s_tc.stats.samples_dropped += n;
89 return 0;
90 }
91 for (uint16_t i = 0; i < n; i++)
92 {
93 uint16_t s = samples[i];
94 ring_push(s);
95 if (s_tc.capturing && s_tc.post_count < s_tc.posttrigger_samples)
96 {
97 s_tc.window[s_tc.pretrigger_samples + s_tc.post_count] = s;
98 s_tc.post_count++;
99 if (s_tc.post_count == s_tc.posttrigger_samples)
100 {
101 pc_tc_window win;
102 win.samples = s_tc.window;
103 win.n_samples = (uint16_t)(s_tc.pretrigger_samples + s_tc.posttrigger_samples);
104 win.pretrigger_samples = s_tc.pretrigger_samples;
105 win.trace_id = s_tc.trace_id++;
106 win.assembly_cycles = pc_cycles() - s_tc.trigger_cycles; // wrap-safe unsigned delta
107 s_tc.capturing = false;
108 s_tc.stats.windows_completed++;
109 s_tc.sink(&win, s_tc.ctx);
110 }
111 }
112 }
113 return n;
114}
115
116bool pc_tc_trigger(void)
117{
118 if (!s_tc.configured)
119 {
120 return false;
121 }
122 if (s_tc.capturing)
123 {
124 s_tc.stats.triggers_dropped++;
125 return false;
126 }
127 for (uint16_t i = 0; i < s_tc.pretrigger_samples; i++)
128 {
129 s_tc.window[i] = s_tc.pre_ring[(s_tc.pre_head + i) % s_tc.pretrigger_samples];
130 }
131 s_tc.post_count = 0;
132 s_tc.capturing = true;
133 s_tc.trigger_cycles = pc_cycles();
134 return true;
135}
136
137void pc_tc_get_stats(pc_tc_stats *out)
138{
139 if (out)
140 {
141 *out = s_tc.stats;
142 }
143}
144
145bool pc_tc_capturing(void)
146{
147 return s_tc.configured && s_tc.capturing;
148}
149
150void pc_tc_end(void)
151{
152 s_tc.configured = false;
153 s_tc.capturing = false;
154}
155
156#endif // PC_ENABLE_TRACE_CAPTURE
Pluggable monotonic clock for all library timing.
uint32_t pc_cycles(void)
Free-running CPU cycle count. ISR-safe. On ARDUINO/ESP32 this is the hardware cycle counter (CCOUNT);...
Definition clock.h:248
#define PC_TC_MAX_WINDOW_SAMPLES
Max samples a window may hold (pretrigger_samples + posttrigger_samples), static-allocated.
Pre/post-trigger sample-window assembler (PC_ENABLE_TRACE_CAPTURE) - the v5 high-rate acquisition pri...