#ifdef _WIN32 #ifndef NOMINMAX #define NOMINMAX #endif #endif #include #ifdef _MSC_VER #pragma warning(push) #pragma warning(disable: 4995) #endif #include "x502api.h" #include "e502api.h" #ifdef _MSC_VER #pragma warning(pop) #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace { constexpr uint32_t kLogicalChannelCount = 2; constexpr uint32_t kStartInMask = (static_cast(1U) << 15U); struct Config { std::string serial; uint32_t mode = X502_LCH_MODE_DIFF; uint32_t range = X502_ADC_RANGE_5; uint32_t ch1 = 2; uint32_t ch2 = 3; uint32_t clock_mode = X502_SYNC_DI_SYN1_RISE; double clock_hz = 2000000.0; double gate_hz = 20000.0; uint32_t windows = 1000; uint32_t plot_windows = 50; uint32_t recv_block_words = 8192; uint32_t recv_timeout_ms = 50; uint32_t clock_wait_ms = 5000; uint32_t window_wait_ms = 5000; uint32_t input_buffer_words = 262144; uint32_t input_step_words = 8192; bool pullup_syn1 = false; bool pulldown_start_in = false; std::string csv_path = "gate_capture.csv"; std::string svg_path = "gate_capture.svg"; }; struct Frame { double ch1 = 0.0; double ch2 = 0.0; }; struct WindowData { std::vector ch1; std::vector ch2; }; struct AverageTrace { std::vector ch1; std::vector ch2; std::vector counts; }; [[noreturn]] void fail(const std::string& message) { throw std::runtime_error(message); } std::string trim_copy(const std::string& text) { const auto first = text.find_first_not_of(" \t\r\n"); if (first == std::string::npos) { return {}; } const auto last = text.find_last_not_of(" \t\r\n"); return text.substr(first, last - first + 1); } bool starts_with(const std::string& value, const std::string& prefix) { return value.rfind(prefix, 0) == 0; } uint32_t parse_u32(const std::string& text, const std::string& field_name) { const std::string clean = trim_copy(text); char* end = nullptr; const auto value = std::strtoull(clean.c_str(), &end, 0); if ((end == clean.c_str()) || (*end != '\0') || (value > std::numeric_limits::max())) { fail("Invalid integer for " + field_name + ": " + text); } return static_cast(value); } double parse_double(const std::string& text, const std::string& field_name) { const std::string clean = trim_copy(text); char* end = nullptr; const double value = std::strtod(clean.c_str(), &end); if ((end == clean.c_str()) || (*end != '\0') || !std::isfinite(value)) { fail("Invalid floating point value for " + field_name + ": " + text); } return value; } uint32_t parse_range(const std::string& text) { const double value = parse_double(text, "range"); if (std::fabs(value - 10.0) < 1e-9) { return X502_ADC_RANGE_10; } if (std::fabs(value - 5.0) < 1e-9) { return X502_ADC_RANGE_5; } if (std::fabs(value - 2.0) < 1e-9) { return X502_ADC_RANGE_2; } if (std::fabs(value - 1.0) < 1e-9) { return X502_ADC_RANGE_1; } if (std::fabs(value - 0.5) < 1e-9) { return X502_ADC_RANGE_05; } if (std::fabs(value - 0.2) < 1e-9) { return X502_ADC_RANGE_02; } fail("Unsupported E-502 range: " + text); } double range_to_volts(uint32_t range) { switch (range) { case X502_ADC_RANGE_10: return 10.0; case X502_ADC_RANGE_5: return 5.0; case X502_ADC_RANGE_2: return 2.0; case X502_ADC_RANGE_1: return 1.0; case X502_ADC_RANGE_05: return 0.5; case X502_ADC_RANGE_02: return 0.2; default: fail("Unknown ADC range enum"); } } uint32_t parse_mode(const std::string& text) { const std::string value = trim_copy(text); if ((value == "diff") || (value == "differential")) { return X502_LCH_MODE_DIFF; } fail("gate_capture supports only mode:diff"); } uint32_t parse_clock_mode(const std::string& text) { const std::string value = trim_copy(text); if (value == "di_syn1_rise") { return X502_SYNC_DI_SYN1_RISE; } if (value == "di_syn1_fall") { return X502_SYNC_DI_SYN1_FALL; } fail("Only clock:di_syn1_rise or clock:di_syn1_fall are supported"); } std::string clock_mode_to_string(uint32_t mode) { switch (mode) { case X502_SYNC_DI_SYN1_RISE: return "di_syn1_rise"; case X502_SYNC_DI_SYN1_FALL: return "di_syn1_fall"; default: return "unknown"; } } std::string phy_pair_name(uint32_t phy_ch) { return "X" + std::to_string(phy_ch + 1) + "-Y" + std::to_string(phy_ch + 1); } void print_help(const char* exe_name) { std::cout << "Usage:\n" << " " << exe_name << " [serial:SN] [mode:diff] [ch1:2] [ch2:3] [range:5]\n" << " [clock:di_syn1_rise] [clock_hz:2000000] [gate_hz:20000]\n" << " [windows:1000] [plot_windows:50]\n" << " [csv:gate_capture.csv] [svg:gate_capture.svg]\n" << " [recv_block:8192] [recv_timeout_ms:50]\n" << " [clock_wait_ms:5000] [window_wait_ms:5000]\n" << " [buffer_words:262144] [step_words:8192]\n" << " [pullup_syn1] [pulldown_start_in]\n" << "\n" << "Fixed sync scheme for this utility:\n" << " DI_SYN1 -> external ADC clock\n" << " START_IN -> gate input read from synchronous digital input (DI16 mirror)\n" << " streams -> start immediately; windows are cut in software while START_IN is high\n" << "\n" << "Defaults for E-502:\n" << " mode:diff -> differential measurement only\n" << " ch1:2, ch2:3 -> X3-Y3 and X4-Y4\n" << " range:5 -> +/-5 V range\n" << " clock:di_syn1_rise\n" << " clock_hz:2000000 -> nominal external clock frequency for timing/plots\n" << " gate_hz:20000 -> nominal square gate frequency for reporting\n" << " windows:1000 -> capture 1000 complete gate windows\n" << " plot_windows:50 -> overlay first 50 windows in SVG\n" << "\n" << "Differential physical channel mapping:\n" << " 0..15 -> X1-Y1 .. X16-Y16\n" << "\n" << "Important behavior:\n" << " The program ignores a gate that is already high at startup and waits for the next\n" << " clean low->high transition. This avoids saving a partial first window.\n" << "\n" << "Recommended example:\n" << " " << exe_name << " clock:di_syn1_rise clock_hz:2000000 gate_hz:20000 windows:1000" << " csv:gate_capture.csv svg:gate_capture.svg\n"; } Config parse_args(int argc, char** argv) { Config cfg; for (int i = 1; i < argc; ++i) { const std::string arg = argv[i]; if ((arg == "help") || (arg == "--help") || (arg == "-h")) { print_help(argv[0]); std::exit(0); } if (arg == "pullup_syn1") { cfg.pullup_syn1 = true; continue; } if (arg == "pulldown_start_in") { cfg.pulldown_start_in = true; continue; } if (starts_with(arg, "serial:")) { cfg.serial = arg.substr(7); continue; } if (starts_with(arg, "mode:")) { cfg.mode = parse_mode(arg.substr(5)); continue; } if (starts_with(arg, "range:")) { cfg.range = parse_range(arg.substr(6)); continue; } if (starts_with(arg, "ch1:")) { cfg.ch1 = parse_u32(arg.substr(4), "ch1"); continue; } if (starts_with(arg, "ch2:")) { cfg.ch2 = parse_u32(arg.substr(4), "ch2"); continue; } if (starts_with(arg, "clock:")) { cfg.clock_mode = parse_clock_mode(arg.substr(6)); continue; } if (starts_with(arg, "clock_hz:")) { cfg.clock_hz = parse_double(arg.substr(9), "clock_hz"); continue; } if (starts_with(arg, "sample_clock_hz:")) { cfg.clock_hz = parse_double(arg.substr(16), "sample_clock_hz"); continue; } if (starts_with(arg, "gate_hz:")) { cfg.gate_hz = parse_double(arg.substr(8), "gate_hz"); continue; } if (starts_with(arg, "windows:")) { cfg.windows = parse_u32(arg.substr(8), "windows"); continue; } if (starts_with(arg, "plot_windows:")) { cfg.plot_windows = parse_u32(arg.substr(13), "plot_windows"); continue; } if (starts_with(arg, "recv_block:")) { cfg.recv_block_words = parse_u32(arg.substr(11), "recv_block"); continue; } if (starts_with(arg, "recv_timeout_ms:")) { cfg.recv_timeout_ms = parse_u32(arg.substr(16), "recv_timeout_ms"); continue; } if (starts_with(arg, "clock_wait_ms:")) { cfg.clock_wait_ms = parse_u32(arg.substr(14), "clock_wait_ms"); continue; } if (starts_with(arg, "window_wait_ms:")) { cfg.window_wait_ms = parse_u32(arg.substr(15), "window_wait_ms"); continue; } if (starts_with(arg, "buffer_words:")) { cfg.input_buffer_words = parse_u32(arg.substr(13), "buffer_words"); continue; } if (starts_with(arg, "step_words:")) { cfg.input_step_words = parse_u32(arg.substr(11), "step_words"); continue; } if (starts_with(arg, "csv:")) { cfg.csv_path = arg.substr(4); continue; } if (starts_with(arg, "svg:")) { cfg.svg_path = arg.substr(4); continue; } fail("Unknown argument: " + arg); } if (cfg.mode != X502_LCH_MODE_DIFF) { fail("gate_capture supports only differential inputs"); } if ((cfg.ch1 >= X502_ADC_DIFF_CH_CNT) || (cfg.ch2 >= X502_ADC_DIFF_CH_CNT)) { fail("Differential E-502 channels must be in range 0..15"); } if (cfg.ch1 == cfg.ch2) { fail("ch1 and ch2 must be different differential pairs"); } if (cfg.clock_hz <= 0.0) { fail("clock_hz must be > 0"); } if (cfg.gate_hz <= 0.0) { fail("gate_hz must be > 0"); } if (cfg.windows == 0) { fail("windows must be > 0"); } if (cfg.plot_windows == 0) { fail("plot_windows must be > 0"); } if (cfg.recv_block_words == 0) { fail("recv_block must be > 0"); } if (cfg.input_step_words == 0) { cfg.input_step_words = cfg.recv_block_words; } if (cfg.input_buffer_words < cfg.recv_block_words) { cfg.input_buffer_words = cfg.recv_block_words; } return cfg; } template Fn load_symbol(HMODULE module, const char* name) { const auto addr = GetProcAddress(module, name); if (addr == nullptr) { fail(std::string("GetProcAddress failed for symbol: ") + name); } return reinterpret_cast(addr); } struct Api { HMODULE x502_module = nullptr; HMODULE e502_module = nullptr; decltype(&X502_Create) Create = nullptr; decltype(&X502_Free) Free = nullptr; decltype(&X502_Close) Close = nullptr; decltype(&X502_GetErrorString) GetErrorString = nullptr; decltype(&X502_GetDevInfo2) GetDevInfo2 = nullptr; decltype(&X502_SetMode) SetMode = nullptr; decltype(&X502_StreamsStop) StreamsStop = nullptr; decltype(&X502_StreamsDisable) StreamsDisable = nullptr; decltype(&X502_SetSyncMode) SetSyncMode = nullptr; decltype(&X502_SetSyncStartMode) SetSyncStartMode = nullptr; decltype(&X502_SetLChannelCount) SetLChannelCount = nullptr; decltype(&X502_SetLChannel) SetLChannel = nullptr; decltype(&X502_SetAdcFreqDivider) SetAdcFreqDivider = nullptr; decltype(&X502_SetAdcInterframeDelay) SetAdcInterframeDelay = nullptr; decltype(&X502_SetDinFreqDivider) SetDinFreqDivider = nullptr; decltype(&X502_SetStreamBufSize) SetStreamBufSize = nullptr; decltype(&X502_SetStreamStep) SetStreamStep = nullptr; decltype(&X502_SetDigInPullup) SetDigInPullup = nullptr; decltype(&X502_SetExtRefFreqValue) SetExtRefFreqValue = nullptr; decltype(&X502_Configure) Configure = nullptr; decltype(&X502_StreamsEnable) StreamsEnable = nullptr; decltype(&X502_StreamsStart) StreamsStart = nullptr; decltype(&X502_Recv) Recv = nullptr; decltype(&X502_ProcessData) ProcessData = nullptr; decltype(&E502_OpenUsb) OpenUsb = nullptr; Api() { x502_module = LoadLibraryA("x502api.dll"); if (x502_module == nullptr) { fail("Cannot load x502api.dll"); } e502_module = LoadLibraryA("e502api.dll"); if (e502_module == nullptr) { fail("Cannot load e502api.dll"); } Create = load_symbol(x502_module, "X502_Create"); Free = load_symbol(x502_module, "X502_Free"); Close = load_symbol(x502_module, "X502_Close"); GetErrorString = load_symbol(x502_module, "X502_GetErrorString"); GetDevInfo2 = load_symbol(x502_module, "X502_GetDevInfo2"); SetMode = load_symbol(x502_module, "X502_SetMode"); StreamsStop = load_symbol(x502_module, "X502_StreamsStop"); StreamsDisable = load_symbol(x502_module, "X502_StreamsDisable"); SetSyncMode = load_symbol(x502_module, "X502_SetSyncMode"); SetSyncStartMode = load_symbol(x502_module, "X502_SetSyncStartMode"); SetLChannelCount = load_symbol(x502_module, "X502_SetLChannelCount"); SetLChannel = load_symbol(x502_module, "X502_SetLChannel"); SetAdcFreqDivider = load_symbol(x502_module, "X502_SetAdcFreqDivider"); SetAdcInterframeDelay = load_symbol(x502_module, "X502_SetAdcInterframeDelay"); SetDinFreqDivider = load_symbol(x502_module, "X502_SetDinFreqDivider"); SetStreamBufSize = load_symbol(x502_module, "X502_SetStreamBufSize"); SetStreamStep = load_symbol(x502_module, "X502_SetStreamStep"); SetDigInPullup = load_symbol(x502_module, "X502_SetDigInPullup"); SetExtRefFreqValue = load_symbol(x502_module, "X502_SetExtRefFreqValue"); Configure = load_symbol(x502_module, "X502_Configure"); StreamsEnable = load_symbol(x502_module, "X502_StreamsEnable"); StreamsStart = load_symbol(x502_module, "X502_StreamsStart"); Recv = load_symbol(x502_module, "X502_Recv"); ProcessData = load_symbol(x502_module, "X502_ProcessData"); OpenUsb = load_symbol(e502_module, "E502_OpenUsb"); } ~Api() { if (e502_module != nullptr) { FreeLibrary(e502_module); } if (x502_module != nullptr) { FreeLibrary(x502_module); } } }; std::string x502_error(const Api& api, int32_t err) { const char* text = api.GetErrorString ? api.GetErrorString(err) : nullptr; std::ostringstream out; out << "err=" << err; if ((text != nullptr) && (*text != '\0')) { out << " (" << text << ")"; } return out.str(); } void expect_ok(const Api& api, int32_t err, const std::string& what) { if (err != X502_ERR_OK) { fail(what + ": " + x502_error(api, err)); } } struct DeviceHandle { const Api& api; t_x502_hnd hnd = nullptr; bool opened = false; bool streams_started = false; explicit DeviceHandle(const Api& api_ref) : api(api_ref), hnd(api.Create()) { if (hnd == nullptr) { fail("X502_Create failed"); } } ~DeviceHandle() { if (hnd != nullptr) { if (streams_started) { api.StreamsStop(hnd); } if (opened) { api.Close(hnd); } api.Free(hnd); } } }; void print_device_info(const t_x502_info& info) { std::cout << "Device: " << info.name << "\n" << "Serial: " << info.serial << "\n" << "FPGA version: " << static_cast(info.fpga_ver >> 8) << "." << static_cast(info.fpga_ver & 0xFF) << "\n" << "PLDA version: " << static_cast(info.plda_ver) << "\n" << "Board revision: " << static_cast(info.board_rev) << "\n" << "MCU firmware: " << info.mcu_firmware_ver << "\n"; } AverageTrace build_average_trace(const std::vector& windows) { AverageTrace result; std::size_t max_len = 0; for (const auto& window : windows) { max_len = std::max(max_len, std::min(window.ch1.size(), window.ch2.size())); } result.ch1.assign(max_len, 0.0); result.ch2.assign(max_len, 0.0); result.counts.assign(max_len, 0U); for (const auto& window : windows) { const std::size_t len = std::min(window.ch1.size(), window.ch2.size()); for (std::size_t i = 0; i < len; ++i) { result.ch1[i] += window.ch1[i]; result.ch2[i] += window.ch2[i]; ++result.counts[i]; } } for (std::size_t i = 0; i < max_len; ++i) { if (result.counts[i] != 0U) { result.ch1[i] /= static_cast(result.counts[i]); result.ch2[i] /= static_cast(result.counts[i]); } } return result; } std::string build_polyline_points(const std::vector& data, double frame_freq_hz, std::size_t max_samples, double y_min, double y_max, double left, double top, double width, double height) { std::ostringstream out; out << std::fixed << std::setprecision(3); const std::size_t len = data.size(); const double max_time_us = (max_samples > 1) ? ((static_cast(max_samples - 1) * 1e6) / frame_freq_hz) : 1.0; const double y_span = std::max(1e-12, y_max - y_min); for (std::size_t i = 0; i < len; ++i) { const double time_us = static_cast(i) * 1e6 / frame_freq_hz; const double x = left + (time_us / max_time_us) * width; const double y = top + height - ((data[i] - y_min) / y_span) * height; out << x << "," << y << " "; } return out.str(); } void write_csv(const std::string& path, const std::vector& windows, double frame_freq_hz) { std::ofstream file(path, std::ios::binary); if (!file) { fail("Cannot open CSV for writing: " + path); } file << "window_idx,sample_in_window,time_us,ch1_v,ch2_v\n"; file << std::fixed << std::setprecision(9); for (std::size_t w = 0; w < windows.size(); ++w) { const std::size_t len = std::min(windows[w].ch1.size(), windows[w].ch2.size()); for (std::size_t i = 0; i < len; ++i) { const double time_us = static_cast(i) * 1e6 / frame_freq_hz; file << w << "," << i << "," << time_us << "," << windows[w].ch1[i] << "," << windows[w].ch2[i] << "\n"; } } } void write_svg(const std::string& path, const std::vector& windows, double frame_freq_hz, double nominal_range_v, uint32_t plot_windows, const Config& cfg) { std::ofstream file(path, std::ios::binary); if (!file) { fail("Cannot open SVG for writing: " + path); } const AverageTrace avg = build_average_trace(windows); std::size_t max_samples = 0; double min_y = std::numeric_limits::infinity(); double max_y = -std::numeric_limits::infinity(); for (const auto& window : windows) { const std::size_t len = std::min(window.ch1.size(), window.ch2.size()); max_samples = std::max(max_samples, len); for (std::size_t i = 0; i < len; ++i) { min_y = std::min(min_y, std::min(window.ch1[i], window.ch2[i])); max_y = std::max(max_y, std::max(window.ch1[i], window.ch2[i])); } } if (!std::isfinite(min_y) || !std::isfinite(max_y) || (min_y == max_y)) { min_y = -nominal_range_v; max_y = nominal_range_v; } else { const double pad = std::max(0.1, (max_y - min_y) * 0.10); min_y -= pad; max_y += pad; } if (max_samples < 2) { max_samples = 2; } const std::size_t overlay_count = std::min(plot_windows, windows.size()); const double max_time_us = (static_cast(max_samples - 1) * 1e6) / frame_freq_hz; const double width = 1500.0; const double height = 980.0; const double left = 95.0; const double right = 40.0; const double top = 55.0; const double gap = 85.0; const double bottom = 75.0; const double plot_width = width - left - right; const double panel_height = (height - top - bottom - gap) / 2.0; const double overlay_top = top; const double average_top = top + panel_height + gap; auto write_panel = [&](double panel_top, const std::string& title) { file << " \n"; for (int i = 0; i <= 10; ++i) { const double x = left + (plot_width * i / 10.0); const double y = panel_top + (panel_height * i / 10.0); file << " \n"; file << " \n"; } if ((0.0 >= min_y) && (0.0 <= max_y)) { const double zero_y = panel_top + panel_height - ((0.0 - min_y) / std::max(1e-12, max_y - min_y)) * panel_height; file << " \n"; } file << " " << title << "\n"; file << std::fixed << std::setprecision(3); for (int i = 0; i <= 10; ++i) { const double x = left + (plot_width * i / 10.0); const double t = max_time_us * i / 10.0; const double y = panel_top + panel_height - (panel_height * i / 10.0); const double v = min_y + (max_y - min_y) * i / 10.0; file << " " << t << "\n"; file << " " << v << "\n"; } }; file << "\n"; file << " \n"; file << " E-502 gate capture\n"; file << " " << phy_pair_name(cfg.ch1) << ", " << phy_pair_name(cfg.ch2) << ", clock=" << cfg.clock_hz << " Hz, gate=" << cfg.gate_hz << " Hz, windows=" << windows.size() << "\n"; write_panel(overlay_top, "Overlay of first windows"); write_panel(average_top, "Average waveform"); for (std::size_t i = 0; i < overlay_count; ++i) { const std::size_t len = std::min(windows[i].ch1.size(), windows[i].ch2.size()); if (len < 2) { continue; } const std::vector ch1(windows[i].ch1.begin(), windows[i].ch1.begin() + static_cast(len)); const std::vector ch2(windows[i].ch2.begin(), windows[i].ch2.begin() + static_cast(len)); file << " \n"; file << " \n"; } if (avg.ch1.size() >= 2U) { file << " \n"; file << " \n"; } file << " CH1\n"; file << " \n"; file << " CH2\n"; file << " \n"; file << " time within window, us\n"; file << " V\n"; file << " V\n"; file << "\n"; } int run(const Config& cfg) { Api api; DeviceHandle device(api); const int32_t open_err = api.OpenUsb(device.hnd, cfg.serial.empty() ? nullptr : cfg.serial.c_str()); expect_ok(api, open_err, "Open device over USB"); device.opened = true; t_x502_info info{}; expect_ok(api, api.GetDevInfo2(device.hnd, &info, sizeof(info)), "Get device info"); print_device_info(info); expect_ok(api, api.SetMode(device.hnd, X502_MODE_FPGA), "Set FPGA mode"); api.StreamsStop(device.hnd); api.StreamsDisable(device.hnd, X502_STREAM_ALL_IN | X502_STREAM_ALL_OUT); expect_ok(api, api.SetSyncMode(device.hnd, cfg.clock_mode), "Set external clock on DI_SYN1"); expect_ok(api, api.SetSyncStartMode(device.hnd, X502_SYNC_INTERNAL), "Set immediate stream start"); const int32_t ext_ref_err = api.SetExtRefFreqValue(device.hnd, cfg.clock_hz); if (ext_ref_err != X502_ERR_OK) { if (cfg.clock_hz <= 1500000.0) { expect_ok(api, ext_ref_err, "Set external reference frequency"); } else { std::cerr << "Warning: X502_SetExtRefFreqValue(" << cfg.clock_hz << ") failed, continuing with nominal timing only: " << x502_error(api, ext_ref_err) << "\n"; } } expect_ok(api, api.SetLChannelCount(device.hnd, kLogicalChannelCount), "Set logical channel count"); expect_ok(api, api.SetLChannel(device.hnd, 0, cfg.ch1, cfg.mode, cfg.range, 1), "Set logical channel 0"); expect_ok(api, api.SetLChannel(device.hnd, 1, cfg.ch2, cfg.mode, cfg.range, 1), "Set logical channel 1"); expect_ok(api, api.SetAdcFreqDivider(device.hnd, 1), "Set ADC frequency divider"); expect_ok(api, api.SetAdcInterframeDelay(device.hnd, 0), "Set ADC interframe delay"); expect_ok(api, api.SetDinFreqDivider(device.hnd, kLogicalChannelCount), "Set DIN frequency divider"); expect_ok(api, api.SetStreamBufSize(device.hnd, X502_STREAM_CH_IN, cfg.input_buffer_words), "Set input buffer size"); expect_ok(api, api.SetStreamStep(device.hnd, X502_STREAM_CH_IN, cfg.input_step_words), "Set input stream step"); uint32_t pullups = 0; if (cfg.pullup_syn1) { pullups |= X502_PULLUPS_DI_SYN1; } if (cfg.pulldown_start_in) { pullups |= X502_PULLDOWN_START_IN; } expect_ok(api, api.SetDigInPullup(device.hnd, pullups), "Set digital input pullups/pulldowns"); expect_ok(api, api.Configure(device.hnd, 0), "Configure device"); expect_ok(api, api.StreamsEnable(device.hnd, X502_STREAM_ADC | X502_STREAM_DIN), "Enable ADC+DIN stream"); const double frame_freq_hz = cfg.clock_hz / static_cast(kLogicalChannelCount); const double expected_window_us = 1e6 / (2.0 * cfg.gate_hz); const double expected_frames_per_window = frame_freq_hz / (2.0 * cfg.gate_hz); std::cout << "Capture settings:\n" << " clock source: " << clock_mode_to_string(cfg.clock_mode) << "\n" << " gate source: start_in (read from synchronous DIN)\n" << " sample clock: " << cfg.clock_hz << " Hz\n" << " per-channel frame rate: " << frame_freq_hz << " Hz\n" << " gate frequency: " << cfg.gate_hz << " Hz\n" << " expected high window: " << expected_window_us << " us\n" << " expected frames per window: " << expected_frames_per_window << "\n" << " channel 1: " << phy_pair_name(cfg.ch1) << "\n" << " channel 2: " << phy_pair_name(cfg.ch2) << "\n" << " ADC range: +/-" << range_to_volts(cfg.range) << " V\n" << " target windows: " << cfg.windows << "\n"; expect_ok(api, api.StreamsStart(device.hnd), "Start streams"); device.streams_started = true; std::vector raw(cfg.recv_block_words); std::vector adc_buffer(cfg.recv_block_words); std::vector din_buffer(cfg.recv_block_words); std::vector windows; windows.reserve(cfg.windows); std::deque pending_frames; std::deque pending_gate; bool have_partial_ch1 = false; double partial_ch1 = 0.0; uint32_t next_lch = 0; bool gate_initialized = false; bool last_gate = false; bool saw_low_before_capture = false; bool in_window = false; WindowData current_window; const ULONGLONG session_start = GetTickCount64(); ULONGLONG last_stream_activity = session_start; ULONGLONG last_window_complete = session_start; ULONGLONG last_din_activity = session_start; ULONGLONG last_gate_edge = session_start; uint64_t total_frames_seen = 0; uint64_t total_din_words = 0; uint64_t total_gate_edges = 0; auto finalize_window = [&]() { const std::size_t len = std::min(current_window.ch1.size(), current_window.ch2.size()); current_window.ch1.resize(len); current_window.ch2.resize(len); if (len != 0U) { windows.push_back(std::move(current_window)); current_window = WindowData{}; last_window_complete = GetTickCount64(); } else { current_window = WindowData{}; } }; auto fail_waiting_for_gate = [&](ULONGLONG now) { std::ostringstream message; message << "ADC clock is present, but no complete START_IN windows were captured within " << cfg.window_wait_ms << " ms. " << "Processed frames=" << total_frames_seen << ", DIN samples=" << total_din_words << ", gate edges=" << total_gate_edges << ". "; if (total_din_words == 0) { message << "No synchronous DIN words were decoded, so START_IN is not being sampled. "; } else if (!gate_initialized) { message << "DIN data is present, but START_IN state was not initialized yet. "; } else if (total_gate_edges == 0) { message << "START_IN appears stuck " << (last_gate ? "HIGH" : "LOW") << ". "; } else if (in_window) { message << "START_IN produced a rising edge, but no matching falling edge closed the window. "; } else { message << "START_IN toggled, but no complete low->high->low window was accepted. "; } message << "Check START_IN wiring, common DGND with the generator, and signal level around 0/3.3 V. " << "Gate progress: last DIN activity " << (now - last_din_activity) << " ms ago, last gate edge " << (now - last_gate_edge) << " ms ago."; fail(message.str()); }; while (windows.size() < cfg.windows) { const int32_t recvd = api.Recv(device.hnd, raw.data(), cfg.recv_block_words, cfg.recv_timeout_ms); if (recvd < 0) { fail("X502_Recv failed: " + x502_error(api, recvd)); } const ULONGLONG now = GetTickCount64(); if (recvd == 0) { if ((now - last_stream_activity) >= cfg.clock_wait_ms) { if (windows.empty() && !gate_initialized) { fail("Timeout waiting for external ADC clock on DI_SYN1. " "Streams start immediately in this program, so a timeout here usually means " "there is no valid clock on DI_SYN1."); } fail("External ADC clock stalled before capture completed."); } if ((windows.size() < cfg.windows) && ((now - last_window_complete) >= cfg.window_wait_ms)) { fail_waiting_for_gate(now); } continue; } last_stream_activity = now; uint32_t adc_count = static_cast(adc_buffer.size()); uint32_t din_count = static_cast(din_buffer.size()); expect_ok(api, api.ProcessData(device.hnd, raw.data(), static_cast(recvd), X502_PROC_FLAGS_VOLT, adc_buffer.data(), &adc_count, din_buffer.data(), &din_count), "Process ADC+DIN data"); for (uint32_t i = 0; i < adc_count; ++i) { const double value = adc_buffer[i]; if (next_lch == 0U) { partial_ch1 = value; have_partial_ch1 = true; next_lch = 1U; } else { if (!have_partial_ch1) { fail("Internal frame assembly error: missing channel 1 sample"); } pending_frames.push_back(Frame{partial_ch1, value}); ++total_frames_seen; have_partial_ch1 = false; next_lch = 0U; } } for (uint32_t i = 0; i < din_count; ++i) { pending_gate.push_back((din_buffer[i] & kStartInMask) != 0U); } if (din_count != 0U) { total_din_words += din_count; last_din_activity = now; } if ((pending_frames.size() > 1000000U) || (pending_gate.size() > 1000000U)) { fail("Internal backlog grew too large while waiting for gate windows. " "ADC frames and DIN samples are not lining up as expected."); } while (!pending_frames.empty() && !pending_gate.empty() && (windows.size() < cfg.windows)) { const Frame frame = pending_frames.front(); pending_frames.pop_front(); const bool gate = pending_gate.front(); pending_gate.pop_front(); if (!gate_initialized) { gate_initialized = true; last_gate = gate; saw_low_before_capture = !gate; last_gate_edge = now; } else if (!saw_low_before_capture && !gate) { saw_low_before_capture = true; } if (gate_initialized && (gate != last_gate)) { ++total_gate_edges; last_gate_edge = now; } if (!in_window && saw_low_before_capture && gate_initialized && !last_gate && gate) { in_window = true; current_window = WindowData{}; } if (in_window && !gate) { finalize_window(); in_window = false; } if (in_window && gate) { current_window.ch1.push_back(frame.ch1); current_window.ch2.push_back(frame.ch2); } last_gate = gate; } if ((windows.size() < cfg.windows) && ((now - last_window_complete) >= cfg.window_wait_ms)) { fail_waiting_for_gate(now); } } if (in_window) { finalize_window(); } expect_ok(api, api.StreamsStop(device.hnd), "Stop streams"); device.streams_started = false; if (windows.size() > cfg.windows) { windows.resize(cfg.windows); } if (windows.empty()) { fail("No complete gate windows were captured"); } write_csv(cfg.csv_path, windows, frame_freq_hz); write_svg(cfg.svg_path, windows, frame_freq_hz, range_to_volts(cfg.range), cfg.plot_windows, cfg); std::size_t min_len = std::numeric_limits::max(); std::size_t max_len = 0; std::size_t total_len = 0; for (const auto& window : windows) { const std::size_t len = std::min(window.ch1.size(), window.ch2.size()); min_len = std::min(min_len, len); max_len = std::max(max_len, len); total_len += len; } const double avg_len = static_cast(total_len) / static_cast(windows.size()); std::cout << "Captured windows: " << windows.size() << "\n" << "Samples per window: min=" << min_len << ", avg=" << avg_len << ", max=" << max_len << "\n" << "CSV: " << cfg.csv_path << "\n" << "SVG: " << cfg.svg_path << "\n"; return 0; } } // namespace int main(int argc, char** argv) { try { const Config cfg = parse_args(argc, argv); return run(cfg); } catch (const std::exception& ex) { std::cerr << "Error: " << ex.what() << "\n"; return 1; } }