diff --git a/src/main/java/de/neemann/digital/draw/library/ElementLibrary.java b/src/main/java/de/neemann/digital/draw/library/ElementLibrary.java index e4591b2a5..b9024d166 100644 --- a/src/main/java/de/neemann/digital/draw/library/ElementLibrary.java +++ b/src/main/java/de/neemann/digital/draw/library/ElementLibrary.java @@ -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 .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) diff --git a/src/main/java/de/neemann/digital/gui/components/data/ScopeTrigger.java b/src/main/java/de/neemann/digital/gui/components/data/ScopeTrigger.java new file mode 100644 index 000000000..1699fbb45 --- /dev/null +++ b/src/main/java/de/neemann/digital/gui/components/data/ScopeTrigger.java @@ -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 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 ordering = ((Main) m).getCircuitComponent().getCircuit().getMeasurementOrdering(); + new OrderMerger(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); + }); + } + } + } + } +} diff --git a/src/main/resources/lang/lang_de.xml b/src/main/resources/lang/lang_de.xml index 9b1a8ea0d..be89eee3b 100644 --- a/src/main/resources/lang/lang_de.xml +++ b/src/main/resources/lang/lang_de.xml @@ -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. + Getriggerter Messwertgraph + Scope + 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. + + Ein Änderung an diesem Eingang veranlasst das Speichern von Messwerten. + Drehencoder Drehknopf mit Drehencoder zur Erfassung einer Drehbewegung. Encodersignal A diff --git a/src/main/resources/lang/lang_en.xml b/src/main/resources/lang/lang_en.xml index ba30c0855..9921b6433 100644 --- a/src/main/resources/lang/lang_en.xml +++ b/src/main/resources/lang/lang_en.xml @@ -247,6 +247,15 @@ You can plot complete clock cycles or single gate changes. Does not affect the simulation. + Triggered Data Graph + Scope + 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. + + A change at this input causes measured values to be stored. + Rotary Encoder Rotary knob with rotary encoder. Used to detect rotational movements. encoder signal A diff --git a/src/test/resources/dig/backtrack/AllComponents.dig b/src/test/resources/dig/backtrack/AllComponents.dig index 927087c8b..0a55d37ad 100644 --- a/src/test/resources/dig/backtrack/AllComponents.dig +++ b/src/test/resources/dig/backtrack/AllComponents.dig @@ -682,6 +682,11 @@ + + ScopeTrigger + + + @@ -940,6 +945,10 @@ + + + +