trigger: a phosphor display keeps the last N acquisitions; and a Force button
Two changes in the trigger pane, landed together. **Persistence.** Trigger mode keeps the last N published acquisitions and draws them fading toward the background, N being a panel control (1..8, default 1) with N=1 meaning today's behaviour exactly, so the knob is the switch. The layers are the accumulate path's own -- one layer per (signal, age) in the row's free trace slots, named with the accumulate vocabulary so `layer_base_name()` strips the suffix and `signal_color()` resolves the base signal -- so a layer is not a signal: the signal list, the colours and the measurements see one row per signal. `ScopeModel` retains the outgoing snapshot in seven atomic `shared_ptr` slots (no lock, no copy; the snapshot already exists) and a layer's channel is resolved **by name inside the snapshot**, because the channel list is renumbered by any structural change since that acquisition was cut. `RowSet::sync_layers()` re-plans from the rows and the depth alone, so a latch costs no rebuild, and `event_data_window()` skips layer slots so an older acquisition can never re-pin the axis. The schedule is 0.5^age -- the acquisition on screen at full brightness and each older one half the previous, independent of N, so raising the depth adds dimmer acquisitions rather than rescaling the ones already on screen. Memory is bounded by the ring's own `--buffer-mb` rather than by the depth, which is the bound that cannot be driven into an OOM, and the panel says when either bound bites. This also wired up `set_trace_alpha()`, which the accumulate path was setting and nothing was reading -- so the documented 0.35 translucency of an accumulation was opaque in fact. **Force.** In the trigger pane beside Arm/Disarm, shown only while trigger mode is on: one acquisition now, with the configured window. It is a synthetic edge rather than a second publisher -- the engine opens one capture at the present moment and everything else is the ordinary latch, so a forced acquisition is cut, pinned, framed, recorded and cached exactly like a latched one, and the button owns no window and no publish. The pre-trigger gate is deliberately skipped (capture now is a request for the history that exists, and the snapshot reports its own shorter pre_s), a capture already filling is refused rather than restarted, and the state afterwards is the mode's: Normal stays armed, Single disarms. The request is one atomic flag written under the model mutex with `armed` -- the producer reads `armed` at the top of a packet and the request at the bottom, so a request written between the two would be dropped -- and consumed at the end of a packet, after the channel loop and before the pending publish. **A defect fixed in passing, in the function Force was modelled on:** `on_arm_clicked()` passed `TriggerConfig::channel` (an index into `channels()`) to `set_signal_enabled()` (an index into `signals()`). With one signal acquired, arming switched on the stream's clock signal and left the watched signal untouched -- and since a signal the panel was not showing is a change, it rebuilt the buffer under the click, dropping the very pre-trigger window the arming, and now the Force, was about to capture. Measured before: `0.0100 s of history behind it`; after: `2.5299 s`. `trigger_signal_index()` is now the one resolution, used by both buttons.
This commit is contained in:
1 parent
fc4700d1c9
commit
e14e1ee069
22 files changed
+1652
-26
No files matched your search
@@ -1214,6 +1214,30 @@ back to the *previous* event, or to blank if this was the first. No event fired,
|
||||
latch counter does not move; a separate `ScopeModel::event_seq()` counts published
|
||||
snapshots, and that is what the frame tick keys its framing on.
|
||||
|
||||
**Force takes one acquisition now.** The trigger panel's **Force** button, beside
|
||||
Arm/Disarm, is the scope's "capture this instant": for the condition that is not coming,
|
||||
it opens a capture at the present moment with the configured pre/post window rather than
|
||||
waiting for an edge. It is deliberately *not* a second publisher — `TriggerEngine::force()`
|
||||
only puts the engine into the pending state an edge leaves it in, with the newest stored
|
||||
stamp as the event's own time, and the ordinary latch does everything else: the cut, the
|
||||
publish, the latch count, the marker at x = 0 and the framing. A forced acquisition is
|
||||
therefore framed, pinned and recorded exactly like a latched one, down to the axis being
|
||||
pinned from the snapshot's own stamps rather than from what was asked for (a request the
|
||||
ring cannot reach back to — a 100 s window at 99 % pre — comes out seconds wide, which is
|
||||
what the probe's second leg reads).
|
||||
|
||||
Two choices are worth naming. The engine's pre-window gate is **skipped**: the history
|
||||
behind "now" is whatever the ring holds, and refusing to capture it because the ring is
|
||||
young would refuse the one thing the button is for. And a capture already filling is **not**
|
||||
restarted — it is the acquisition being asked for, and restarting it would throw away the
|
||||
post window it has collected; the click is ignored (and said so on stderr) in that state
|
||||
only. What the trigger *is* afterwards is still the mode's decision, made where it always
|
||||
was: Normal leaves it armed for the next edge, Single disarms, because one acquisition is
|
||||
what the mode means whether an edge produced it or the button. The button lives in the body
|
||||
of the pane rather than in the locked pane's heading beside Arm/Disarm: the heading's one
|
||||
control is the runtime *state* a locked pane exists to change, while a force is an action
|
||||
whose subject — the condition, the level, the pre/post split and the mode — is in the body.
|
||||
|
||||
The frame that **frames** a snapshot is the one that consumes its sequence number, not
|
||||
merely the first frame to see it. The tick reads `display_mode()` before it takes the
|
||||
model's mutex, while the producer publishes the snapshot inside that mutex and only lowers
|
||||
@@ -1233,6 +1257,30 @@ snapshot's own stamps. That re-basing machinery already existed (it was written
|
||||
frozen view and then never needed, since snapshots carry edge-relative stamps); this is
|
||||
what finally uses it.
|
||||
|
||||
**Persistence: the last N acquisitions, dimmer with age.** Trigger mode can keep the last
|
||||
`N` acquisitions on screen at once, each older one drawn at half the brightness of the one before
|
||||
it (1, 0.5, 0.25, 0.125, …), which is what a phosphor display does with successive sweeps: they
|
||||
are aligned on their own trigger edge, so the shape of the event repeats and whatever varies
|
||||
between acquisitions — jitter, ringing, an occasional glitch — reads as a faint ghost behind the
|
||||
current trace. <gui>Persistence</gui> in the trigger panel is the knob; `1`, the default, is
|
||||
exactly the display without it, and the range stops at 8, where the eighth layer would be 0.8 %
|
||||
ink that nothing can see. The older acquisitions are drawn *behind* the current one and are never
|
||||
pinned: the axis, the framing and every measurement still describe the event on screen.
|
||||
`N = 1` retains nothing at all — no copy, no layer — and lowering the depth frees the retained
|
||||
snapshots there and then, because a trigger that has stopped firing never reaches another publish.
|
||||
|
||||
**And it says what it costs.** A retained acquisition is a whole window copied out of the ring, so
|
||||
the retention is bounded by two things: the depth, and **`--buffer-mb`** — persistence may hold at
|
||||
most as much again as the buffer does, which is the bound a long window on a fast stream reaches
|
||||
long before the eighth layer. The other bound is the plot's own: a layer takes a *free trace slot
|
||||
in the row that plots its signal*, layer by layer, so a row of four signals — the layout the app
|
||||
starts with — has room for one older acquisition of each, while a row per signal has room for all
|
||||
seven. A depth that the room or the budget cuts short is counted and said in a notice, rather than
|
||||
quietly drawing fewer layers than were asked for. The layers are *not* signals: they hold no
|
||||
channel, so the signal list, the colour chips, the calibration table, the layout file and every
|
||||
measurement see one signal where the plot draws several traces of it — the same rule the event
|
||||
*pane*'s accumulate view follows.
|
||||
|
||||
**The whole trigger path measures the window in stream seconds, and never in
|
||||
samples.** `TriggerEngine` is fed each sample's timestamp and latches once that clock
|
||||
has advanced past the post window; `publish_event()` then finds the slice covering
|
||||
|
||||
Reference in new issue
Block a user