This commit is contained in:
awe
2026-04-20 15:06:44 +03:00
parent cc9b22127a
commit fe9b0f6165

View File

@ -37,15 +37,17 @@ namespace {
constexpr uint32_t kE502DiSyn2Mask =
(static_cast<uint32_t>(1U) << 13U) | (static_cast<uint32_t>(1U) << 17U);
constexpr uint32_t kE502Digital1Mask = (static_cast<uint32_t>(1U) << 0U);
constexpr uint32_t kE502Digital2Mask = (static_cast<uint32_t>(1U) << 1U);
constexpr uint32_t kInternalRefSetting = X502_REF_FREQ_2000KHZ;
constexpr uint32_t kInternalRefHz = 2000000U;
struct Config {
std::string serial;
std::optional<uint32_t> 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<uint64_t>::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<decltype(StreamsDisable)>(x502_module, "X502_StreamsDisable");
SetSyncMode = load_symbol<decltype(SetSyncMode)>(x502_module, "X502_SetSyncMode");
SetSyncStartMode = load_symbol<decltype(SetSyncStartMode)>(x502_module, "X502_SetSyncStartMode");
SetDinFreqDivider = load_symbol<decltype(SetDinFreqDivider)>(x502_module, "X502_SetDinFreqDivider");
SetRefFreq = load_symbol<decltype(SetRefFreq)>(x502_module, "X502_SetRefFreq");
SetDinFreq = load_symbol<decltype(SetDinFreq)>(x502_module, "X502_SetDinFreq");
SetStreamBufSize = load_symbol<decltype(SetStreamBufSize)>(x502_module, "X502_SetStreamBufSize");
SetStreamStep = load_symbol<decltype(SetStreamStep)>(x502_module, "X502_SetStreamStep");
SetDigInPullup = load_symbol<decltype(SetDigInPullup)>(x502_module, "X502_SetDigInPullup");
SetExtRefFreqValue = load_symbol<decltype(SetExtRefFreqValue)>(x502_module, "X502_SetExtRefFreqValue");
Configure = load_symbol<decltype(Configure)>(x502_module, "X502_Configure");
StreamsEnable = load_symbol<decltype(StreamsEnable)>(x502_module, "X502_StreamsEnable");
StreamsStart = load_symbol<decltype(StreamsStart)>(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<uint32_t> raw(cfg.recv_block_words);
std::vector<uint32_t> din_buffer(cfg.recv_block_words);
const uint64_t duration_limit_clocks = (cfg.duration_ms == 0.0)
? 0U
: std::max<uint64_t>(
1U,
static_cast<uint64_t>(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<Di1StepClockCount> 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) {
if (di1_changed && (current_step.clocks != 0U)) {
finalize_current_step();
current_step_level = di1_level;
}
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();
}
current_step.add(di2_high);
}
last_gate = gate;