Compare commits
29 Commits
af462ab46a
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 8175d1cba0 | |||
| 70b5557be9 | |||
| 4a0740539b | |||
| bee128dec7 | |||
| 94df442395 | |||
| 38a76ead58 | |||
| deb21dc681 | |||
| 61ac134424 | |||
| 4aded6604a | |||
| d36fb23470 | |||
| 9b9b7a79ad | |||
| 4e69bb67e2 | |||
| 71bf90f003 | |||
| f462e65cc2 | |||
| 00dc0e21b7 | |||
| fe9b0f6165 | |||
| cc9b22127a | |||
| 60a678168f | |||
| 3817f21473 | |||
| f85669cc1e | |||
| 262c1887c7 | |||
| 8668ff89c1 | |||
| 4af1324e3b | |||
| ff2add9504 | |||
| 900fdc1262 | |||
| de62e39d24 | |||
| a2848212d2 | |||
| 120ffaa6f1 | |||
| 6ea2653275 |
28
build_lchm_clock_counter.sh
Executable file
28
build_lchm_clock_counter.sh
Executable file
@ -0,0 +1,28 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
cd "$SCRIPT_DIR"
|
||||||
|
|
||||||
|
CXX="${CXX:-g++}"
|
||||||
|
OUT="${OUT:-lchm_clock_counter.exe}"
|
||||||
|
|
||||||
|
CXXFLAGS=(
|
||||||
|
-std=c++17
|
||||||
|
-O2
|
||||||
|
-Wall
|
||||||
|
-Wextra
|
||||||
|
-pedantic
|
||||||
|
)
|
||||||
|
|
||||||
|
SOURCES=(
|
||||||
|
lchm_clock_counter.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
LDFLAGS=(
|
||||||
|
-ldl
|
||||||
|
)
|
||||||
|
|
||||||
|
"$CXX" "${CXXFLAGS[@]}" "${SOURCES[@]}" "${LDFLAGS[@]}" -o "$OUT"
|
||||||
|
|
||||||
|
echo "Built $OUT"
|
||||||
32
build_main.sh
Executable file
32
build_main.sh
Executable file
@ -0,0 +1,32 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
cd "$SCRIPT_DIR"
|
||||||
|
|
||||||
|
CXX="${CXX:-g++}"
|
||||||
|
OUT="${OUT:-main.exe}"
|
||||||
|
|
||||||
|
CXXFLAGS=(
|
||||||
|
-std=c++17
|
||||||
|
-O2
|
||||||
|
-Wall
|
||||||
|
-Wextra
|
||||||
|
-pedantic
|
||||||
|
)
|
||||||
|
|
||||||
|
SOURCES=(
|
||||||
|
main.cpp
|
||||||
|
capture_file_writer.cpp
|
||||||
|
tty_protocol_writer.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
LDFLAGS=(
|
||||||
|
-ldl
|
||||||
|
-lutil
|
||||||
|
-lpthread
|
||||||
|
)
|
||||||
|
|
||||||
|
"$CXX" "${CXXFLAGS[@]}" "${SOURCES[@]}" "${LDFLAGS[@]}" -o "$OUT"
|
||||||
|
|
||||||
|
echo "Built $OUT"
|
||||||
978
lchm_clock_counter.cpp
Normal file
978
lchm_clock_counter.cpp
Normal file
@ -0,0 +1,978 @@
|
|||||||
|
#ifdef _WIN32
|
||||||
|
#ifndef NOMINMAX
|
||||||
|
#define NOMINMAX
|
||||||
|
#endif
|
||||||
|
#include <windows.h>
|
||||||
|
#else
|
||||||
|
#include <csignal>
|
||||||
|
#include <dlfcn.h>
|
||||||
|
#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 <algorithm>
|
||||||
|
#include <array>
|
||||||
|
#include <chrono>
|
||||||
|
#include <cmath>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <iomanip>
|
||||||
|
#include <iostream>
|
||||||
|
#include <limits>
|
||||||
|
#include <optional>
|
||||||
|
#include <sstream>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
double clock_hz = 2000000.0;
|
||||||
|
double duration_ms = 100.0; // Legacy-only option retained for backward compatibility warnings.
|
||||||
|
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_syn2 = 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 {
|
||||||
|
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 Di1StepClockCount {
|
||||||
|
bool di1_level = false;
|
||||||
|
LchmClockCount count;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct RunningStats {
|
||||||
|
uint64_t count = 0;
|
||||||
|
uint64_t clocks_min = std::numeric_limits<uint64_t>::max();
|
||||||
|
uint64_t clocks_max = 0;
|
||||||
|
uint64_t clocks_sum = 0;
|
||||||
|
uint64_t high_min = std::numeric_limits<uint64_t>::max();
|
||||||
|
uint64_t high_max = 0;
|
||||||
|
uint64_t high_sum = 0;
|
||||||
|
uint64_t low_min = std::numeric_limits<uint64_t>::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<uint32_t>::max())) {
|
||||||
|
fail("Invalid integer for " + field_name + ": " + text);
|
||||||
|
}
|
||||||
|
return static_cast<uint32_t>(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<uint32_t, 4> 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();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string parse_legacy_clock_mode(const std::string& text) {
|
||||||
|
const std::string value = trim_copy(text);
|
||||||
|
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_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_syn2]\n"
|
||||||
|
<< "\n"
|
||||||
|
<< "Fixed counting scheme:\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"
|
||||||
|
<< "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_hz:2000000 windows:1000 pullup_syn2\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.legacy_pullup_syn1 = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (arg == "pullup_syn2") {
|
||||||
|
cfg.pullup_syn2 = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (arg == "clean_start") {
|
||||||
|
cfg.legacy_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.legacy_clock_arg_used = true;
|
||||||
|
cfg.legacy_clock_arg = parse_legacy_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");
|
||||||
|
cfg.legacy_duration_ms = true;
|
||||||
|
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<std::string>& 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 <typename Fn>
|
||||||
|
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<Fn>(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<Fn>(addr);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Fn>
|
||||||
|
Fn try_load_symbol(ModuleHandle module, const char* name) {
|
||||||
|
#ifdef _WIN32
|
||||||
|
const auto addr = GetProcAddress(module, name);
|
||||||
|
return reinterpret_cast<Fn>(addr);
|
||||||
|
#else
|
||||||
|
dlerror();
|
||||||
|
void* addr = dlsym(module, name);
|
||||||
|
(void) dlerror();
|
||||||
|
return reinterpret_cast<Fn>(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_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_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<decltype(Create)>(x502_module, "X502_Create");
|
||||||
|
Free = load_symbol<decltype(Free)>(x502_module, "X502_Free");
|
||||||
|
Close = load_symbol<decltype(Close)>(x502_module, "X502_Close");
|
||||||
|
GetErrorString = load_symbol<decltype(GetErrorString)>(x502_module, "X502_GetErrorString");
|
||||||
|
GetDevInfo = try_load_symbol<decltype(GetDevInfo)>(x502_module, "X502_GetDevInfo");
|
||||||
|
GetDevInfo2 = try_load_symbol<decltype(GetDevInfo2)>(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<decltype(SetMode)>(x502_module, "X502_SetMode");
|
||||||
|
StreamsStop = load_symbol<decltype(StreamsStop)>(x502_module, "X502_StreamsStop");
|
||||||
|
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");
|
||||||
|
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");
|
||||||
|
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");
|
||||||
|
GetRecvReadyCount = load_symbol<decltype(GetRecvReadyCount)>(x502_module, "X502_GetRecvReadyCount");
|
||||||
|
Recv = load_symbol<decltype(Recv)>(x502_module, "X502_Recv");
|
||||||
|
ProcessData = load_symbol<decltype(ProcessData)>(x502_module, "X502_ProcessData");
|
||||||
|
|
||||||
|
OpenUsb = load_symbol<decltype(OpenUsb)>(e502_module, "E502_OpenUsb");
|
||||||
|
OpenByIpAddr = load_symbol<decltype(OpenByIpAddr)>(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<TickMs>(GetTickCount64());
|
||||||
|
#else
|
||||||
|
using namespace std::chrono;
|
||||||
|
return static_cast<TickMs>(
|
||||||
|
duration_cast<milliseconds>(steady_clock::now().time_since_epoch()).count());
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
TickMs elapsed_ms(TickMs now, TickMs start) {
|
||||||
|
return (now >= start) ? (now - start) : 0U;
|
||||||
|
}
|
||||||
|
|
||||||
|
#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<unsigned>(info.fpga_ver >> 8U) << "."
|
||||||
|
<< static_cast<unsigned>(info.fpga_ver & 0xFFU) << "\n"
|
||||||
|
<< "PLDA version: " << static_cast<unsigned>(info.plda_ver) << "\n"
|
||||||
|
<< "Board revision: " << static_cast<unsigned>(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<double>(sum) / static_cast<double>(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) {
|
||||||
|
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);
|
||||||
|
|
||||||
|
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, X502_SYNC_INTERNAL), "Set internal sync mode");
|
||||||
|
expect_ok(api, api.SetSyncStartMode(device.hnd, X502_SYNC_INTERNAL), "Set immediate stream start");
|
||||||
|
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_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: 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) {
|
||||||
|
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<uint32_t> raw(cfg.recv_block_words);
|
||||||
|
std::vector<uint32_t> din_buffer(cfg.recv_block_words);
|
||||||
|
|
||||||
|
bool gate_initialized = false;
|
||||||
|
bool last_gate = 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();
|
||||||
|
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 = [&](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, TickMs now) {
|
||||||
|
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
|
||||||
|
<< ", high=" << current.di2_high_clocks
|
||||||
|
<< ", low=" << current.di2_low_clocks;
|
||||||
|
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);
|
||||||
|
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";
|
||||||
|
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 = now;
|
||||||
|
}
|
||||||
|
current.clear();
|
||||||
|
current_step.clear();
|
||||||
|
current_steps.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 " << elapsed_ms(now, last_din_activity)
|
||||||
|
<< " ms ago, last DI_SYN2 edge " << elapsed_ms(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<uint32_t>(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 (elapsed_ms(now, last_stream_activity) >= cfg.clock_wait_ms) {
|
||||||
|
fail("Timeout waiting for DIN stream data in internal clock mode. "
|
||||||
|
"Check device state and DIN stream configuration.");
|
||||||
|
}
|
||||||
|
if (elapsed_ms(now, last_lchm_complete) >= cfg.lchm_wait_ms) {
|
||||||
|
fail_waiting_for_lchm(now);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
last_stream_activity = now;
|
||||||
|
total_raw_words += static_cast<uint64_t>(recvd);
|
||||||
|
|
||||||
|
uint32_t din_count = static_cast<uint32_t>(din_buffer.size());
|
||||||
|
expect_ok(api,
|
||||||
|
api.ProcessData(device.hnd,
|
||||||
|
raw.data(),
|
||||||
|
static_cast<uint32_t>(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 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;
|
||||||
|
last_gate_edge = now;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (gate_initialized && (gate != last_gate)) {
|
||||||
|
++total_gate_edges;
|
||||||
|
last_gate_edge = now;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!in_lchm && gate_initialized && !last_gate && gate) {
|
||||||
|
start_lchm(di1_level);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (in_lchm && last_gate && !gate) {
|
||||||
|
finalize_lchm("di_syn2_fall", now);
|
||||||
|
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);
|
||||||
|
current_step.add(di2_high);
|
||||||
|
}
|
||||||
|
|
||||||
|
last_gate = gate;
|
||||||
|
}
|
||||||
|
|
||||||
|
const TickMs now_after_block = tick_count_ms();
|
||||||
|
if ((stats.count < cfg.windows) && (elapsed_ms(now_after_block, last_lchm_complete) >= cfg.lchm_wait_ms)) {
|
||||||
|
fail_waiting_for_lchm(now_after_block);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (console_stop_requested() && in_lchm) {
|
||||||
|
finalize_lchm("user_stop", tick_count_ms());
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
34
run_di1_group_avg.sh
Executable file
34
run_di1_group_avg.sh
Executable file
@ -0,0 +1,34 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
cd "$SCRIPT_DIR"
|
||||||
|
|
||||||
|
BIN="${BIN:-./main.exe}"
|
||||||
|
TTY_PATH="${TTY_PATH:-/tmp/ttyADC_data}"
|
||||||
|
LIB_DIR="${LIB_DIR:-$HOME/.local/lib}"
|
||||||
|
|
||||||
|
if [[ ! -x "$BIN" ]]; then
|
||||||
|
echo "Binary '$BIN' not found or not executable. Run ./build_main.sh first." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -d "$LIB_DIR" ]]; then
|
||||||
|
export LD_LIBRARY_PATH="${LIB_DIR}${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
exec "$BIN" \
|
||||||
|
clock:internal \
|
||||||
|
internal_ref_hz:2000000 \
|
||||||
|
start:di_syn2_rise \
|
||||||
|
stop:di_syn2_fall \
|
||||||
|
sample_clock_hz:max \
|
||||||
|
range:5\
|
||||||
|
di1:trace \
|
||||||
|
di1_group_avg \
|
||||||
|
duration_ms:100 \
|
||||||
|
packet_limit:0 \
|
||||||
|
do1_toggle_per_frame \
|
||||||
|
profile:amplitude \
|
||||||
|
"tty:${TTY_PATH}" \
|
||||||
|
"$@"
|
||||||
45
run_do1_noise_subtract.sh
Executable file
45
run_do1_noise_subtract.sh
Executable file
@ -0,0 +1,45 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
cd "$SCRIPT_DIR"
|
||||||
|
|
||||||
|
BIN="${BIN:-./main.exe}"
|
||||||
|
TTY_PATH="${TTY_PATH:-/tmp/ttyADC_data}"
|
||||||
|
LIB_DIR="${LIB_DIR:-$HOME/.local/lib}"
|
||||||
|
NOISE_AVG_STEPS="${NOISE_AVG_STEPS:-}"
|
||||||
|
|
||||||
|
if [[ ! -x "$BIN" ]]; then
|
||||||
|
echo "Binary '$BIN' not found or not executable. Run ./build_main.sh first." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -z "$NOISE_AVG_STEPS" ]]; then
|
||||||
|
echo "Set NOISE_AVG_STEPS to a positive integer, for example: NOISE_AVG_STEPS=16" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! [[ "$NOISE_AVG_STEPS" =~ ^[0-9]+$ ]] || [[ "$NOISE_AVG_STEPS" -eq 0 ]]; then
|
||||||
|
echo "NOISE_AVG_STEPS must be a positive integer, got '$NOISE_AVG_STEPS'" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -d "$LIB_DIR" ]]; then
|
||||||
|
export LD_LIBRARY_PATH="${LIB_DIR}${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
exec "$BIN" \
|
||||||
|
clock:internal \
|
||||||
|
internal_ref_hz:2000000 \
|
||||||
|
start:di_syn2_rise \
|
||||||
|
stop:di_syn2_fall \
|
||||||
|
sample_clock_hz:max \
|
||||||
|
range:5 \
|
||||||
|
duration_ms:100 \
|
||||||
|
packet_limit:0 \
|
||||||
|
do1_toggle_per_frame \
|
||||||
|
do1_noise_subtract \
|
||||||
|
"noise_avg_steps:${NOISE_AVG_STEPS}" \
|
||||||
|
profile:phase \
|
||||||
|
"tty:${TTY_PATH}" \
|
||||||
|
"$@"
|
||||||
@ -22,7 +22,9 @@ TtyProtocolWriter::TtyProtocolWriter(std::string path, std::size_t ring_capacity
|
|||||||
|
|
||||||
TtyProtocolWriter::~TtyProtocolWriter() = default;
|
TtyProtocolWriter::~TtyProtocolWriter() = default;
|
||||||
|
|
||||||
void TtyProtocolWriter::emit_packet_start() {}
|
void TtyProtocolWriter::emit_packet_start(uint16_t marker) {
|
||||||
|
(void) marker;
|
||||||
|
}
|
||||||
|
|
||||||
void TtyProtocolWriter::emit_step(uint16_t index, int16_t ch1_avg, int16_t ch2_avg) {
|
void TtyProtocolWriter::emit_step(uint16_t index, int16_t ch1_avg, int16_t ch2_avg) {
|
||||||
(void) index;
|
(void) index;
|
||||||
@ -97,6 +99,37 @@ void set_fd_raw(int fd) {
|
|||||||
throw std::runtime_error(io_error("Cannot read tty attributes for", std::to_string(fd)));
|
throw std::runtime_error(io_error("Cannot read tty attributes for", std::to_string(fd)));
|
||||||
}
|
}
|
||||||
::cfmakeraw(&tio);
|
::cfmakeraw(&tio);
|
||||||
|
tio.c_cc[VINTR] = _POSIX_VDISABLE;
|
||||||
|
tio.c_cc[VQUIT] = _POSIX_VDISABLE;
|
||||||
|
tio.c_cc[VERASE] = _POSIX_VDISABLE;
|
||||||
|
tio.c_cc[VKILL] = _POSIX_VDISABLE;
|
||||||
|
tio.c_cc[VEOF] = _POSIX_VDISABLE;
|
||||||
|
tio.c_cc[VTIME] = 0;
|
||||||
|
tio.c_cc[VMIN] = 1;
|
||||||
|
#ifdef VSWTC
|
||||||
|
tio.c_cc[VSWTC] = _POSIX_VDISABLE;
|
||||||
|
#endif
|
||||||
|
tio.c_cc[VSTART] = _POSIX_VDISABLE;
|
||||||
|
tio.c_cc[VSTOP] = _POSIX_VDISABLE;
|
||||||
|
tio.c_cc[VSUSP] = _POSIX_VDISABLE;
|
||||||
|
#ifdef VEOL
|
||||||
|
tio.c_cc[VEOL] = _POSIX_VDISABLE;
|
||||||
|
#endif
|
||||||
|
#ifdef VREPRINT
|
||||||
|
tio.c_cc[VREPRINT] = _POSIX_VDISABLE;
|
||||||
|
#endif
|
||||||
|
#ifdef VDISCARD
|
||||||
|
tio.c_cc[VDISCARD] = _POSIX_VDISABLE;
|
||||||
|
#endif
|
||||||
|
#ifdef VWERASE
|
||||||
|
tio.c_cc[VWERASE] = _POSIX_VDISABLE;
|
||||||
|
#endif
|
||||||
|
#ifdef VLNEXT
|
||||||
|
tio.c_cc[VLNEXT] = _POSIX_VDISABLE;
|
||||||
|
#endif
|
||||||
|
#ifdef VEOL2
|
||||||
|
tio.c_cc[VEOL2] = _POSIX_VDISABLE;
|
||||||
|
#endif
|
||||||
if (::tcsetattr(fd, TCSANOW, &tio) != 0) {
|
if (::tcsetattr(fd, TCSANOW, &tio) != 0) {
|
||||||
throw std::runtime_error(io_error("Cannot apply raw tty attributes to", std::to_string(fd)));
|
throw std::runtime_error(io_error("Cannot apply raw tty attributes to", std::to_string(fd)));
|
||||||
}
|
}
|
||||||
@ -227,8 +260,8 @@ TtyProtocolWriter::~TtyProtocolWriter() {
|
|||||||
close_fd_if_open(impl_->fd);
|
close_fd_if_open(impl_->fd);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TtyProtocolWriter::emit_packet_start() {
|
void TtyProtocolWriter::emit_packet_start(uint16_t marker) {
|
||||||
enqueue_frame(0x000A, 0xFFFF, 0xFFFF, 0xFFFF);
|
enqueue_frame(marker, 0xFFFF, 0xFFFF, 0xFFFF);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TtyProtocolWriter::emit_step(uint16_t index, int16_t ch1_avg, int16_t ch2_avg) {
|
void TtyProtocolWriter::emit_step(uint16_t index, int16_t ch1_avg, int16_t ch2_avg) {
|
||||||
|
|||||||
@ -21,7 +21,7 @@ public:
|
|||||||
TtyProtocolWriter(TtyProtocolWriter&& other) noexcept = delete;
|
TtyProtocolWriter(TtyProtocolWriter&& other) noexcept = delete;
|
||||||
TtyProtocolWriter& operator=(TtyProtocolWriter&& other) noexcept = delete;
|
TtyProtocolWriter& operator=(TtyProtocolWriter&& other) noexcept = delete;
|
||||||
|
|
||||||
void emit_packet_start();
|
void emit_packet_start(uint16_t marker = 0x000A);
|
||||||
void emit_step(uint16_t index, int16_t ch1_avg, int16_t ch2_avg);
|
void emit_step(uint16_t index, int16_t ch1_avg, int16_t ch2_avg);
|
||||||
void enqueue_encoded_frames(const uint16_t* words, std::size_t frame_count);
|
void enqueue_encoded_frames(const uint16_t* words, std::size_t frame_count);
|
||||||
StatsSnapshot stats() const;
|
StatsSnapshot stats() const;
|
||||||
|
|||||||
Reference in New Issue
Block a user