#ifdef _WIN32 #ifndef NOMINMAX #define NOMINMAX #endif #include #else #include #include #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 namespace { constexpr uint32_t kE502DiSyn2Mask = (static_cast(1U) << 13U) | (static_cast(1U) << 17U); constexpr uint32_t kE502Digital2Mask = (static_cast(1U) << 1U); struct Config { std::string serial; std::optional ip_addr; uint32_t clock_mode = X502_SYNC_DI_SYN1_RISE; double clock_hz = 2000000.0; double duration_ms = 100.0; uint32_t windows = 1000; uint32_t recv_block_words = 8192; uint32_t recv_timeout_ms = 50; uint32_t clock_wait_ms = 5000; uint32_t lchm_wait_ms = 5000; uint32_t input_buffer_words = 262144; uint32_t input_step_words = 8192; bool pullup_syn1 = false; bool pullup_syn2 = false; bool clean_start = false; }; struct LchmClockCount { uint64_t clocks = 0; uint64_t di2_high_clocks = 0; uint64_t di2_low_clocks = 0; void clear() { clocks = 0; di2_high_clocks = 0; di2_low_clocks = 0; } void add(bool di2_high) { ++clocks; if (di2_high) { ++di2_high_clocks; } else { ++di2_low_clocks; } } }; struct RunningStats { uint64_t count = 0; uint64_t clocks_min = std::numeric_limits::max(); uint64_t clocks_max = 0; uint64_t clocks_sum = 0; uint64_t high_min = std::numeric_limits::max(); uint64_t high_max = 0; uint64_t high_sum = 0; uint64_t low_min = std::numeric_limits::max(); uint64_t low_max = 0; uint64_t low_sum = 0; void add(const LchmClockCount& value) { ++count; clocks_min = std::min(clocks_min, value.clocks); clocks_max = std::max(clocks_max, value.clocks); clocks_sum += value.clocks; high_min = std::min(high_min, value.di2_high_clocks); high_max = std::max(high_max, value.di2_high_clocks); high_sum += value.di2_high_clocks; low_min = std::min(low_min, value.di2_low_clocks); low_max = std::max(low_max, value.di2_low_clocks); low_sum += value.di2_low_clocks; } }; [[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] > 255U) { fail("IPv4 byte out of range: " + token); } } if (std::getline(ss, token, '.')) { fail("Invalid IPv4 address: " + text); } return (parts[0] << 24U) | (parts[1] << 16U) | (parts[2] << 8U) | parts[3]; } std::string ipv4_to_string(uint32_t ip_addr) { std::ostringstream out; out << ((ip_addr >> 24U) & 0xFFU) << '.' << ((ip_addr >> 16U) & 0xFFU) << '.' << ((ip_addr >> 8U) & 0xFFU) << '.' << (ip_addr & 0xFFU); return out.str(); } 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"; } } void print_help(const char* exe_name) { std::cout << "Usage:\n" << " " << exe_name << " [serial:SN] [ip:192.168.0.10]\n" << " [clock:di_syn1_rise] [clock_hz:2000000]\n" << " [duration_ms:100] [windows:1000]\n" << " [recv_block:8192] [recv_timeout_ms:50]\n" << " [clock_wait_ms:5000] [lchm_wait_ms:5000]\n" << " [buffer_words:262144] [step_words:8192]\n" << " [pullup_syn1] [pullup_syn2] [clean_start]\n" << "\n" << "Fixed counting scheme:\n" << " DI_SYN1 -> external clock; one synchronous DIN sample is one clock\n" << " DI_SYN2 -> LCHM window; rising edge starts, falling edge stops\n" << " DI2 -> extra digital level split into high/low clock counts\n" << "\n" << "By default, an initial HIGH level on DI_SYN2 starts counting immediately,\n" << "matching main.exe packet startup behavior. Use clean_start to skip that\n" << "partial first window until a clean low->high transition is observed.\n" << "duration_ms closes an active window if DI_SYN2 does not fall; use\n" << "duration_ms:0 to require a real DI_SYN2 falling edge.\n" << "\n" << "Example:\n" << " " << exe_name << " clock:di_syn1_rise clock_hz:2000000 windows:1000\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 == "clean_start") { cfg.clean_start = 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, "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, "duration_ms:")) { cfg.duration_ms = parse_double(arg.substr(12), "duration_ms"); continue; } if (starts_with(arg, "windows:")) { cfg.windows = parse_u32(arg.substr(8), "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, "lchm_wait_ms:")) { cfg.lchm_wait_ms = parse_u32(arg.substr(13), "lchm_wait_ms"); continue; } if (starts_with(arg, "window_wait_ms:")) { cfg.lchm_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; } fail("Unknown argument: " + arg); } if (cfg.clock_hz <= 0.0) { fail("clock_hz must be > 0"); } if (cfg.duration_ms < 0.0) { fail("duration_ms must be >= 0"); } if (cfg.windows == 0U) { fail("windows must be > 0"); } if (cfg.recv_block_words == 0U) { fail("recv_block must be > 0"); } if (cfg.input_step_words == 0U) { 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; } #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 } template Fn try_load_symbol(ModuleHandle module, const char* name) { #ifdef _WIN32 const auto addr = GetProcAddress(module, name); return reinterpret_cast(addr); #else dlerror(); void* addr = dlsym(module, name); (void) dlerror(); 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_GetDevInfo) GetDevInfo = 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_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_GetRecvReadyCount) GetRecvReadyCount = 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"); GetDevInfo = try_load_symbol(x502_module, "X502_GetDevInfo"); GetDevInfo2 = try_load_symbol(x502_module, "X502_GetDevInfo2"); if ((GetDevInfo == nullptr) && (GetDevInfo2 == nullptr)) { fail("Neither X502_GetDevInfo nor X502_GetDevInfo2 is available in x502 API library"); } 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"); 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"); GetRecvReadyCount = load_symbol(x502_module, "X502_GetRecvReadyCount"); 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)); } } using TickMs = uint64_t; TickMs 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 } #ifdef _WIN32 volatile LONG g_console_stop_requested = 0; BOOL WINAPI console_ctrl_handler(DWORD ctrl_type) { if ((ctrl_type == CTRL_C_EVENT) || (ctrl_type == CTRL_BREAK_EVENT) || (ctrl_type == CTRL_CLOSE_EVENT)) { InterlockedExchange(&g_console_stop_requested, 1); return TRUE; } return FALSE; } bool console_stop_requested() { return InterlockedCompareExchange(&g_console_stop_requested, 0, 0) != 0; } #else volatile std::sig_atomic_t g_console_stop_requested = 0; void console_ctrl_handler(int) { g_console_stop_requested = 1; } bool console_stop_requested() { return g_console_stop_requested != 0; } #endif struct ConsoleCtrlGuard { bool installed = false; ConsoleCtrlGuard() { #ifdef _WIN32 installed = SetConsoleCtrlHandler(console_ctrl_handler, TRUE) != 0; #else struct sigaction action {}; action.sa_handler = console_ctrl_handler; sigemptyset(&action.sa_mask); installed = (sigaction(SIGINT, &action, nullptr) == 0); #endif } ~ConsoleCtrlGuard() { #ifdef _WIN32 if (installed) { SetConsoleCtrlHandler(console_ctrl_handler, FALSE); } #else if (installed) { struct sigaction action {}; action.sa_handler = SIG_DFL; sigemptyset(&action.sa_mask); sigaction(SIGINT, &action, nullptr); } #endif } }; 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 >> 8U) << "." << static_cast(info.fpga_ver & 0xFFU) << "\n" << "PLDA version: " << static_cast(info.plda_ver) << "\n" << "Board revision: " << static_cast(info.board_rev) << "\n" << "MCU firmware: " << info.mcu_firmware_ver << "\n"; } void print_summary(const RunningStats& stats) { if (stats.count == 0U) { std::cout << "No complete LCHM windows captured\n"; return; } const auto avg = [count = stats.count](uint64_t sum) { return static_cast(sum) / static_cast(count); }; std::cout << std::fixed << std::setprecision(3) << "Summary: windows=" << stats.count << "\n" << " clocks: min=" << stats.clocks_min << ", avg=" << avg(stats.clocks_sum) << ", max=" << stats.clocks_max << "\n" << " di2_high_clocks: min=" << stats.high_min << ", avg=" << avg(stats.high_sum) << ", max=" << stats.high_max << "\n" << " di2_low_clocks: min=" << stats.low_min << ", avg=" << avg(stats.low_sum) << ", max=" << stats.low_max << "\n"; } int run(const Config& cfg) { Api api; DeviceHandle device(api); int32_t open_err = X502_ERR_OK; if (cfg.ip_addr.has_value()) { open_err = api.OpenByIpAddr(device.hnd, *cfg.ip_addr, 0, 5000); } else { open_err = api.OpenUsb(device.hnd, cfg.serial.empty() ? nullptr : cfg.serial.c_str()); } expect_ok(api, open_err, cfg.ip_addr.has_value() ? ("Open device by IP " + ipv4_to_string(*cfg.ip_addr)) : std::string("Open device over USB")); device.opened = true; t_x502_info info {}; int32_t info_err = X502_ERR_OK; if (api.GetDevInfo2 != nullptr) { info_err = api.GetDevInfo2(device.hnd, &info, sizeof(info)); } else { info_err = api.GetDevInfo(device.hnd, &info); } expect_ok(api, info_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.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 DIN divider 1: " << x502_error(api, ext_ref_err) << "\n"; } } expect_ok(api, api.SetDinFreqDivider(device.hnd, 1), "Set DIN frequency divider to one clock"); 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; } expect_ok(api, api.SetDigInPullup(device.hnd, pullups), "Set digital input pullups"); expect_ok(api, api.Configure(device.hnd, 0), "Configure device"); expect_ok(api, api.StreamsEnable(device.hnd, X502_STREAM_DIN), "Enable DIN stream"); std::cout << "LCHM clock counter settings:\n" << " clock source: " << clock_mode_to_string(cfg.clock_mode) << "\n" << " nominal clock: " << cfg.clock_hz << " Hz\n" << " duration limit: " << ((cfg.duration_ms == 0.0) ? std::string("disabled") : std::to_string(cfg.duration_ms) + " ms") << "\n" << " LCHM gate: DI_SYN2 high window\n" << " DI2 split: enabled\n" << " initial high DI_SYN2: " << (cfg.clean_start ? "skip until next low->high" : "count immediately") << "\n" << " target windows: " << cfg.windows << "\n" << " recv block words: " << cfg.recv_block_words << "\n" << " input step words: " << cfg.input_step_words << "\n" << " input buffer words: " << cfg.input_buffer_words << "\n"; ConsoleCtrlGuard console_guard; if (!console_guard.installed) { std::cerr << "Warning: Ctrl+C handler could not be installed; stop may be abrupt.\n"; } expect_ok(api, api.StreamsStart(device.hnd), "Start streams"); device.streams_started = true; std::vector raw(cfg.recv_block_words); std::vector din_buffer(cfg.recv_block_words); const uint64_t duration_limit_clocks = (cfg.duration_ms == 0.0) ? 0U : std::max( 1U, static_cast(std::llround((cfg.duration_ms / 1000.0) * cfg.clock_hz))); bool gate_initialized = false; bool last_gate = false; bool saw_low_before_capture = false; bool in_lchm = false; LchmClockCount current; RunningStats stats; const TickMs session_start = tick_count_ms(); TickMs last_stream_activity = session_start; TickMs last_lchm_complete = session_start; TickMs last_din_activity = session_start; TickMs last_gate_edge = session_start; uint64_t total_raw_words = 0; uint64_t total_din_words = 0; uint64_t total_gate_edges = 0; auto start_lchm = [&]() { in_lchm = true; current.clear(); }; auto finalize_lchm = [&](const char* close_reason) { if (current.clocks != (current.di2_high_clocks + current.di2_low_clocks)) { std::ostringstream message; message << "DI2 clock split invariant failed: clocks=" << current.clocks << ", high=" << current.di2_high_clocks << ", low=" << current.di2_low_clocks; fail(message.str()); } if (current.clocks != 0U) { const uint64_t lchm_index = stats.count + 1U; stats.add(current); std::cout << "LCHM " << lchm_index << ": clocks=" << current.clocks << ", di2_high_clocks=" << current.di2_high_clocks << ", di2_low_clocks=" << current.di2_low_clocks << ", close_reason=" << close_reason << "\n"; last_lchm_complete = tick_count_ms(); } current.clear(); }; auto fail_waiting_for_lchm = [&](TickMs now) { std::ostringstream message; message << "ADC/DIN clock is present, but no complete DI_SYN2 LCHM window was captured within " << cfg.lchm_wait_ms << " ms. " << "DIN samples=" << total_din_words << ", gate edges=" << total_gate_edges << ". "; if (total_din_words == 0U) { message << "No synchronous DIN words were decoded. "; } else if (!gate_initialized) { message << "DIN data is present, but DI_SYN2 state was not initialized yet. "; } else if (total_gate_edges == 0U) { message << "DI_SYN2 appears stuck " << (last_gate ? "HIGH" : "LOW") << ". "; } else if (in_lchm) { message << "DI_SYN2 produced a rising edge, but no matching falling edge closed the LCHM. "; } else { message << "DI_SYN2 toggled, but no complete low->high->low LCHM was accepted. "; } message << "Check DI_SYN2 wiring, common DGND, and signal level around 0/3.3 V. " << "Progress: last DIN activity " << (now - last_din_activity) << " ms ago, last DI_SYN2 edge " << (now - last_gate_edge) << " ms ago."; fail(message.str()); }; while ((stats.count < cfg.windows) && !console_stop_requested()) { uint32_t recv_request_words = cfg.recv_block_words; uint32_t recv_timeout_ms = cfg.recv_timeout_ms; uint32_t ready_words = 0; const int32_t ready_err = api.GetRecvReadyCount(device.hnd, &ready_words); if ((ready_err == X502_ERR_OK) && (ready_words != 0U)) { recv_request_words = std::min(ready_words, cfg.recv_block_words); recv_timeout_ms = 0; } const int32_t recvd = api.Recv(device.hnd, raw.data(), recv_request_words, recv_timeout_ms); if (recvd < 0) { fail("X502_Recv failed: " + x502_error(api, recvd)); } const TickMs now = tick_count_ms(); if (recvd == 0) { if ((now - last_stream_activity) >= cfg.clock_wait_ms) { fail("Timeout waiting for external clock on DI_SYN1. " "The stream starts immediately, so this usually means there is no valid clock on DI_SYN1."); } if ((now - last_lchm_complete) >= cfg.lchm_wait_ms) { fail_waiting_for_lchm(now); } continue; } last_stream_activity = now; total_raw_words += static_cast(recvd); uint32_t din_count = static_cast(din_buffer.size()); expect_ok(api, api.ProcessData(device.hnd, raw.data(), static_cast(recvd), 0U, nullptr, nullptr, din_buffer.data(), &din_count), "Process DIN data"); if (din_count != 0U) { total_din_words += din_count; last_din_activity = now; } for (uint32_t i = 0; (i < din_count) && (stats.count < cfg.windows); ++i) { const uint32_t din_value = din_buffer[i]; const bool gate = (din_value & kE502DiSyn2Mask) != 0U; const bool di2_high = (din_value & kE502Digital2Mask) != 0U; if (!gate_initialized) { gate_initialized = true; last_gate = gate; saw_low_before_capture = !gate; last_gate_edge = now; if (gate && !cfg.clean_start) { start_lchm(); } } 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_lchm && saw_low_before_capture && gate_initialized && !last_gate && gate) { start_lchm(); } if (in_lchm && !gate) { finalize_lchm("di_syn2_fall"); in_lchm = false; } if (in_lchm && gate) { current.add(di2_high); } if (in_lchm && (duration_limit_clocks != 0U) && (current.clocks >= duration_limit_clocks)) { finalize_lchm("duration_limit"); in_lchm = false; if (gate && (stats.count < cfg.windows)) { start_lchm(); } } last_gate = gate; } if ((stats.count < cfg.windows) && ((now - last_lchm_complete) >= cfg.lchm_wait_ms)) { fail_waiting_for_lchm(now); } } if (console_stop_requested() && in_lchm) { finalize_lchm("user_stop"); } expect_ok(api, api.StreamsStop(device.hnd), "Stop streams"); device.streams_started = false; std::cout << "Raw words read: " << total_raw_words << ", DIN samples: " << total_din_words << ", DI_SYN2 edges: " << total_gate_edges << "\n"; print_summary(stats); 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; } }