adds a triggered scope, closes #571

This commit is contained in:
hneemann 2020-12-05 10:46:40 +01:00
parent fa42431b05
commit ac0d9db8be
5 changed files with 172 additions and 0 deletions

View File

@ -27,6 +27,7 @@ import de.neemann.digital.draw.elements.Tunnel;
import de.neemann.digital.draw.shapes.ShapeFactory;
import de.neemann.digital.gui.Settings;
import de.neemann.digital.gui.components.data.DummyElement;
import de.neemann.digital.gui.components.data.ScopeTrigger;
import de.neemann.digital.gui.components.graphics.GraphicCard;
import de.neemann.digital.gui.components.graphics.LedMatrix;
import de.neemann.digital.gui.components.graphics.VGA;
@ -130,6 +131,7 @@ public class ElementLibrary implements Iterable<ElementLibrary.ElementContainer>
.add(DummyElement.TEXTDESCRIPTION)
.add(Probe.DESCRIPTION)
.add(DummyElement.DATADESCRIPTION)
.add(ScopeTrigger.DESCRIPTION)
.add(new LibraryNode(Lang.get("lib_displays"))
.add(RGBLED.DESCRIPTION)
.add(Out.POLARITYAWARELEDDESCRIPTION)

View File

@ -0,0 +1,142 @@
/*
* Copyright (c) 2020 Helmut Neemann.
* Use of this source code is governed by the GPL v3 license
* that can be found in the LICENSE file.
*/
package de.neemann.digital.gui.components.data;
import de.neemann.digital.core.*;
import de.neemann.digital.core.element.Element;
import de.neemann.digital.core.element.ElementAttributes;
import de.neemann.digital.core.element.ElementTypeDescription;
import de.neemann.digital.core.element.Keys;
import de.neemann.digital.data.Value;
import de.neemann.digital.data.ValueTable;
import de.neemann.digital.draw.elements.PinException;
import de.neemann.digital.gui.Main;
import de.neemann.digital.gui.components.OrderMerger;
import de.neemann.digital.lang.Lang;
import de.neemann.digital.testing.parser.TestRow;
import javax.swing.*;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.ArrayList;
import java.util.List;
import static de.neemann.digital.core.element.PinInfo.input;
/**
* The ScopeElement
*/
public class ScopeTrigger extends Node implements Element {
/**
* The ScopeElement description
*/
public static final ElementTypeDescription DESCRIPTION =
new ElementTypeDescription(ScopeTrigger.class, input("T").setClock())
.addAttribute(Keys.LABEL)
.addAttribute(Keys.MAX_STEP_COUNT);
private final int maxSize;
private final String label;
private ObservableValue clockValue;
private boolean lastClock;
private ValueTable logData;
private ArrayList<Signal> signals;
private Model model;
private GraphDialog graphDialog;
private boolean clockHasChanged;
private ScopeModelStateObserver scopeModelStateObserver;
/**
* Creates a new instance
*
* @param attr the elements attributes
*/
public ScopeTrigger(ElementAttributes attr) {
label = attr.getLabel();
maxSize = attr.get(Keys.MAX_STEP_COUNT);
}
@Override
public void setInputs(ObservableValues inputs) throws NodeException {
clockValue = inputs.get(0).checkBits(1, this).addObserverToValue(this);
}
@Override
public void readInputs() throws NodeException {
boolean clock = clockValue.getBool();
if (clock != lastClock)
clockHasChanged = true;
lastClock = clock;
}
@Override
public void writeOutputs() throws NodeException {
}
@Override
public ObservableValues getOutputs() throws PinException {
return ObservableValues.EMPTY_LIST;
}
@Override
public void init(Model model) throws NodeException {
signals = model.getSignalsCopy();
this.model = model;
signals.removeIf(signal -> !signal.isShowInGraph());
String[] names = new String[signals.size()];
for (int i = 0; i < signals.size(); i++)
names[i] = signals.get(i).getName();
JFrame m = model.getWindowPosManager().getMainFrame();
if (m instanceof Main) {
List<String> ordering = ((Main) m).getCircuitComponent().getCircuit().getMeasurementOrdering();
new OrderMerger<String, Signal>(ordering) {
@Override
public boolean equals(Signal a, String b) {
return a.getName().equals(b);
}
}.order(signals);
}
this.logData = new ValueTable(names).setMaxSize(maxSize);
scopeModelStateObserver = new ScopeModelStateObserver();
model.addObserver(scopeModelStateObserver, ModelEventType.STEP);
}
private final class ScopeModelStateObserver implements ModelStateObserver {
@Override
public void handleEvent(ModelEvent event) {
if (clockHasChanged && event.getType() == ModelEventType.STEP) {
Value[] sample = new Value[logData.getColumns()];
for (int i = 0; i < logData.getColumns(); i++)
sample[i] = new Value(signals.get(i).getValue());
logData.add(new TestRow(sample));
clockHasChanged = false;
if (graphDialog == null || !graphDialog.isVisible()) {
SwingUtilities.invokeLater(() -> {
String title = label;
if (title.isEmpty())
title = Lang.get("elem_ScopeTrigger_short");
graphDialog = new GraphDialog(model.getWindowPosManager().getMainFrame(), title, logData);
graphDialog.addWindowListener(new WindowAdapter() {
@Override
public void windowClosed(WindowEvent e) {
model.modify(() -> model.removeObserver(scopeModelStateObserver));
}
});
graphDialog.setVisible(true);
model.getWindowPosManager().register("Scope_" + label, graphDialog);
});
}
}
}
}
}

View File

@ -243,6 +243,16 @@
Es können sowohl komplette Taktschritte als auch einzelne Gatter-Veränderungen angezeigt werden.
Hat keine weitere Funktion für die Simulation.
</string>
<string name="elem_ScopeTrigger">Getriggerter Messwertgraph</string>
<string name="elem_ScopeTrigger_short">Scope</string>
<string name="elem_ScopeTrigger_tt">Zeigt einen Messwertgraph, wobei nur dann Messwerte gespeichert werden,
wenn sich das Eingangssignal verändert.
Das Speichern findet statt, wenn sich die Schaltung stabilisiert hat.
Der Trigger startet nicht die Messung wie bei einem echten Scope, sondern jedes Triggerereignis
speichert einen einzigen Messwert für jedes der angezeigten Signale.
</string>
<string name="elem_ScopeTrigger_pin_T">Ein Änderung an diesem Eingang veranlasst das Speichern von Messwerten.</string>
<string name="elem_RotEncoder">Drehencoder</string>
<string name="elem_RotEncoder_tt">Drehknopf mit Drehencoder zur Erfassung einer Drehbewegung.</string>
<string name="elem_RotEncoder_pin_A">Encodersignal A</string>

View File

@ -247,6 +247,15 @@
You can plot complete clock cycles or single gate changes.
Does not affect the simulation.
</string>
<string name="elem_ScopeTrigger">Triggered Data Graph</string>
<string name="elem_ScopeTrigger_short">Scope</string>
<string name="elem_ScopeTrigger_tt">Shows a graph of measured values, whereby measured values are only stored if
the input signal changes. Storing takes place when the circuit has stabilized.
The trigger does not start the measurement like in a real scope, but each trigger event stores a single
measurement value for each of the shown signals.
</string>
<string name="elem_ScopeTrigger_pin_T">A change at this input causes measured values to be stored.</string>
<string name="elem_RotEncoder">Rotary Encoder</string>
<string name="elem_RotEncoder_tt">Rotary knob with rotary encoder. Used to detect rotational movements.</string>
<string name="elem_RotEncoder_pin_A">encoder signal A</string>

View File

@ -682,6 +682,11 @@
</elementAttributes>
<pos x="800" y="480"/>
</visualElement>
<visualElement>
<elementName>ScopeTrigger</elementName>
<elementAttributes/>
<pos x="820" y="940"/>
</visualElement>
</visualElements>
<wires>
<wire>
@ -940,6 +945,10 @@
<p1 x="580" y="940"/>
<p2 x="800" y="940"/>
</wire>
<wire>
<p1 x="800" y="940"/>
<p2 x="820" y="940"/>
</wire>
<wire>
<p1 x="280" y="940"/>
<p2 x="300" y="940"/>