govnovod 5
This commit is contained in:
135
main.cpp
135
main.cpp
@ -40,6 +40,12 @@ enum class StopMode {
|
||||
DiSyn2Fall
|
||||
};
|
||||
|
||||
enum class Di1Mode {
|
||||
ZeroOnChange,
|
||||
Trace,
|
||||
Ignore
|
||||
};
|
||||
|
||||
struct Config {
|
||||
std::string serial;
|
||||
std::optional<uint32_t> ip_addr;
|
||||
@ -60,6 +66,7 @@ struct Config {
|
||||
uint32_t sync_mode = X502_SYNC_DI_SYN1_RISE;
|
||||
uint32_t sync_start_mode = X502_SYNC_DI_SYN2_RISE;
|
||||
StopMode stop_mode = StopMode::DiSyn2Fall;
|
||||
Di1Mode di1_mode = Di1Mode::ZeroOnChange;
|
||||
|
||||
uint32_t recv_block_words = 32768;
|
||||
uint32_t recv_timeout_ms = 50;
|
||||
@ -307,6 +314,33 @@ std::string stop_mode_to_string(StopMode mode) {
|
||||
}
|
||||
}
|
||||
|
||||
Di1Mode parse_di1_mode(const std::string& text) {
|
||||
const std::string value = trim_copy(text);
|
||||
if ((value == "zero") || (value == "zero_on_change") || (value == "mark")) {
|
||||
return Di1Mode::ZeroOnChange;
|
||||
}
|
||||
if ((value == "trace") || (value == "plot") || (value == "stream")) {
|
||||
return Di1Mode::Trace;
|
||||
}
|
||||
if ((value == "ignore") || (value == "off") || (value == "none")) {
|
||||
return Di1Mode::Ignore;
|
||||
}
|
||||
fail("Unsupported di1 mode: " + text);
|
||||
}
|
||||
|
||||
std::string di1_mode_to_string(Di1Mode mode) {
|
||||
switch (mode) {
|
||||
case Di1Mode::ZeroOnChange:
|
||||
return "zero";
|
||||
case Di1Mode::Trace:
|
||||
return "trace";
|
||||
case Di1Mode::Ignore:
|
||||
return "ignore";
|
||||
default:
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
bool sync_uses_di_syn1(uint32_t mode) {
|
||||
return (mode == X502_SYNC_DI_SYN1_RISE) || (mode == X502_SYNC_DI_SYN1_FALL);
|
||||
}
|
||||
@ -335,6 +369,7 @@ void print_help(const char* exe_name) {
|
||||
<< " [mode:diff|comm] [range:0.2] [clock:di_syn1_rise]\n"
|
||||
<< " [start:di_syn2_rise] [stop:di_syn2_fall] [sample_clock_hz:125000|max]\n"
|
||||
<< " [internal_ref_hz:2000000]\n"
|
||||
<< " [di1:zero|trace|ignore]\n"
|
||||
<< " [duration_ms:100] [packet_limit:0] [csv:capture.csv] [svg:capture.svg]\n"
|
||||
<< " [live_html:live_plot.html] [live_json:live_plot.json]\n"
|
||||
<< " [recv_block:32768] [stats_period_ms:1000] [live_update_period_ms:1000] [svg_history_packets:50] [start_wait_ms:10000]\n"
|
||||
@ -355,6 +390,9 @@ void print_help(const char* exe_name) {
|
||||
<< " sample_clock_hz:125000 -> requested ADC sample rate; for external clock it is the expected rate\n"
|
||||
<< " sample_clock_hz:max -> with clock:internal, use the maximum ADC speed\n"
|
||||
<< " internal_ref_hz:2000000 -> internal base clock for clock:internal (1500000 or 2000000)\n"
|
||||
<< " di1:zero -> write ADC sample as 0 on each DI1 level change\n"
|
||||
<< " di1:trace -> keep ADC unchanged and store DI1 as a separate synchronous trace\n"
|
||||
<< " di1:ignore -> ignore DI1 for both zeroing and plotting\n"
|
||||
<< " stats_period_ms:1000 -> print online transfer/capture statistics every 1000 ms (0 disables)\n"
|
||||
<< " recv_block:32768 -> request up to 32768 raw 32-bit words per X502_Recv() call\n"
|
||||
<< " live_update_period_ms:1000 -> refresh live HTML/JSON no more than once per second\n"
|
||||
@ -379,8 +417,8 @@ void print_help(const char* exe_name) {
|
||||
<< " stop: frames | di_syn2_rise | di_syn2_fall\n"
|
||||
<< "\n"
|
||||
<< "This build enables synchronous DIN together with ADC. DI_SYN2 stop edges are detected\n"
|
||||
<< "inside the same input stream, packets are split continuously by DI_SYN2 edges, and if\n"
|
||||
<< "digital input 1 changes state the corresponding ADC sample is written to the buffer as 0.\n"
|
||||
<< "inside the same input stream, packets are split continuously by DI_SYN2 edges, and DI1\n"
|
||||
<< "can either zero ADC samples on change, be exported as a separate synchronous trace, or be ignored.\n"
|
||||
<< "Open live_plot.html in a browser to see the live graph update over time.\n"
|
||||
<< "The live HTML supports X/Y zoom buttons, mouse-wheel zoom, and reset.\n"
|
||||
<< "\n"
|
||||
@ -460,6 +498,10 @@ Config parse_args(int argc, char** argv) {
|
||||
cfg.stop_mode = parse_stop_mode(arg.substr(5));
|
||||
continue;
|
||||
}
|
||||
if (starts_with(arg, "di1:")) {
|
||||
cfg.di1_mode = parse_di1_mode(arg.substr(4));
|
||||
continue;
|
||||
}
|
||||
if (starts_with(arg, "sample_clock_hz:")) {
|
||||
const std::string value = trim_copy(arg.substr(16));
|
||||
cfg.sample_clock_specified = true;
|
||||
@ -784,8 +826,11 @@ bool matches_stop_edge(StopMode mode, bool prev_level, bool current_level) {
|
||||
|
||||
struct PacketAccumulator {
|
||||
std::array<std::vector<double>, 2> channels;
|
||||
std::vector<uint8_t> di1;
|
||||
std::size_t zeroed_samples = 0;
|
||||
std::size_t stored_samples = 0;
|
||||
bool pending_frame_di1_valid = false;
|
||||
uint8_t pending_frame_di1 = 0;
|
||||
|
||||
void reset(std::size_t reserve_frames, std::size_t channel_count) {
|
||||
for (std::size_t i = 0; i < channels.size(); ++i) {
|
||||
@ -795,12 +840,20 @@ struct PacketAccumulator {
|
||||
channel.reserve(reserve_frames);
|
||||
}
|
||||
}
|
||||
di1.clear();
|
||||
di1.reserve(reserve_frames);
|
||||
zeroed_samples = 0;
|
||||
stored_samples = 0;
|
||||
pending_frame_di1_valid = false;
|
||||
pending_frame_di1 = 0;
|
||||
}
|
||||
|
||||
std::size_t frame_count(std::size_t channel_count) const {
|
||||
return (channel_count <= 1U) ? channels[0].size() : std::min(channels[0].size(), channels[1].size());
|
||||
std::size_t frames = (channel_count <= 1U) ? channels[0].size() : std::min(channels[0].size(), channels[1].size());
|
||||
if (!di1.empty()) {
|
||||
frames = std::min(frames, di1.size());
|
||||
}
|
||||
return frames;
|
||||
}
|
||||
};
|
||||
|
||||
@ -982,12 +1035,13 @@ int run(const Config& cfg) {
|
||||
<< " channel 1: " << phy_channel_name(cfg.mode, cfg.ch1) << "\n"
|
||||
<< " channel 2: "
|
||||
<< ((cfg.channel_count >= 2U) ? phy_channel_name(cfg.mode, cfg.ch2) : std::string("disabled")) << "\n"
|
||||
<< " DI1 handling: " << di1_mode_to_string(cfg.di1_mode) << "\n"
|
||||
<< " ADC range: +/-" << range_to_volts(cfg.range) << " V\n"
|
||||
<< " max frames per packet per channel: " << target_frames << "\n";
|
||||
|
||||
CaptureFileWriter writer(cfg.csv_path, cfg.svg_path, cfg.live_html_path, cfg.live_json_path);
|
||||
writer.initialize_live_plot();
|
||||
writer.initialize_csv(cfg.channel_count);
|
||||
writer.initialize_csv(cfg.channel_count, cfg.di1_mode == Di1Mode::Trace);
|
||||
std::cout << " live plot html: " << writer.live_html_path() << "\n"
|
||||
<< " live plot data: " << writer.live_json_path() << "\n"
|
||||
<< " recv block words: " << cfg.recv_block_words << "\n"
|
||||
@ -1064,9 +1118,14 @@ int run(const Config& cfg) {
|
||||
<< ", ADC samples/s=" << adc_samples_per_s
|
||||
<< ", DIN samples/s=" << din_samples_per_s
|
||||
<< ", frames/s per channel=" << frames_per_ch_per_s
|
||||
<< ", packets/s=" << packets_per_s
|
||||
<< ", zeroed on DI1 change=" << zeroed_fraction << "% ("
|
||||
<< stats_zeroed_samples << "/" << stats_stored_adc_samples << ")\n";
|
||||
<< ", packets/s=" << packets_per_s;
|
||||
if (cfg.di1_mode == Di1Mode::ZeroOnChange) {
|
||||
std::cout << ", zeroed on DI1 change=" << zeroed_fraction << "% ("
|
||||
<< stats_zeroed_samples << "/" << stats_stored_adc_samples << ")";
|
||||
} else if (cfg.di1_mode == Di1Mode::Trace) {
|
||||
std::cout << ", DI1 trace=enabled";
|
||||
}
|
||||
std::cout << "\n";
|
||||
|
||||
if (!final_report) {
|
||||
stats_window_start = now;
|
||||
@ -1098,12 +1157,19 @@ int run(const Config& cfg) {
|
||||
} else {
|
||||
current_packet.channels[1].clear();
|
||||
}
|
||||
if (cfg.di1_mode == Di1Mode::Trace) {
|
||||
current_packet.di1.resize(frames);
|
||||
} else {
|
||||
current_packet.di1.clear();
|
||||
}
|
||||
|
||||
CapturePacket packet;
|
||||
packet.packet_index = static_cast<std::size_t>(total_completed_packets + 1U);
|
||||
packet.channel_count = cfg.channel_count;
|
||||
packet.has_di1_trace = (cfg.di1_mode == Di1Mode::Trace);
|
||||
packet.ch1 = std::move(current_packet.channels[0]);
|
||||
packet.ch2 = std::move(current_packet.channels[1]);
|
||||
packet.di1 = std::move(current_packet.di1);
|
||||
|
||||
const double packet_duration_ms = (1000.0 * static_cast<double>(frames)) / actual_frame_freq_hz;
|
||||
const double zeroed_fraction = (current_packet.stored_samples == 0U)
|
||||
@ -1115,9 +1181,14 @@ int run(const Config& cfg) {
|
||||
<< "Packet " << packet.packet_index
|
||||
<< ": frames/ch=" << frames
|
||||
<< ", duration_ms=" << packet_duration_ms
|
||||
<< ", close_reason=" << packet_close_reason_to_string(reason)
|
||||
<< ", zeroed_on_DI1_change=" << zeroed_fraction << "% ("
|
||||
<< current_packet.zeroed_samples << "/" << current_packet.stored_samples << ")\n";
|
||||
<< ", close_reason=" << packet_close_reason_to_string(reason);
|
||||
if (cfg.di1_mode == Di1Mode::ZeroOnChange) {
|
||||
std::cout << ", zeroed_on_DI1_change=" << zeroed_fraction << "% ("
|
||||
<< current_packet.zeroed_samples << "/" << current_packet.stored_samples << ")";
|
||||
} else if (cfg.di1_mode == Di1Mode::Trace) {
|
||||
std::cout << ", di1_trace_frames=" << packet.di1.size();
|
||||
}
|
||||
std::cout << "\n";
|
||||
|
||||
writer.append_csv_packet(packet, actual_frame_freq_hz, csv_global_frame_index);
|
||||
++total_completed_packets;
|
||||
@ -1268,12 +1339,12 @@ int run(const Config& cfg) {
|
||||
pending_din.pop_front();
|
||||
|
||||
const bool di1_level = (din_value & kE502Digital1Mask) != 0U;
|
||||
bool zero_on_di1_change = false;
|
||||
bool di1_changed = false;
|
||||
if (!di1_initialized) {
|
||||
di1_prev_level = di1_level;
|
||||
di1_initialized = true;
|
||||
} else if (di1_level != di1_prev_level) {
|
||||
zero_on_di1_change = true;
|
||||
di1_changed = true;
|
||||
di1_prev_level = di1_level;
|
||||
}
|
||||
|
||||
@ -1323,8 +1394,13 @@ int run(const Config& cfg) {
|
||||
const uint32_t lch = next_lch;
|
||||
next_lch = (next_lch + 1U) % cfg.channel_count;
|
||||
|
||||
if ((cfg.di1_mode == Di1Mode::Trace) && ((cfg.channel_count <= 1U) || (lch == 0U))) {
|
||||
current_packet.pending_frame_di1 = static_cast<uint8_t>(di1_level ? 1U : 0U);
|
||||
current_packet.pending_frame_di1_valid = true;
|
||||
}
|
||||
|
||||
double stored_value = adc_value;
|
||||
if (zero_on_di1_change) {
|
||||
if ((cfg.di1_mode == Di1Mode::ZeroOnChange) && di1_changed) {
|
||||
stored_value = 0.0;
|
||||
++total_zeroed_samples;
|
||||
++stats_zeroed_samples;
|
||||
@ -1337,6 +1413,12 @@ int run(const Config& cfg) {
|
||||
++total_stored_adc_samples;
|
||||
++stats_stored_adc_samples;
|
||||
if (lch == (cfg.channel_count - 1U)) {
|
||||
if ((cfg.di1_mode == Di1Mode::Trace) &&
|
||||
current_packet.pending_frame_di1_valid &&
|
||||
(current_packet.di1.size() < target_frames)) {
|
||||
current_packet.di1.push_back(current_packet.pending_frame_di1);
|
||||
current_packet.pending_frame_di1_valid = false;
|
||||
}
|
||||
++total_completed_frames;
|
||||
++stats_completed_frames;
|
||||
}
|
||||
@ -1382,9 +1464,13 @@ int run(const Config& cfg) {
|
||||
}
|
||||
|
||||
std::cout << "Captured " << total_completed_packets << " packet(s), "
|
||||
<< total_packet_frames << " total frames per channel kept for final SVG\n"
|
||||
<< "ADC samples forced to 0 on DI1 change: " << total_zeroed_samples << "\n"
|
||||
<< "Average stats: "
|
||||
<< total_packet_frames << " total frames per channel kept for final SVG\n";
|
||||
if (cfg.di1_mode == Di1Mode::ZeroOnChange) {
|
||||
std::cout << "ADC samples forced to 0 on DI1 change: " << total_zeroed_samples << "\n";
|
||||
} else if (cfg.di1_mode == Di1Mode::Trace) {
|
||||
std::cout << "DI1 synchronous trace exported to CSV/live plot/SVG\n";
|
||||
}
|
||||
std::cout << "Average stats: "
|
||||
<< "MB/s=" << std::fixed << std::setprecision(3)
|
||||
<< ((static_cast<double>(total_raw_words) * sizeof(uint32_t)) /
|
||||
std::max(1e-9, static_cast<double>(GetTickCount64() - capture_loop_start) / 1000.0) /
|
||||
@ -1401,12 +1487,17 @@ int run(const Config& cfg) {
|
||||
<< ", packets/s="
|
||||
<< (static_cast<double>(total_completed_packets) /
|
||||
std::max(1e-9, static_cast<double>(GetTickCount64() - capture_loop_start) / 1000.0))
|
||||
<< ", packets captured=" << total_completed_packets
|
||||
<< ", zeroed on DI1 change="
|
||||
<< ((total_stored_adc_samples == 0U)
|
||||
? 0.0
|
||||
: (100.0 * static_cast<double>(total_zeroed_samples) / static_cast<double>(total_stored_adc_samples)))
|
||||
<< "% (" << total_zeroed_samples << "/" << total_stored_adc_samples << ")\n"
|
||||
<< ", packets captured=" << total_completed_packets;
|
||||
if (cfg.di1_mode == Di1Mode::ZeroOnChange) {
|
||||
std::cout << ", zeroed on DI1 change="
|
||||
<< ((total_stored_adc_samples == 0U)
|
||||
? 0.0
|
||||
: (100.0 * static_cast<double>(total_zeroed_samples) / static_cast<double>(total_stored_adc_samples)))
|
||||
<< "% (" << total_zeroed_samples << "/" << total_stored_adc_samples << ")";
|
||||
} else if (cfg.di1_mode == Di1Mode::Trace) {
|
||||
std::cout << ", DI1 trace=enabled";
|
||||
}
|
||||
std::cout << "\n"
|
||||
<< "Final SVG packets retained in memory: " << svg_packets.size() << "\n"
|
||||
<< "CSV: " << cfg.csv_path << "\n"
|
||||
<< "SVG: " << cfg.svg_path << "\n";
|
||||
|
||||
Reference in New Issue
Block a user