Initial commit
This commit is contained in:
commit
8059b90de7
29 files changed
+7893
No files matched your search
+112
@@ -0,0 +1,112 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// SPDX-FileCopyrightText: 2026 Martino Ferrari
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace udpscope {
|
||||
|
||||
/// Which way the trigger signal crosses the level.
|
||||
enum class TriggerEdge { Rising, Falling, Both };
|
||||
|
||||
/// How an armed trigger re-arms after it fired.
|
||||
enum class TriggerMode { Normal, Single };
|
||||
|
||||
/// The trigger panel's configuration (spec item 2.2).
|
||||
///
|
||||
/// The trigger names a *signal* (an index into `ScopeModel::signals()`), not a
|
||||
/// channel: the panel lists every signal the stream describes, whether or not it
|
||||
/// is being acquired, and picking one is what arms the trigger on it. The
|
||||
/// acquisition resolves the signal to the channel carrying it.
|
||||
struct TriggerConfig {
|
||||
bool enabled = false; ///< trigger mode on/off (toolbar).
|
||||
bool armed = false; ///< arm/disarm button.
|
||||
int channel = -1; ///< index into ScopeModel::channels(), -1 = none.
|
||||
double level = 0.0;
|
||||
TriggerEdge edge = TriggerEdge::Rising;
|
||||
double pretrigger_pct = 50.0; ///< share of the buffer shown before the event.
|
||||
double holdoff_s = 0.010; ///< default from the spec.
|
||||
TriggerMode mode = TriggerMode::Normal;
|
||||
|
||||
/// Seconds of post-trigger data to collect before latching the event, given
|
||||
/// the buffer's span.
|
||||
double post_span_s(double buffer_span_s) const;
|
||||
};
|
||||
|
||||
/// The producer-side trigger state machine. Fed one sample at a time in stream
|
||||
/// order; it says when the event buffer should be latched.
|
||||
class TriggerEngine {
|
||||
public:
|
||||
/// Configures the channel and the post-trigger window. Resets the state when the
|
||||
/// channel changes.
|
||||
///
|
||||
/// `post_seconds` is the post-trigger window in **stream seconds**, the same clock
|
||||
/// `process()` is fed and the same clock the x axis reads, so the capture covers
|
||||
/// exactly that much axis by construction.
|
||||
///
|
||||
/// It used to be a count of samples, converted from seconds by a measured estimate
|
||||
/// of the channel's cadence. That estimate is what made the window wrong: when it
|
||||
/// collapsed (any stream whose time signal advances in large steps drove it to the
|
||||
/// 1 S/s floor) the count came out as 1, the engine latched on the edge sample
|
||||
/// itself, and the event had no post-trigger data at all. Seconds need no estimate.
|
||||
///
|
||||
/// `pre_seconds` is the pre-trigger window, and `oldest_s` the stream time of the
|
||||
/// oldest sample the buffer still holds. Together they gate the edge: a capture is
|
||||
/// only allowed once the history reaches back a full `pre_seconds` before it, so an
|
||||
/// event cannot latch onto a half-filled pre window. `oldest_s` has to be the real
|
||||
/// buffer's, not a timer since arming -- the ring keeps filling with the trigger
|
||||
/// off, so the history is usually already there and waiting would be wrong.
|
||||
void configure(const TriggerConfig& cfg, double post_seconds, double pre_seconds,
|
||||
double oldest_s);
|
||||
/// Disarms without forgetting the configuration.
|
||||
void disarm() noexcept { armed_ = false; pending_ = false; }
|
||||
void arm(double now_s) noexcept;
|
||||
|
||||
/// Feeds one sample of the trigger channel. `now_s` is the sample's timestamp
|
||||
/// in stream seconds. Returns true when the event has just been latched.
|
||||
bool process(double value, double now_s) noexcept;
|
||||
|
||||
bool pending() const noexcept { return pending_; }
|
||||
bool armed() const noexcept { return armed_; }
|
||||
|
||||
/// Diagnostics for the capture state machine: how many samples have landed in
|
||||
/// the post-trigger window, the window it is filling (in stream seconds), how
|
||||
/// much of that has elapsed, and the last value fed.
|
||||
std::uint64_t post_accum() const noexcept { return post_accum_count_; }
|
||||
double post_seconds() const noexcept { return post_seconds_; }
|
||||
double post_elapsed() const noexcept {
|
||||
return pending_ ? last_t_s_ - pending_start_s_ : 0.0;
|
||||
}
|
||||
/// How much history the buffer holds behind the newest sample, against the
|
||||
/// pre-trigger window an edge needs before it may open a capture.
|
||||
double history() const noexcept { return last_t_s_ - oldest_s_; }
|
||||
double pre_seconds() const noexcept { return pre_seconds_; }
|
||||
double last_value() const noexcept { return prev_; }
|
||||
bool have_last() const noexcept { return have_prev_; }
|
||||
double last_t() const noexcept { return last_t_s_; }
|
||||
double holdoff_until() const noexcept { return holdoff_until_s_; }
|
||||
|
||||
/// Stream time of the edge that armed the event now on screen. Valid once
|
||||
/// `process()` has fired; the view is centred on this instant (pre-trigger share
|
||||
/// before it, post-trigger share after), so it is the x a trigger marker belongs
|
||||
/// at. Unchanged while a later event has not fired yet.
|
||||
double last_trigger_time() const noexcept { return last_trigger_s_; }
|
||||
|
||||
private:
|
||||
TriggerConfig cfg_{};
|
||||
bool armed_ = false;
|
||||
bool have_prev_ = false;
|
||||
double prev_ = 0.0;
|
||||
bool pending_ = false; ///< an edge was seen; collecting the post window
|
||||
double pending_start_s_ = 0.0;
|
||||
double holdoff_until_s_ = 0.0;
|
||||
std::uint64_t post_accum_count_ = 0;
|
||||
double post_seconds_ = 0.0; ///< post-trigger window, in stream seconds.
|
||||
double pre_seconds_ = 0.0; ///< pre-trigger window an edge needs behind it.
|
||||
double oldest_s_ = 0.0; ///< stream time of the oldest sample still buffered.
|
||||
double last_t_s_ = 0.0;
|
||||
double last_trigger_s_ = 0.0;
|
||||
};
|
||||
|
||||
} // namespace udpscope
|
||||
Reference in new issue
Block a user