#ifdef _WIN32 #ifndef NOMINMAX #define NOMINMAX #endif #endif #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 #include namespace { enum class StopMode { TargetFrames, DiSyn2Rise, DiSyn2Fall }; struct Config { std::string serial; std::optional ip_addr; uint32_t mode = X502_LCH_MODE_DIFF; uint32_t range = X502_ADC_RANGE_5; uint32_t ch1 = 2; uint32_t ch2 = 3; double sample_clock_hz = 2000000.0; double duration_ms = 40.0; uint32_t sync_mode = X502_SYNC_EXTERNAL_MASTER; uint32_t sync_start_mode = X502_SYNC_DI_SYN1_RISE; StopMode stop_mode = StopMode::TargetFrames; uint32_t recv_block_words = 8192; uint32_t recv_timeout_ms = 100; uint32_t start_wait_ms = 10000; uint32_t input_buffer_words = 262144; uint32_t input_step_words = 8192; bool pullup_syn1 = false; bool pullup_syn2 = false; bool pulldown_conv_in = false; bool pulldown_start_in = false; std::string csv_path = "capture.csv"; std::string svg_path = "capture.svg"; }; [[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_ipv4(const std::string& text) { std::array parts{}; std::stringstream ss(text); std::string token; for (std::size_t i = 0; i < parts.size(); ++i) { if (!std::getline(ss, token, '.')) { fail("Invalid IPv4 address: " + text); } parts[i] = parse_u32(token, "ip"); if (parts[i] > 255) { fail("IPv4 byte out of range: " + token); } } if (std::getline(ss, token, '.')) { fail("Invalid IPv4 address: " + text); } return (parts[0] << 24) | (parts[1] << 16) | (parts[2] << 8) | parts[3]; } std::string ipv4_to_string(uint32_t ip_addr) { std::ostringstream out; out << ((ip_addr >> 24) & 0xFF) << '.' << ((ip_addr >> 16) & 0xFF) << '.' << ((ip_addr >> 8) & 0xFF) << '.' << (ip_addr & 0xFF); return out.str(); } 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 == "comm") || (value == "gnd") || (value == "single_ended")) { return X502_LCH_MODE_COMM; } if ((value == "diff") || (value == "differential")) { return X502_LCH_MODE_DIFF; } fail("Unsupported input mode: " + text); } uint32_t parse_sync_mode(const std::string& text) { const std::string value = trim_copy(text); if ((value == "conv_in") || (value == "start_in") || (value == "external_master")) { return X502_SYNC_EXTERNAL_MASTER; } if (value == "di_syn1_rise") { return X502_SYNC_DI_SYN1_RISE; } if (value == "di_syn1_fall") { return X502_SYNC_DI_SYN1_FALL; } if (value == "di_syn2_rise") { return X502_SYNC_DI_SYN2_RISE; } if (value == "di_syn2_fall") { return X502_SYNC_DI_SYN2_FALL; } if ((value == "internal") || (value == "immediate")) { return X502_SYNC_INTERNAL; } fail("Unsupported sync mode: " + text); } std::string sync_mode_to_string(uint32_t mode, bool for_start) { switch (mode) { case X502_SYNC_INTERNAL: return for_start ? "immediate" : "internal"; case X502_SYNC_EXTERNAL_MASTER: return for_start ? "start_in" : "conv_in"; case X502_SYNC_DI_SYN1_RISE: return "di_syn1_rise"; case X502_SYNC_DI_SYN1_FALL: return "di_syn1_fall"; case X502_SYNC_DI_SYN2_RISE: return "di_syn2_rise"; case X502_SYNC_DI_SYN2_FALL: return "di_syn2_fall"; default: return "unknown"; } } StopMode parse_stop_mode(const std::string& text) { const std::string value = trim_copy(text); if ((value == "frames") || (value == "duration") || (value == "none")) { return StopMode::TargetFrames; } if (value == "di_syn2_rise") { return StopMode::DiSyn2Rise; } if (value == "di_syn2_fall") { return StopMode::DiSyn2Fall; } fail("Unsupported stop mode: " + text); } std::string stop_mode_to_string(StopMode mode) { switch (mode) { case StopMode::TargetFrames: return "target_frames"; case StopMode::DiSyn2Rise: return "di_syn2_rise"; case StopMode::DiSyn2Fall: return "di_syn2_fall"; default: return "unknown"; } } bool sync_uses_di_syn1(uint32_t mode) { return (mode == X502_SYNC_DI_SYN1_RISE) || (mode == X502_SYNC_DI_SYN1_FALL); } bool sync_uses_di_syn2(uint32_t mode) { return (mode == X502_SYNC_DI_SYN2_RISE) || (mode == X502_SYNC_DI_SYN2_FALL); } std::string phy_channel_name(uint32_t mode, uint32_t phy_ch) { if (mode == X502_LCH_MODE_DIFF) { return "X" + std::to_string(phy_ch + 1) + "-Y" + std::to_string(phy_ch + 1); } if (phy_ch < 16) { return "X" + std::to_string(phy_ch + 1); } if (phy_ch < 32) { return "Y" + std::to_string((phy_ch - 16) + 1); } return "CH" + std::to_string(phy_ch); } void print_help(const char* exe_name) { std::cout << "Usage:\n" << " " << exe_name << " [serial:SN] [ip:192.168.0.10] [ch1:2] [ch2:3]\n" << " [mode:diff|comm] [range:5] [clock:conv_in]\n" << " [start:di_syn1_rise] [stop:frames] [sample_clock_hz:2000000]\n" << " [duration_ms:40] [csv:capture.csv] [svg:capture.svg]\n" << " [recv_block:8192] [start_wait_ms:10000]\n" << " [pullup_syn1] [pullup_syn2] [pulldown_conv_in] [pulldown_start_in]\n" << "\n" << "Defaults for E-502:\n" << " ch1:2, ch2:3 -> X3-Y3 and X4-Y4\n" << " mode:diff -> differential measurement\n" << " range:5 -> +/-5 V range\n" << " clock:conv_in -> external sample clock on CONV_IN\n" << " start:di_syn1_rise-> start on DI_SYN1 rising edge\n" << " stop:frames -> stop after duration_ms worth of frames\n" << " duration_ms:40 -> capture one 40 ms chirp or max length when stop is external\n" << "\n" << "Differential physical channel mapping:\n" << " 0..15 -> X1-Y1 .. X16-Y16\n" << "\n" << "Common-ground physical channel mapping:\n" << " 0..15 -> X1..X16\n" << " 16..31 -> Y1..Y16\n" << "\n" << "Useful sync lines on E-502:\n" << " clock: conv_in | di_syn1_rise | di_syn1_fall | di_syn2_rise | di_syn2_fall\n" << " start: immediate | start_in | di_syn1_rise | di_syn1_fall | di_syn2_rise | di_syn2_fall\n" << " stop: frames | di_syn2_rise | di_syn2_fall\n" << "\n" << "Stop on DI_SYN2 is polled asynchronously via X502_AsyncInDig(), so ADC stream stays\n" << "ADC-only. For reliable stop detection hold DI_SYN2 active until the program stops.\n" << "\n" << "Recommended working example:\n" << " " << exe_name << " clock:di_syn1_rise start:start_in stop:di_syn2_rise sample_clock_hz:2000000" << " duration_ms:40 csv:chirp.csv svg:chirp.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 == "pullup_syn2") { cfg.pullup_syn2 = true; continue; } if (arg == "pulldown_conv_in") { cfg.pulldown_conv_in = 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, "ip:")) { cfg.ip_addr = parse_ipv4(arg.substr(3)); 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.sync_mode = parse_sync_mode(arg.substr(6)); continue; } if (starts_with(arg, "start:")) { cfg.sync_start_mode = parse_sync_mode(arg.substr(6)); continue; } if (starts_with(arg, "stop:")) { cfg.stop_mode = parse_stop_mode(arg.substr(5)); continue; } if (starts_with(arg, "sample_clock_hz:")) { cfg.sample_clock_hz = parse_double(arg.substr(16), "sample_clock_hz"); continue; } if (starts_with(arg, "duration_ms:")) { cfg.duration_ms = parse_double(arg.substr(12), "duration_ms"); 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, "start_wait_ms:")) { cfg.start_wait_ms = parse_u32(arg.substr(14), "start_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.duration_ms <= 0.0) { fail("duration_ms must be > 0"); } if (cfg.sample_clock_hz <= 0.0) { fail("sample_clock_hz 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; } if (sync_uses_di_syn1(cfg.sync_mode) && sync_uses_di_syn1(cfg.sync_start_mode)) { fail("clock and start cannot both use DI_SYN1; use start_in or immediate start"); } if (sync_uses_di_syn2(cfg.sync_mode) && sync_uses_di_syn2(cfg.sync_start_mode)) { fail("clock and start cannot both use DI_SYN2; use start_in or immediate start"); } if ((cfg.stop_mode != StopMode::TargetFrames) && sync_uses_di_syn2(cfg.sync_mode)) { fail("DI_SYN2 cannot be used simultaneously for clock and stop"); } if ((cfg.stop_mode != StopMode::TargetFrames) && sync_uses_di_syn2(cfg.sync_start_mode)) { fail("DI_SYN2 cannot be used simultaneously for start and stop"); } if (cfg.mode == X502_LCH_MODE_DIFF) { if ((cfg.ch1 >= X502_ADC_DIFF_CH_CNT) || (cfg.ch2 >= X502_ADC_DIFF_CH_CNT)) { fail("For differential mode E-502 channels must be in range 0..15"); } } else { if ((cfg.ch1 >= X502_ADC_COMM_CH_CNT) || (cfg.ch2 >= X502_ADC_COMM_CH_CNT)) { fail("For common-ground mode E-502 channels must be in range 0..31"); } } 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_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_AsyncInDig) AsyncInDig = nullptr; decltype(&X502_Recv) Recv = nullptr; decltype(&X502_ProcessAdcData) ProcessAdcData = nullptr; decltype(&E502_OpenUsb) OpenUsb = nullptr; decltype(&E502_OpenByIpAddr) OpenByIpAddr = 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"); 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"); AsyncInDig = load_symbol(x502_module, "X502_AsyncInDig"); Recv = load_symbol(x502_module, "X502_Recv"); ProcessAdcData = load_symbol(x502_module, "X502_ProcessAdcData"); OpenUsb = load_symbol(e502_module, "E502_OpenUsb"); OpenByIpAddr = load_symbol(e502_module, "E502_OpenByIpAddr"); } ~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)); } } constexpr uint32_t kE502DiSyn2Mask = (static_cast(1U) << 13U) | (static_cast(1U) << 17U); bool read_di_syn2_level(const Api& api, t_x502_hnd hnd) { uint32_t din = 0; expect_ok(api, api.AsyncInDig(hnd, &din), "Read digital inputs"); return (din & kE502DiSyn2Mask) != 0; } 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"; } struct PlotPoint { std::size_t sample_index = 0; double value = 0.0; }; std::vector build_min_max_trace(const std::vector& data, std::size_t max_columns) { std::vector result; if (data.empty()) { return result; } const std::size_t bucket_size = std::max(1, (data.size() + max_columns - 1) / max_columns); result.reserve(max_columns * 2); for (std::size_t begin = 0; begin < data.size(); begin += bucket_size) { const std::size_t end = std::min(begin + bucket_size, data.size()); std::size_t min_index = begin; std::size_t max_index = begin; for (std::size_t i = begin + 1; i < end; ++i) { if (data[i] < data[min_index]) { min_index = i; } if (data[i] > data[max_index]) { max_index = i; } } if (min_index <= max_index) { result.push_back({min_index, data[min_index]}); if (max_index != min_index) { result.push_back({max_index, data[max_index]}); } } else { result.push_back({max_index, data[max_index]}); result.push_back({min_index, data[min_index]}); } } if (result.back().sample_index != (data.size() - 1)) { result.push_back({data.size() - 1, data.back()}); } return result; } std::string polyline_points(const std::vector& trace, double max_time_s, double y_min, double y_max, double left, double top, double width, double height) { std::ostringstream out; out << std::fixed << std::setprecision(3); const double y_span = std::max(1e-12, y_max - y_min); const std::size_t max_index = trace.empty() ? 1 : trace.back().sample_index; const double time_scale = (max_time_s > 0.0) ? max_time_s : 1.0; for (const auto& point : trace) { const double time_s = (max_index == 0) ? 0.0 : (static_cast(point.sample_index) / static_cast(max_index)) * time_scale; const double x = left + (time_s / time_scale) * width; const double y = top + height - ((point.value - y_min) / y_span) * height; out << x << "," << y << " "; } return out.str(); } void write_csv(const std::string& path, const std::vector& ch1, const std::vector& ch2, double frame_freq_hz) { std::ofstream file(path, std::ios::binary); if (!file) { fail("Cannot open CSV for writing: " + path); } file << "frame_index,time_s,ch1_v,ch2_v\n"; file << std::fixed << std::setprecision(9); const std::size_t frames = std::min(ch1.size(), ch2.size()); for (std::size_t i = 0; i < frames; ++i) { const double time_s = static_cast(i) / frame_freq_hz; file << i << "," << time_s << "," << ch1[i] << "," << ch2[i] << "\n"; } } void write_svg(const std::string& path, const std::vector& ch1, const std::vector& ch2, double frame_freq_hz, double nominal_range_v) { std::ofstream file(path, std::ios::binary); if (!file) { fail("Cannot open SVG for writing: " + path); } const std::size_t frames = std::min(ch1.size(), ch2.size()); const double total_time_s = (frames > 1) ? (static_cast(frames - 1) / frame_freq_hz) : 0.0; double min_y = std::numeric_limits::infinity(); double max_y = -std::numeric_limits::infinity(); for (double v : ch1) { min_y = std::min(min_y, v); max_y = std::max(max_y, v); } for (double v : ch2) { min_y = std::min(min_y, v); max_y = std::max(max_y, v); } 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.08); min_y -= pad; max_y += pad; } const auto trace1 = build_min_max_trace(ch1, 1800); const auto trace2 = build_min_max_trace(ch2, 1800); const double width = 1400.0; const double height = 800.0; const double left = 90.0; const double right = 40.0; const double top = 40.0; const double bottom = 80.0; const double plot_w = width - left - right; const double plot_h = height - top - bottom; const double zero_y = top + plot_h - ((0.0 - min_y) / std::max(1e-12, max_y - min_y)) * plot_h; file << "\n"; file << " \n"; file << " \n"; for (int i = 0; i <= 10; ++i) { const double x = left + (plot_w * i / 10.0); const double y = top + (plot_h * i / 10.0); file << " \n"; file << " \n"; } if ((0.0 >= min_y) && (0.0 <= max_y)) { file << " \n"; } file << " \n"; file << " \n"; file << " E-502 capture: CH1 and CH2\n"; file << " time, s\n"; file << " V\n"; file << std::fixed << std::setprecision(6); for (int i = 0; i <= 10; ++i) { const double x = left + (plot_w * i / 10.0); const double t = total_time_s * i / 10.0; file << " " << t << "\n"; const double y = top + plot_h - (plot_h * i / 10.0); const double v = min_y + (max_y - min_y) * i / 10.0; file << " " << v << "\n"; } const double legend_y = height - 48.0; file << " \n"; file << " CH1\n"; file << " \n"; file << " CH2\n"; file << "\n"; } int run(const Config& cfg) { Api api; DeviceHandle device(api); int32_t err = X502_ERR_OK; if (cfg.ip_addr.has_value()) { err = api.OpenByIpAddr(device.hnd, *cfg.ip_addr, 0, 5000); } else { err = api.OpenUsb(device.hnd, cfg.serial.empty() ? nullptr : cfg.serial.c_str()); } expect_ok(api, err, "Open device"); device.opened = true; t_x502_info info{}; err = api.GetDevInfo2(device.hnd, &info, sizeof(info)); expect_ok(api, err, "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.sync_mode), "Set sync mode"); expect_ok(api, api.SetSyncStartMode(device.hnd, cfg.sync_start_mode), "Set sync start mode"); if (cfg.sync_mode != X502_SYNC_INTERNAL) { const int32_t ext_ref_err = api.SetExtRefFreqValue(device.hnd, cfg.sample_clock_hz); if (ext_ref_err != X502_ERR_OK) { if (cfg.sample_clock_hz <= 1500000.0) { expect_ok(api, ext_ref_err, "Set external reference frequency"); } else { std::cerr << "Warning: X502_SetExtRefFreqValue(" << cfg.sample_clock_hz << ") failed, continuing with manual divider configuration: " << x502_error(api, ext_ref_err) << "\n"; } } } expect_ok(api, api.SetLChannelCount(device.hnd, 2), "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.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.pullup_syn2) { pullups |= X502_PULLUPS_DI_SYN2; } if (cfg.pulldown_conv_in) { pullups |= X502_PULLDOWN_CONV_IN; } 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), "Enable ADC stream"); const double frame_freq_hz = cfg.sample_clock_hz / 2.0; const std::size_t target_frames = std::max( 1, static_cast(std::llround((cfg.duration_ms / 1000.0) * frame_freq_hz))); std::cout << "Capture settings:\n" << " clock source: " << sync_mode_to_string(cfg.sync_mode, false) << "\n" << " start source: " << sync_mode_to_string(cfg.sync_start_mode, true) << "\n" << " stop source: " << stop_mode_to_string(cfg.stop_mode) << "\n" << " sample clock: " << cfg.sample_clock_hz << " Hz\n" << " per-channel frame rate: " << frame_freq_hz << " Hz\n" << " duration: " << cfg.duration_ms << " ms\n" << " channel 1: " << phy_channel_name(cfg.mode, cfg.ch1) << "\n" << " channel 2: " << phy_channel_name(cfg.mode, cfg.ch2) << "\n" << " ADC range: +/-" << range_to_volts(cfg.range) << " V\n" << " target frames per channel: " << target_frames << "\n"; expect_ok(api, api.StreamsStart(device.hnd), "Start streams"); device.streams_started = true; std::vector raw(cfg.recv_block_words); std::vector processed(cfg.recv_block_words); std::array, 2> channels; channels[0].reserve(target_frames); channels[1].reserve(target_frames); bool capture_started = false; bool stop_requested = false; bool stop_prev_initialized = false; bool stop_prev_level = false; bool stopped_by_external_signal = false; uint32_t next_lch = 0; const ULONGLONG start_wait_deadline = GetTickCount64() + cfg.start_wait_ms; while (!stop_requested && ((channels[0].size() < target_frames) || (channels[1].size() < target_frames))) { 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)); } if (recvd == 0) { if (!capture_started && (GetTickCount64() >= start_wait_deadline)) { std::ostringstream message; message << "Timeout before first ADC data. start=" << sync_mode_to_string(cfg.sync_start_mode, true) << ", clock=" << sync_mode_to_string(cfg.sync_mode, false) << ". "; if (cfg.sync_start_mode == X502_SYNC_EXTERNAL_MASTER) { message << "With start:start_in the module waits for START_IN after StreamsStart(); " "until that condition occurs the external clock is ignored. "; } else if (cfg.sync_start_mode == X502_SYNC_INTERNAL) { message << "Start is immediate, so this usually means there is no valid external clock on the selected clock line. "; } else { message << "This usually means the selected start condition did not occur after StreamsStart(), " "or no valid external clock arrived afterwards. "; } message << "For a quick clock-only check, try start:immediate. " "If you use a separate start pulse, it must arrive after the program starts waiting. " "You can also increase start_wait_ms."; fail(message.str()); } continue; } uint32_t adc_count = static_cast(recvd); expect_ok(api, api.ProcessAdcData(device.hnd, raw.data(), processed.data(), &adc_count, X502_PROC_FLAGS_VOLT), "Process ADC data"); if (adc_count == 0) { continue; } capture_started = true; for (uint32_t i = 0; i < adc_count; ++i) { const uint32_t lch = next_lch; next_lch = (next_lch + 1U) % 2U; if (channels[lch].size() < target_frames) { channels[lch].push_back(processed[i]); } if ((channels[0].size() >= target_frames) && (channels[1].size() >= target_frames)) { break; } } if ((cfg.stop_mode != StopMode::TargetFrames) && capture_started) { const bool stop_level = read_di_syn2_level(api, device.hnd); if (!stop_prev_initialized) { stop_prev_level = stop_level; stop_prev_initialized = true; } else { const bool is_edge = ((cfg.stop_mode == StopMode::DiSyn2Rise) && !stop_prev_level && stop_level) || ((cfg.stop_mode == StopMode::DiSyn2Fall) && stop_prev_level && !stop_level); if (is_edge) { stop_requested = true; stopped_by_external_signal = true; } stop_prev_level = stop_level; } } } expect_ok(api, api.StreamsStop(device.hnd), "Stop streams"); device.streams_started = false; const std::size_t frames = std::min(channels[0].size(), channels[1].size()); channels[0].resize(frames); channels[1].resize(frames); write_csv(cfg.csv_path, channels[0], channels[1], frame_freq_hz); write_svg(cfg.svg_path, channels[0], channels[1], frame_freq_hz, range_to_volts(cfg.range)); std::cout << "Captured " << frames << " frames per channel\n" << "Stop reason: " << (stopped_by_external_signal ? "DI_SYN2 edge" : "target frame count") << "\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; } }