diff --git a/lchm_clock_counter.cpp b/lchm_clock_counter.cpp index 96f3352..b957673 100644 --- a/lchm_clock_counter.cpp +++ b/lchm_clock_counter.cpp @@ -37,15 +37,17 @@ namespace { constexpr uint32_t kE502DiSyn2Mask = (static_cast(1U) << 13U) | (static_cast(1U) << 17U); +constexpr uint32_t kE502Digital1Mask = (static_cast(1U) << 0U); constexpr uint32_t kE502Digital2Mask = (static_cast(1U) << 1U); +constexpr uint32_t kInternalRefSetting = X502_REF_FREQ_2000KHZ; +constexpr uint32_t kInternalRefHz = 2000000U; 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; + double duration_ms = 100.0; // Legacy-only option retained for backward compatibility warnings. uint32_t windows = 1000; uint32_t recv_block_words = 8192; @@ -55,9 +57,12 @@ struct Config { uint32_t input_buffer_words = 262144; uint32_t input_step_words = 8192; - bool pullup_syn1 = false; bool pullup_syn2 = false; - bool clean_start = false; + bool legacy_clock_arg_used = false; + std::string legacy_clock_arg; + bool legacy_pullup_syn1 = false; + bool legacy_clean_start = false; + bool legacy_duration_ms = false; }; struct LchmClockCount { @@ -81,6 +86,11 @@ struct LchmClockCount { } }; +struct Di1StepClockCount { + bool di1_level = false; + LchmClockCount count; +}; + struct RunningStats { uint64_t count = 0; uint64_t clocks_min = std::numeric_limits::max(); @@ -172,52 +182,35 @@ std::string ipv4_to_string(uint32_t ip_addr) { return out.str(); } -uint32_t parse_clock_mode(const std::string& text) { +std::string parse_legacy_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"; + if ((value == "di_syn1_rise") || (value == "di_syn1_fall")) { + return value; } + fail("Unsupported legacy clock mode: " + text + ". Use di_syn1_rise or di_syn1_fall."); } 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" + << " [clock_hz:2000000] [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" + << " [pullup_syn2]\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" + << " clock -> internal only (fixed reference 2000000 Hz)\n" + << " DI_SYN2 -> strict LCHM window; low->high starts, high->low stops\n" + << " DI1 -> step delimiter; both DI1 edges split LCHM into steps\n" + << " DI2 -> high/low clocks counted per step and per full LCHM\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" + << "Legacy arguments are accepted but ignored with warning:\n" + << " clock:di_syn1_rise|di_syn1_fall, pullup_syn1, clean_start, duration_ms:...\n" << "\n" << "Example:\n" - << " " << exe_name << " clock:di_syn1_rise clock_hz:2000000 windows:1000\n"; + << " " << exe_name << " clock_hz:2000000 windows:1000 pullup_syn2\n"; } Config parse_args(int argc, char** argv) { @@ -230,7 +223,7 @@ Config parse_args(int argc, char** argv) { std::exit(0); } if (arg == "pullup_syn1") { - cfg.pullup_syn1 = true; + cfg.legacy_pullup_syn1 = true; continue; } if (arg == "pullup_syn2") { @@ -238,7 +231,7 @@ Config parse_args(int argc, char** argv) { continue; } if (arg == "clean_start") { - cfg.clean_start = true; + cfg.legacy_clean_start = true; continue; } if (starts_with(arg, "serial:")) { @@ -250,7 +243,8 @@ Config parse_args(int argc, char** argv) { continue; } if (starts_with(arg, "clock:")) { - cfg.clock_mode = parse_clock_mode(arg.substr(6)); + cfg.legacy_clock_arg_used = true; + cfg.legacy_clock_arg = parse_legacy_clock_mode(arg.substr(6)); continue; } if (starts_with(arg, "clock_hz:")) { @@ -263,6 +257,7 @@ Config parse_args(int argc, char** argv) { } if (starts_with(arg, "duration_ms:")) { cfg.duration_ms = parse_double(arg.substr(12), "duration_ms"); + cfg.legacy_duration_ms = true; continue; } if (starts_with(arg, "windows:")) { @@ -428,11 +423,11 @@ struct Api { decltype(&X502_StreamsDisable) StreamsDisable = nullptr; decltype(&X502_SetSyncMode) SetSyncMode = nullptr; decltype(&X502_SetSyncStartMode) SetSyncStartMode = nullptr; - decltype(&X502_SetDinFreqDivider) SetDinFreqDivider = nullptr; + decltype(&X502_SetRefFreq) SetRefFreq = nullptr; + decltype(&X502_SetDinFreq) SetDinFreq = 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; @@ -473,11 +468,11 @@ struct Api { 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"); + SetRefFreq = load_symbol(x502_module, "X502_SetRefFreq"); + SetDinFreq = load_symbol(x502_module, "X502_SetDinFreq"); 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"); @@ -638,6 +633,22 @@ void print_summary(const RunningStats& stats) { } int run(const Config& cfg) { + if (cfg.legacy_clock_arg_used) { + std::cerr << "Warning: legacy argument clock:" << cfg.legacy_clock_arg + << " is ignored. lchm_clock_counter uses internal clock only.\n"; + } + if (cfg.legacy_pullup_syn1) { + std::cerr << "Warning: legacy argument pullup_syn1 is ignored in internal-only mode.\n"; + } + if (cfg.legacy_clean_start) { + std::cerr << "Warning: legacy argument clean_start is ignored. " + << "LCHM starts strictly on DI_SYN2 low->high.\n"; + } + if (cfg.legacy_duration_ms) { + std::cerr << "Warning: legacy argument duration_ms:" << cfg.duration_ms + << " is ignored. LCHM closes only on DI_SYN2 high->low.\n"; + } + Api api; DeviceHandle device(api); @@ -666,30 +677,17 @@ int run(const Config& cfg) { 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.SetSyncMode(device.hnd, X502_SYNC_INTERNAL), "Set internal sync mode"); 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.SetRefFreq(device.hnd, kInternalRefSetting), "Set internal reference frequency"); + double actual_din_freq_hz = cfg.clock_hz; + expect_ok(api, api.SetDinFreq(device.hnd, &actual_din_freq_hz), "Set DIN frequency"); 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; } @@ -699,21 +697,23 @@ int run(const Config& cfg) { 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") + << " clock source: internal\n" + << " internal ref: " << kInternalRefHz << " Hz\n" + << " requested DIN clock: " << cfg.clock_hz << " Hz\n" + << " effective DIN clock: " << actual_din_freq_hz << " Hz\n" + << " LCHM gate: strict DI_SYN2 low->high start, high->low stop\n" + << " DI1 step segmentation: both edges\n" + << " DI2 split: enabled per step and per full LCHM\n" + << " duration limit: disabled (legacy duration_ms ignored)" << "\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"; + if (std::fabs(actual_din_freq_hz - cfg.clock_hz) > std::max(0.5, cfg.clock_hz * 1e-6)) { + std::cerr << "Warning: effective DIN clock differs from requested value: requested=" + << cfg.clock_hz << " Hz, effective=" << actual_din_freq_hz << " Hz\n"; + } ConsoleCtrlGuard console_guard; if (!console_guard.installed) { @@ -725,17 +725,16 @@ int run(const Config& cfg) { 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 di1_initialized = false; + bool last_di1_level = false; bool in_lchm = false; LchmClockCount current; + LchmClockCount current_step; + bool current_step_level = false; + std::vector current_steps; RunningStats stats; const TickMs session_start = tick_count_ms(); @@ -748,12 +747,28 @@ int run(const Config& cfg) { uint64_t total_din_words = 0; uint64_t total_gate_edges = 0; - auto start_lchm = [&]() { + auto start_lchm = [&](bool di1_level) { in_lchm = true; current.clear(); + current_step.clear(); + current_step_level = di1_level; + current_steps.clear(); + }; + + auto finalize_current_step = [&]() { + if (current_step.clocks == 0U) { + return; + } + Di1StepClockCount step; + step.di1_level = current_step_level; + step.count = current_step; + current_steps.push_back(step); + current_step.clear(); }; auto finalize_lchm = [&](const char* close_reason) { + finalize_current_step(); + if (current.clocks != (current.di2_high_clocks + current.di2_low_clocks)) { std::ostringstream message; message << "DI2 clock split invariant failed: clocks=" << current.clocks @@ -762,6 +777,25 @@ int run(const Config& cfg) { fail(message.str()); } + uint64_t step_sum_clocks = 0; + uint64_t step_sum_high = 0; + uint64_t step_sum_low = 0; + for (const auto& step : current_steps) { + step_sum_clocks += step.count.clocks; + step_sum_high += step.count.di2_high_clocks; + step_sum_low += step.count.di2_low_clocks; + } + if ((step_sum_clocks != current.clocks) || + (step_sum_high != current.di2_high_clocks) || + (step_sum_low != current.di2_low_clocks)) { + std::ostringstream message; + message << "DI1 step split invariant failed: total clocks/high/low=" + << current.clocks << "/" << current.di2_high_clocks << "/" << current.di2_low_clocks + << ", step sum clocks/high/low=" + << step_sum_clocks << "/" << step_sum_high << "/" << step_sum_low; + fail(message.str()); + } + if (current.clocks != 0U) { const uint64_t lchm_index = stats.count + 1U; stats.add(current); @@ -771,9 +805,20 @@ int run(const Config& cfg) { << ", di2_low_clocks=" << current.di2_low_clocks << ", close_reason=" << close_reason << "\n"; + for (std::size_t step_index = 0; step_index < current_steps.size(); ++step_index) { + const auto& step = current_steps[step_index]; + std::cout << " step " << (step_index + 1U) + << ": di1_level=" << (step.di1_level ? "HIGH" : "LOW") + << ", clocks=" << step.count.clocks + << ", di2_high_clocks=" << step.count.di2_high_clocks + << ", di2_low_clocks=" << step.count.di2_low_clocks + << "\n"; + } last_lchm_complete = tick_count_ms(); } current.clear(); + current_step.clear(); + current_steps.clear(); }; auto fail_waiting_for_lchm = [&](TickMs now) { @@ -819,8 +864,8 @@ int run(const Config& cfg) { 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."); + fail("Timeout waiting for DIN stream data in internal clock mode. " + "Check device state and DIN stream configuration."); } if ((now - last_lchm_complete) >= cfg.lchm_wait_ms) { fail_waiting_for_lchm(now); @@ -851,18 +896,22 @@ int run(const Config& cfg) { 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 di1_level = (din_value & kE502Digital1Mask) != 0U; const bool di2_high = (din_value & kE502Digital2Mask) != 0U; + bool di1_changed = false; + + if (!di1_initialized) { + di1_initialized = true; + last_di1_level = di1_level; + } else if (di1_level != last_di1_level) { + di1_changed = true; + last_di1_level = di1_level; + } 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)) { @@ -870,25 +919,22 @@ int run(const Config& cfg) { last_gate_edge = now; } - if (!in_lchm && saw_low_before_capture && gate_initialized && !last_gate && gate) { - start_lchm(); + if (!in_lchm && gate_initialized && !last_gate && gate) { + start_lchm(di1_level); } - if (in_lchm && !gate) { + if (in_lchm && last_gate && !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(); + if (di1_changed && (current_step.clocks != 0U)) { + finalize_current_step(); + current_step_level = di1_level; } + current.add(di2_high); + current_step.add(di2_high); } last_gate = gate;