Skip to content

Commit 9f21b26

Browse files
authored
Add runtime switching of pin logging, with export to VCD file (#8)
* Add option to enable/disable/reset pin logging at runtime * Add ability to export one of a regex of signals
1 parent 23a41b7 commit 9f21b26

File tree

4 files changed

+187
-64
lines changed

4 files changed

+187
-64
lines changed

library.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@
2020
"dependencies":
2121
{
2222
"imgui": "https://github.com/p3p/imgui.git#pio_docking",
23-
"implot": "https://github.com/p3p/implot.git#pio_master"
23+
"implot": "https://github.com/p3p/implot.git#pio_master",
24+
"vcd-writer": "https://github.com/favorart/vcd-writer"
2425
},
2526
"build": {
2627
"libLDFMode": "deep"

src/MarlinSimulator/application.cpp

Lines changed: 145 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@
88
#include "../HAL.h"
99
#include <src/MarlinCore.h>
1010
#include <src/pins/pinsDebug.h>
11+
#include <fstream>
12+
#include <vcd_writer.h>
13+
#include <regex>
1114

1215
Application::Application() {
1316
sim.vis.create();
@@ -72,67 +75,157 @@ Application::Application() {
7275
});
7376

7477
user_interface.addElement<UiWindow>("Signal Analyser", [this](UiWindow* window){
75-
struct ScrollingData {
76-
int MaxSize;
77-
int Offset;
78-
ImVector<ImPlotPoint> Data;
79-
ScrollingData() {
80-
MaxSize = 100000;
81-
Offset = 0;
82-
Data.reserve(MaxSize);
78+
if (!Gpio::isLoggingEnabled()) {
79+
if (ImGui::Button("Enable Pin Logging")) {
80+
Gpio::setLoggingEnabled(true);
8381
}
84-
void AddPoint(double x, double y) {
85-
if (Data.size() < MaxSize)
86-
Data.push_back(ImPlotPoint(x,y));
87-
else {
88-
Data[Offset] = ImPlotPoint(x,y);
89-
Offset = (Offset + 1) % MaxSize;
90-
}
82+
}
83+
else {
84+
if (ImGui::Button("Disable Pin Logging")) {
85+
Gpio::setLoggingEnabled(false);
86+
}
87+
ImGui::SameLine();
88+
if (ImGui::Button("Reset logs")) {
89+
Gpio::resetLogs();
9190
}
92-
void Erase() {
93-
if (Data.size() > 0) {
94-
Data.shrink(0);
95-
Offset = 0;
91+
92+
struct ScrollingData {
93+
int MaxSize;
94+
int Offset;
95+
ImVector<ImPlotPoint> Data;
96+
ScrollingData() {
97+
MaxSize = 100000;
98+
Offset = 0;
99+
Data.reserve(MaxSize);
100+
}
101+
void AddPoint(double x, double y) {
102+
if (Data.size() < MaxSize)
103+
Data.push_back(ImPlotPoint(x,y));
104+
else {
105+
Data[Offset] = ImPlotPoint(x,y);
106+
Offset = (Offset + 1) % MaxSize;
107+
}
108+
}
109+
void Erase() {
110+
if (Data.size() > 0) {
111+
Data.shrink(0);
112+
Offset = 0;
113+
}
114+
}
115+
};
116+
117+
static pin_type monitor_pin = X_STEP_PIN;
118+
static const char* label = "Select Pin";
119+
static char* active_label = (char *)label;
120+
if(ImGui::BeginCombo("##Select Pin", active_label)) {
121+
for (auto p : pin_array) {
122+
if (ImGui::Selectable(p.name, p.pin == monitor_pin)) {
123+
monitor_pin = p.pin;
124+
active_label = (char *)p.name;
96125
}
126+
if (p.pin == monitor_pin) ImGui::SetItemDefaultFocus();
127+
}
128+
ImGui::EndCombo();
97129
}
98-
};
99-
100-
static pin_type monitor_pin = X_STEP_PIN;
101-
static const char* label = "Select Pin";
102-
static char* active_label = (char *)label;
103-
if(ImGui::BeginCombo("##Select Pin", active_label)) {
104-
for (auto p : pin_array) {
105-
if (ImGui::Selectable(p.name, p.pin == monitor_pin)) {
106-
monitor_pin = p.pin;
107-
active_label = (char *)p.name;
130+
131+
if (Gpio::pin_map[monitor_pin].event_log.size()) {
132+
ScrollingData sdata;
133+
134+
pin_log_data last{};
135+
for (auto v : Gpio::pin_map[monitor_pin].event_log) {
136+
if (last.timestamp) sdata.AddPoint(v.timestamp, last.value);
137+
sdata.AddPoint(v.timestamp, v.value);
138+
last = v;
139+
}
140+
sdata.AddPoint(Kernel::SimulationRuntime::nanos(), last.value);
141+
142+
static float window = 10000000000.0f;
143+
ImGui::SliderFloat("Window", &window, 10.f, 100000000000.f,"%.0f ns", ImGuiSliderFlags_Logarithmic);
144+
static float offset = 0.0f;
145+
ImGui::SliderFloat("X offset", &offset, 0.f, 10000000000.f,"%.0f ns");
146+
ImGui::SliderFloat("X offset", &offset, 0.f, 100000000000.f,"%.0f ns");
147+
if (!ImPlot::GetCurrentContext()) ImPlot::CreateContext();
148+
ImPlot::SetNextPlotLimitsX(Kernel::SimulationRuntime::nanos() - window - offset, Kernel::SimulationRuntime::nanos() - offset, ImGuiCond_Always);
149+
ImPlot::SetNextPlotLimitsY(0.0f, 1.2f, ImGuiCond_Always);
150+
static int rt_axis = ImPlotAxisFlags_NoTickLabels | ImPlotAxisFlags_LockMin;
151+
if (ImPlot::BeginPlot("##Scrolling", "Time (ns)", NULL, ImVec2(-1,150), ImPlotAxisFlags_NoTickLabels | ImPlotFlags_Query, rt_axis, rt_axis)) {
152+
ImPlot::PlotLine("pin", &sdata.Data[0].x, &sdata.Data[0].y, sdata.Data.size(), sdata.Offset, sizeof(ImPlotPoint));
153+
ImPlot::EndPlot();
108154
}
109-
if (p.pin == monitor_pin) ImGui::SetItemDefaultFocus();
110155
}
111-
ImGui::EndCombo();
112-
}
113156

114-
if (Gpio::pin_map[monitor_pin].event_log.size()) {
115-
ScrollingData sdata;
157+
static bool export_single_pin = false;
158+
if (ImGui::Button("Export selected pin to file")) {
159+
export_single_pin = true;
160+
ImGuiFileDialog::Instance()->OpenDialog("PulseExportDlgKey", "Choose File", "Value Change Dump (*.vcd){.vcd},.*", ".");
161+
}
162+
116163

117-
pin_log_data last{};
118-
for (auto v : Gpio::pin_map[monitor_pin].event_log) {
119-
if (last.timestamp) sdata.AddPoint(v.timestamp, last.value);
120-
sdata.AddPoint(v.timestamp, v.value);
121-
last = v;
164+
if (ImGui::Button("Export pins matching regex to file")) {
165+
export_single_pin = false;
166+
ImGuiFileDialog::Instance()->OpenDialog("PulseExportDlgKey", "Choose File", "Value Change Dump (*.vcd){.vcd},.*", ".");
167+
}
168+
169+
static char export_regex[128] = "";
170+
ImGui::SameLine();
171+
ImGui::InputText("Pin regex", export_regex, sizeof(export_regex));
172+
173+
if (ImGuiFileDialog::Instance()->Display("PulseExportDlgKey", ImGuiWindowFlags_NoDocking)) {
174+
try{
175+
if (ImGuiFileDialog::Instance()->IsOk()) {
176+
std::string image_filename = ImGuiFileDialog::Instance()->GetFilePathName();
177+
178+
using namespace vcd;
179+
180+
HeadPtr head = makeVCDHeader(static_cast<TimeScale>(50), TimeScaleUnit::ns, utils::now());
181+
VCDWriter writer{image_filename, std::move(head)};
182+
183+
if (export_single_pin) {
184+
std::string pin_name(active_label);
185+
auto scope = pin_name.substr(0, pin_name.find_first_of('_'));
186+
auto var = writer.register_var(scope, pin_name, VariableType::wire, 1);
187+
188+
if (Gpio::pin_map[monitor_pin].event_log.size() && pin_array[monitor_pin].is_digital) {
189+
for (const auto &value : Gpio::pin_map[monitor_pin].event_log) {
190+
writer.change(var, value.timestamp / 50, utils::format("%u", value.value));
191+
}
192+
}
193+
}
194+
else {
195+
std::map<size_t, VarPtr> pin_to_var_map;
196+
std::regex expression(export_regex);
197+
198+
for (auto pin : pin_array) {
199+
std::string pin_name(pin.name);
200+
bool regex_match = strlen(export_regex) == 0 || std::regex_search(pin_name, expression);
201+
auto scope = pin_name.substr(0, pin_name.find_first_of('_'));
202+
if (pin.is_digital && regex_match)
203+
pin_to_var_map[pin.pin] = writer.register_var(scope, pin_name, VariableType::wire, 1);
204+
}
205+
206+
std::multimap<uint64_t, std::pair<pin_t, uint16_t> > timestamp_pin_change_map;
207+
208+
for (auto pin : pin_array) {
209+
if (pin.is_digital && pin_to_var_map.find(pin.pin) != pin_to_var_map.end())
210+
for (const auto &data : Gpio::pin_map[pin.pin].event_log)
211+
{
212+
timestamp_pin_change_map.emplace(std::make_pair(data.timestamp, std::make_pair(pin.pin, data.value)));
213+
}
214+
}
215+
216+
auto timestamp_offset = timestamp_pin_change_map.begin()->first;
217+
for (const auto &timestamp : timestamp_pin_change_map) {
218+
writer.change(pin_to_var_map[timestamp.second.first], (timestamp.first - timestamp_offset) / 50, utils::format("%u", timestamp.second.second));
219+
}
220+
}
221+
}
222+
}
223+
catch (const std::exception& e)
224+
{
225+
auto test = e.what();
226+
}
227+
ImGuiFileDialog::Instance()->Close();
122228
}
123-
sdata.AddPoint(Kernel::SimulationRuntime::nanos(), last.value);
124-
125-
static float window = 10000000000.0f;
126-
ImGui::SliderFloat("Window", &window, 10.f, 100000000000.f,"%.0f ns");
127-
static float offset = 0.0f;
128-
ImGui::SliderFloat("X offset", &offset, 0.f, 10000000000.f,"%.0f ns");
129-
// ImPlot::SetNextPlotLimitsX(Kernel::SimulationRuntime::nanos() - window - offset, Kernel::SimulationRuntime::nanos() - offset, ImGuiCond_Always);
130-
// ImPlot::SetNextPlotLimitsY(0.0f, 1.2f, ImGuiCond_Always);
131-
// static int rt_axis = ImPlotAxisFlags_NoTickLabels | ImPlotAxisFlags_LockMin;
132-
// if (ImPlot::BeginPlot("##Scrolling", "Time (ns)", NULL, ImVec2(-1,150), ImPlotAxisFlags_NoTickLabels | ImPlotFlags_Query, rt_axis, rt_axis)) {
133-
// ImPlot::PlotLine("pin", &sdata.Data[0].x, &sdata.Data[0].y, sdata.Data.size(), sdata.Offset, sizeof(ImPlotPoint));
134-
// ImPlot::EndPlot();
135-
// }
136229
}
137230
});
138231

src/MarlinSimulator/hardware/Gpio.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
#include "Gpio.h"
22

33
pin_data Gpio::pin_map[Gpio::pin_count] = {};
4-
4+
bool Gpio::logging_enabled = false;

src/MarlinSimulator/hardware/Gpio.h

Lines changed: 39 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,6 @@ struct pin_data {
7272
std::atomic_uint8_t mode;
7373
std::atomic_uint16_t value;
7474
std::vector<std::function<void(GpioEvent&)>> callbacks;
75-
bool event_log_enabled = false;
7675
std::deque<pin_log_data> event_log;
7776
};
7877

@@ -83,9 +82,13 @@ class Gpio {
8382

8483
static void set_pin_value(const pin_type pin, const uint16_t value) {
8584
if (!valid_pin(pin)) return;
86-
pin_map[pin].value = value;
87-
//pin_map[pin].event_log.push_back(pin_log_data{Kernel::TimeControl::nanos(), pin_map[pin].value});
88-
//if (pin_map[pin].event_log.size() > 100000) pin_map[pin].event_log.pop_front();
85+
if (value != pin_map[pin].value) { // Optimizes for size, but misses "meaningless" sets
86+
pin_map[pin].value = value;
87+
if (logging_enabled) {
88+
pin_map[pin].event_log.push_back(pin_log_data{Kernel::TimeControl::nanos(), pin_map[pin].value});
89+
if (pin_map[pin].event_log.size() > 100000) pin_map[pin].event_log.pop_front();
90+
}
91+
}
8992
}
9093

9194
static uint16_t get_pin_value(const pin_type pin) {
@@ -103,12 +106,16 @@ class Gpio {
103106

104107
static void set(const pin_type pin, const uint16_t value) {
105108
if (!valid_pin(pin)) return;
106-
GpioEvent::Type evt_type = value > 1 ? GpioEvent::SET_VALUE : value > pin_map[pin].value ? GpioEvent::RISE : value < pin_map[pin].value ? GpioEvent::FALL : GpioEvent::NOP;
107-
pin_map[pin].value = value;
108-
GpioEvent evt(Kernel::TimeControl::getTicks(), pin, evt_type);
109-
//pin_map[pin].event_log.push_back(pin_log_data{Kernel::TimeControl::nanos(), pin_map[pin].value});
110-
//if (pin_map[pin].event_log.size() > 100000) pin_map[pin].event_log.pop_front();
111-
for (auto callback : pin_map[pin].callbacks) callback(evt);
109+
if (value != pin_map[pin].value) { // Optimizes for size, but misses "meaningless" sets
110+
GpioEvent::Type evt_type = value > 1 ? GpioEvent::SET_VALUE : value > pin_map[pin].value ? GpioEvent::RISE : value < pin_map[pin].value ? GpioEvent::FALL : GpioEvent::NOP;
111+
pin_map[pin].value = value;
112+
GpioEvent evt(Kernel::TimeControl::getTicks(), pin, evt_type);
113+
if (logging_enabled) {
114+
pin_map[pin].event_log.push_back(pin_log_data{Kernel::TimeControl::nanos(), pin_map[pin].value});
115+
if (pin_map[pin].event_log.size() > 100000) pin_map[pin].event_log.pop_front();
116+
}
117+
for (auto callback : pin_map[pin].callbacks) callback(evt);
118+
}
112119
}
113120

114121
static uint16_t get(const pin_type pin) {
@@ -159,5 +166,27 @@ class Gpio {
159166
return pin_map[pin].attach(args...);
160167
}
161168

169+
static void resetLogs() {
170+
for (auto &pin : pin_map) {
171+
// Seed each pin with an initial value to ensure important edges are not the first sample.
172+
pin.event_log.clear();
173+
pin.event_log.push_back(pin_log_data{Kernel::TimeControl::nanos(), pin.value});
174+
}
175+
}
176+
177+
static void setLoggingEnabled(bool enable) {
178+
if (!logging_enabled && enable) {
179+
resetLogs();
180+
}
181+
logging_enabled = enable;
182+
}
183+
184+
static bool isLoggingEnabled() {
185+
return logging_enabled;
186+
}
187+
162188
static pin_data pin_map[pin_count];
189+
190+
private:
191+
static bool logging_enabled;
163192
};

0 commit comments

Comments
 (0)