diff --git a/lchm_clock_counter.cpp b/lchm_clock_counter.cpp index 5ac743c..96f3352 100644 --- a/lchm_clock_counter.cpp +++ b/lchm_clock_counter.cpp @@ -45,6 +45,7 @@ struct Config { 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; @@ -56,6 +57,7 @@ struct Config { bool pullup_syn1 = false; bool pullup_syn2 = false; + bool clean_start = false; }; struct LchmClockCount { @@ -197,19 +199,22 @@ void print_help(const char* exe_name) { << "Usage:\n" << " " << exe_name << " [serial:SN] [ip:192.168.0.10]\n" << " [clock:di_syn1_rise] [clock_hz:2000000]\n" - << " [windows:1000]\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]\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" - << "If DI_SYN2 is already high at startup, the first partial LCHM is skipped\n" - << "until a clean low->high transition is observed.\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"; @@ -232,6 +237,10 @@ Config parse_args(int argc, char** argv) { 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; @@ -252,6 +261,10 @@ Config parse_args(int argc, char** argv) { 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; @@ -290,6 +303,9 @@ Config parse_args(int argc, char** argv) { 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"); } @@ -685,8 +701,15 @@ int run(const Config& cfg) { 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" @@ -702,6 +725,11 @@ 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; @@ -720,7 +748,12 @@ int run(const Config& cfg) { uint64_t total_din_words = 0; uint64_t total_gate_edges = 0; - auto finalize_lchm = [&]() { + 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 @@ -736,6 +769,7 @@ int run(const Config& cfg) { << ": 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(); } @@ -824,6 +858,9 @@ int run(const Config& cfg) { 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; } @@ -834,12 +871,11 @@ int run(const Config& cfg) { } if (!in_lchm && saw_low_before_capture && gate_initialized && !last_gate && gate) { - in_lchm = true; - current.clear(); + start_lchm(); } if (in_lchm && !gate) { - finalize_lchm(); + finalize_lchm("di_syn2_fall"); in_lchm = false; } @@ -847,6 +883,14 @@ int run(const Config& cfg) { 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; } @@ -856,7 +900,7 @@ int run(const Config& cfg) { } if (console_stop_requested() && in_lchm) { - finalize_lchm(); + finalize_lchm("user_stop"); } expect_ok(api, api.StreamsStop(device.hnd), "Stop streams");