#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 #ifdef _WIN32 #include #else #include #include #endif namespace { struct Config { std::string serial; std::optional ip_addr; double sample_clock_hz = 50000.0; double capture_ms = 120.0; uint32_t range = X502_ADC_RANGE_5; uint32_t top = 8; std::vector exclude_phy = {2, 3, 18, 19}; uint32_t recv_block_words = 8192; uint32_t recv_timeout_ms = 50; }; [[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); if (clean.empty()) { fail("Missing integer value for " + field_name); } 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); if (clean.empty()) { fail("Missing floating point value for " + field_name); } 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] > 255U) { 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"); } } std::vector parse_exclude_phy(const std::string& text) { const std::string clean = trim_copy(text); if (clean.empty() || (clean == "none")) { return {}; } std::vector result; std::stringstream ss(clean); std::string token; while (std::getline(ss, token, ',')) { const std::string item = trim_copy(token); if (item.empty()) { continue; } const uint32_t phy = parse_u32(item, "exclude_phy"); if (phy >= X502_ADC_COMM_CH_CNT) { fail("exclude_phy has out-of-range channel " + std::to_string(phy) + " (allowed 0..31)"); } if (std::find(result.begin(), result.end(), phy) == result.end()) { result.push_back(phy); } } return result; } std::string phy_channel_name(uint32_t phy_ch) { if (phy_ch < 16U) { return "X" + std::to_string(phy_ch + 1U); } return "Y" + std::to_string((phy_ch - 16U) + 1U); } void print_help(const char* exe_name) { std::cout << "Usage:\n" << " " << exe_name << " [serial:SN] [ip:192.168.0.10]\n" << " [sample_clock_hz:50000] [capture_ms:120] [range:5]\n" << " [exclude_phy:2,3,18,19|none] [top:8]\n" << " [recv_block:8192] [recv_timeout_ms:50]\n" << "\n" << "Purpose:\n" << " Scan single-ended analog inputs (mode:comm, phy 0..31) and rank channels\n" << " by activity to locate where the ADC signal actually arrives.\n" << "\n" << "Defaults:\n" << " sample_clock_hz:50000\n" << " capture_ms:120\n" << " range:5 -> +/-5 V\n" << " top:8\n" << " exclude_phy:2,3,18,19 -> excludes X3,Y3,X4,Y4 from current diff setup\n" << "\n" << "Single-ended physical channel mapping:\n" << " 0..15 -> X1..X16\n" << " 16..31 -> Y1..Y16\n" << "\n" << "Scoring:\n" << " ranking priority: stddev first, then peak-to-peak (p2p)\n" << "\n" << "Example:\n" << " " << exe_name << " sample_clock_hz:50000 capture_ms:120 range:5 exclude_phy:2,3,18,19 top:8\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 (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, "sample_clock_hz:")) { cfg.sample_clock_hz = parse_double(arg.substr(16), "sample_clock_hz"); continue; } if (starts_with(arg, "capture_ms:")) { cfg.capture_ms = parse_double(arg.substr(11), "capture_ms"); continue; } if (starts_with(arg, "range:")) { cfg.range = parse_range(arg.substr(6)); continue; } if (starts_with(arg, "exclude_phy:")) { cfg.exclude_phy = parse_exclude_phy(arg.substr(12)); continue; } if (starts_with(arg, "top:")) { cfg.top = parse_u32(arg.substr(4), "top"); 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; } fail("Unknown argument: " + arg); } if (cfg.sample_clock_hz <= 0.0) { fail("sample_clock_hz must be > 0"); } if (cfg.capture_ms <= 0.0) { fail("capture_ms must be > 0"); } if (cfg.top == 0U) { fail("top must be > 0"); } if (cfg.recv_block_words == 0U) { fail("recv_block must be > 0"); } return cfg; } #ifdef _WIN32 using ModuleHandle = HMODULE; #else using ModuleHandle = void*; #endif std::string dynamic_loader_error() { #ifdef _WIN32 return "unknown error"; #else const char* error = dlerror(); return ((error != nullptr) && (*error != '\0')) ? std::string(error) : std::string("unknown error"); #endif } ModuleHandle open_library(const char* path) { #ifdef _WIN32 return LoadLibraryA(path); #else dlerror(); return dlopen(path, RTLD_LAZY | RTLD_LOCAL); #endif } void close_library(ModuleHandle module) { #ifdef _WIN32 if (module != nullptr) { FreeLibrary(module); } #else if (module != nullptr) { dlclose(module); } #endif } ModuleHandle load_library_or_fail(const std::vector& candidates, const std::string& description) { std::string last_error = "no candidates provided"; for (const auto& candidate : candidates) { ModuleHandle module = open_library(candidate.c_str()); if (module != nullptr) { return module; } last_error = dynamic_loader_error(); } std::ostringstream out; out << "Cannot load " << description << ". Tried:"; for (const auto& candidate : candidates) { out << " " << candidate; } out << ". Last error: " << last_error; fail(out.str()); } template Fn load_symbol(ModuleHandle module, const char* name) { #ifdef _WIN32 const auto addr = GetProcAddress(module, name); if (addr == nullptr) { fail(std::string("GetProcAddress failed for symbol: ") + name); } return reinterpret_cast(addr); #else dlerror(); void* addr = dlsym(module, name); const char* error = dlerror(); if ((addr == nullptr) || (error != nullptr)) { std::ostringstream out; out << "dlsym failed for symbol " << name << ": " << ((error != nullptr) ? error : "unknown error"); fail(out.str()); } return reinterpret_cast(addr); #endif } struct Api { ModuleHandle x502_module = nullptr; ModuleHandle 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_SetRefFreq) SetRefFreq = nullptr; decltype(&X502_SetLChannelCount) SetLChannelCount = nullptr; decltype(&X502_SetLChannel) SetLChannel = nullptr; decltype(&X502_SetAdcFreq) SetAdcFreq = 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; decltype(&E502_OpenByIpAddr) OpenByIpAddr = nullptr; Api() { x502_module = load_library_or_fail( #ifdef _WIN32 {"x502api.dll"}, #else {"libx502api.so", "x502api.so", "./libx502api.so", "./x502api.so"}, #endif "x502 API library"); e502_module = load_library_or_fail( #ifdef _WIN32 {"e502api.dll"}, #else {"libe502api.so", "e502api.so", "./libe502api.so", "./e502api.so"}, #endif "e502 API library"); 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"); SetRefFreq = load_symbol(x502_module, "X502_SetRefFreq"); SetLChannelCount = load_symbol(x502_module, "X502_SetLChannelCount"); SetLChannel = load_symbol(x502_module, "X502_SetLChannel"); SetAdcFreq = load_symbol(x502_module, "X502_SetAdcFreq"); 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"); OpenByIpAddr = load_symbol(e502_module, "E502_OpenByIpAddr"); } ~Api() { close_library(e502_module); close_library(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); } } }; uint64_t tick_count_ms() { #ifdef _WIN32 return static_cast(GetTickCount64()); #else using namespace std::chrono; return static_cast( duration_cast(steady_clock::now().time_since_epoch()).count()); #endif } struct RunningStats { std::size_t count = 0; double mean = 0.0; double m2 = 0.0; double sum_sq = 0.0; double min = std::numeric_limits::infinity(); double max = -std::numeric_limits::infinity(); void add(double value) { ++count; const double delta = value - mean; mean += delta / static_cast(count); const double delta2 = value - mean; m2 += delta * delta2; sum_sq += value * value; if (value < min) { min = value; } if (value > max) { max = value; } } }; struct ChannelResult { uint32_t phy_ch = 0; std::string pin_name; std::size_t sample_count = 0; double score = -1.0; double stddev = 0.0; double peak_to_peak = 0.0; double rms = 0.0; double mean = 0.0; double min = 0.0; double max = 0.0; }; void configure_base_capture(const Api& api, DeviceHandle& device, const Config& cfg) { expect_ok(api, api.SetMode(device.hnd, X502_MODE_FPGA), "Set FPGA mode"); api.StreamsStop(device.hnd); device.streams_started = false; api.StreamsDisable(device.hnd, X502_STREAM_ALL_IN | X502_STREAM_ALL_OUT); expect_ok(api, api.SetSyncMode(device.hnd, X502_SYNC_INTERNAL), "Set sync mode"); expect_ok(api, api.SetSyncStartMode(device.hnd, X502_SYNC_INTERNAL), "Set sync start mode"); expect_ok(api, api.SetRefFreq(device.hnd, X502_REF_FREQ_2000KHZ), "Set internal reference frequency"); double adc_freq = cfg.sample_clock_hz; double frame_freq = 0.0; expect_ok(api, api.SetAdcFreq(device.hnd, &adc_freq, &frame_freq), "Set ADC frequency"); } ChannelResult scan_phy_channel(const Api& api, DeviceHandle& device, const Config& cfg, uint32_t phy_ch) { ChannelResult result; result.phy_ch = phy_ch; result.pin_name = phy_channel_name(phy_ch); configure_base_capture(api, device, cfg); expect_ok(api, api.SetLChannelCount(device.hnd, 1), "Set logical channel count"); expect_ok(api, api.SetLChannel(device.hnd, 0, phy_ch, X502_LCH_MODE_COMM, cfg.range, 1), "Set logical channel"); expect_ok(api, api.Configure(device.hnd, 0), "Configure device"); expect_ok(api, api.StreamsEnable(device.hnd, X502_STREAM_ADC), "Enable ADC stream"); expect_ok(api, api.StreamsStart(device.hnd), "Start streams"); device.streams_started = true; std::vector raw(cfg.recv_block_words); std::vector adc(cfg.recv_block_words); RunningStats stats; const uint64_t capture_until = tick_count_ms() + static_cast(std::llround(cfg.capture_ms)); while (tick_count_ms() < capture_until) { 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 while scanning " + result.pin_name + ": " + x502_error(api, recvd)); } if (recvd == 0) { continue; } uint32_t adc_count = static_cast(adc.size()); expect_ok(api, api.ProcessData(device.hnd, raw.data(), static_cast(recvd), X502_PROC_FLAGS_VOLT, adc.data(), &adc_count, nullptr, nullptr), "ProcessData"); for (uint32_t i = 0; i < adc_count; ++i) { stats.add(adc[i]); } } api.StreamsStop(device.hnd); device.streams_started = false; api.StreamsDisable(device.hnd, X502_STREAM_ALL_IN | X502_STREAM_ALL_OUT); result.sample_count = stats.count; if (stats.count == 0U) { return result; } result.mean = stats.mean; result.min = stats.min; result.max = stats.max; result.peak_to_peak = stats.max - stats.min; result.rms = std::sqrt(stats.sum_sq / static_cast(stats.count)); const double variance = (stats.count > 1U) ? (stats.m2 / static_cast(stats.count - 1U)) : 0.0; result.stddev = std::sqrt(std::max(0.0, variance)); // Stddev is the dominant rank metric, p2p is a secondary tie-breaker. result.score = result.stddev * 1000000.0 + result.peak_to_peak; return result; } 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"; } void print_ranked_table(const std::vector& sorted) { std::cout << "\nRanked channels (sorted by stddev, then p2p):\n"; std::cout << std::left << std::setw(5) << "Rank" << std::setw(6) << "phy" << std::setw(6) << "pin" << std::setw(12) << "samples" << std::setw(14) << "score" << std::setw(14) << "stddev[V]" << std::setw(14) << "p2p[V]" << std::setw(14) << "rms[V]" << std::setw(14) << "mean[V]" << std::setw(14) << "min[V]" << std::setw(14) << "max[V]" << "\n"; std::cout << std::fixed << std::setprecision(6); std::size_t rank = 1; for (const auto& item : sorted) { std::cout << std::left << std::setw(5) << rank << std::setw(6) << item.phy_ch << std::setw(6) << item.pin_name << std::setw(12) << item.sample_count << std::setw(14) << item.score << std::setw(14) << item.stddev << std::setw(14) << item.peak_to_peak << std::setw(14) << item.rms << std::setw(14) << item.mean << std::setw(14) << item.min << std::setw(14) << item.max << "\n"; ++rank; } } 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); std::array excluded{}; for (uint32_t phy : cfg.exclude_phy) { if (phy >= X502_ADC_COMM_CH_CNT) { fail("exclude_phy has out-of-range channel: " + std::to_string(phy)); } excluded[phy] = true; } std::vector scan_channels; scan_channels.reserve(X502_ADC_COMM_CH_CNT); for (uint32_t phy = 0; phy < X502_ADC_COMM_CH_CNT; ++phy) { if (!excluded[phy]) { scan_channels.push_back(phy); } } if (scan_channels.empty()) { fail("No channels left to scan after exclude_phy filtering"); } std::cout << "Probe settings:\n" << " source: " << (cfg.ip_addr ? ("ip:" + ipv4_to_string(*cfg.ip_addr)) : (cfg.serial.empty() ? std::string("usb:auto") : ("serial:" + cfg.serial))) << "\n" << " mode: comm (single-ended), channel_count=1\n" << " sample_clock_hz: " << cfg.sample_clock_hz << "\n" << " capture_ms per channel: " << cfg.capture_ms << "\n" << " ADC range: +/-" << range_to_volts(cfg.range) << " V\n" << " top: " << cfg.top << "\n" << " excluded channels: "; if (cfg.exclude_phy.empty()) { std::cout << "none"; } else { for (std::size_t i = 0; i < cfg.exclude_phy.size(); ++i) { if (i != 0U) { std::cout << ","; } std::cout << cfg.exclude_phy[i] << "(" << phy_channel_name(cfg.exclude_phy[i]) << ")"; } } std::cout << "\n"; std::vector results; results.reserve(scan_channels.size()); for (std::size_t i = 0; i < scan_channels.size(); ++i) { const uint32_t phy = scan_channels[i]; std::cout << "Scanning [" << (i + 1U) << "/" << scan_channels.size() << "] " << "phy=" << phy << " (" << phy_channel_name(phy) << ")...\n"; results.push_back(scan_phy_channel(api, device, cfg, phy)); } api.StreamsStop(device.hnd); device.streams_started = false; api.StreamsDisable(device.hnd, X502_STREAM_ALL_IN | X502_STREAM_ALL_OUT); std::sort(results.begin(), results.end(), [](const ChannelResult& a, const ChannelResult& b) { const bool a_valid = a.sample_count != 0U; const bool b_valid = b.sample_count != 0U; if (a_valid != b_valid) { return a_valid; } if (a.stddev != b.stddev) { return a.stddev > b.stddev; } if (a.peak_to_peak != b.peak_to_peak) { return a.peak_to_peak > b.peak_to_peak; } return a.phy_ch < b.phy_ch; }); print_ranked_table(results); const std::size_t top_count = std::min(cfg.top, results.size()); std::cout << "\nTop " << top_count << " candidate(s):\n"; for (std::size_t i = 0; i < top_count; ++i) { const auto& item = results[i]; std::cout << " #" << (i + 1U) << " phy=" << item.phy_ch << " pin=" << item.pin_name << " stddev=" << std::fixed << std::setprecision(6) << item.stddev << " V" << " p2p=" << item.peak_to_peak << " V" << " score=" << item.score << " samples=" << item.sample_count << "\n"; } if (!results.empty() && (results.front().sample_count == 0U)) { std::cout << "\nWarning: no ADC samples were decoded. Check device connection and sample clock setup.\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; } }