add pin probe
This commit is contained in:
779
adc_pin_probe.cpp
Normal file
779
adc_pin_probe.cpp
Normal file
@ -0,0 +1,779 @@
|
||||
#ifdef _WIN32
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
#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 <cmath>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <limits>
|
||||
#include <optional>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#else
|
||||
#include <chrono>
|
||||
#include <dlfcn.h>
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
|
||||
struct Config {
|
||||
std::string serial;
|
||||
std::optional<uint32_t> ip_addr;
|
||||
|
||||
double sample_clock_hz = 50000.0;
|
||||
double capture_ms = 120.0;
|
||||
uint32_t range = X502_ADC_RANGE_5;
|
||||
uint32_t top = 8;
|
||||
std::vector<uint32_t> exclude_phy = {2, 3, 18, 19};
|
||||
|
||||
uint32_t recv_block_words = 8192;
|
||||
uint32_t recv_timeout_ms = 50;
|
||||
};
|
||||
|
||||
[[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);
|
||||
if (clean.empty()) {
|
||||
fail("Missing integer value for " + field_name);
|
||||
}
|
||||
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);
|
||||
if (clean.empty()) {
|
||||
fail("Missing floating point value for " + field_name);
|
||||
}
|
||||
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] << 24) | (parts[1] << 16) | (parts[2] << 8) | parts[3];
|
||||
}
|
||||
|
||||
std::string ipv4_to_string(uint32_t ip_addr) {
|
||||
std::ostringstream out;
|
||||
out << ((ip_addr >> 24) & 0xFF) << '.'
|
||||
<< ((ip_addr >> 16) & 0xFF) << '.'
|
||||
<< ((ip_addr >> 8) & 0xFF) << '.'
|
||||
<< (ip_addr & 0xFF);
|
||||
return out.str();
|
||||
}
|
||||
|
||||
uint32_t parse_range(const std::string& text) {
|
||||
const double value = parse_double(text, "range");
|
||||
if (std::fabs(value - 10.0) < 1e-9) {
|
||||
return X502_ADC_RANGE_10;
|
||||
}
|
||||
if (std::fabs(value - 5.0) < 1e-9) {
|
||||
return X502_ADC_RANGE_5;
|
||||
}
|
||||
if (std::fabs(value - 2.0) < 1e-9) {
|
||||
return X502_ADC_RANGE_2;
|
||||
}
|
||||
if (std::fabs(value - 1.0) < 1e-9) {
|
||||
return X502_ADC_RANGE_1;
|
||||
}
|
||||
if (std::fabs(value - 0.5) < 1e-9) {
|
||||
return X502_ADC_RANGE_05;
|
||||
}
|
||||
if (std::fabs(value - 0.2) < 1e-9) {
|
||||
return X502_ADC_RANGE_02;
|
||||
}
|
||||
fail("Unsupported E-502 range: " + text);
|
||||
}
|
||||
|
||||
double range_to_volts(uint32_t range) {
|
||||
switch (range) {
|
||||
case X502_ADC_RANGE_10:
|
||||
return 10.0;
|
||||
case X502_ADC_RANGE_5:
|
||||
return 5.0;
|
||||
case X502_ADC_RANGE_2:
|
||||
return 2.0;
|
||||
case X502_ADC_RANGE_1:
|
||||
return 1.0;
|
||||
case X502_ADC_RANGE_05:
|
||||
return 0.5;
|
||||
case X502_ADC_RANGE_02:
|
||||
return 0.2;
|
||||
default:
|
||||
fail("Unknown ADC range enum");
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<uint32_t> parse_exclude_phy(const std::string& text) {
|
||||
const std::string clean = trim_copy(text);
|
||||
if (clean.empty() || (clean == "none")) {
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<uint32_t> result;
|
||||
std::stringstream ss(clean);
|
||||
std::string token;
|
||||
while (std::getline(ss, token, ',')) {
|
||||
const std::string item = trim_copy(token);
|
||||
if (item.empty()) {
|
||||
continue;
|
||||
}
|
||||
const uint32_t phy = parse_u32(item, "exclude_phy");
|
||||
if (phy >= X502_ADC_COMM_CH_CNT) {
|
||||
fail("exclude_phy has out-of-range channel " + std::to_string(phy) + " (allowed 0..31)");
|
||||
}
|
||||
if (std::find(result.begin(), result.end(), phy) == result.end()) {
|
||||
result.push_back(phy);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string phy_channel_name(uint32_t phy_ch) {
|
||||
if (phy_ch < 16U) {
|
||||
return "X" + std::to_string(phy_ch + 1U);
|
||||
}
|
||||
return "Y" + std::to_string((phy_ch - 16U) + 1U);
|
||||
}
|
||||
|
||||
void print_help(const char* exe_name) {
|
||||
std::cout
|
||||
<< "Usage:\n"
|
||||
<< " " << exe_name << " [serial:SN] [ip:192.168.0.10]\n"
|
||||
<< " [sample_clock_hz:50000] [capture_ms:120] [range:5]\n"
|
||||
<< " [exclude_phy:2,3,18,19|none] [top:8]\n"
|
||||
<< " [recv_block:8192] [recv_timeout_ms:50]\n"
|
||||
<< "\n"
|
||||
<< "Purpose:\n"
|
||||
<< " Scan single-ended analog inputs (mode:comm, phy 0..31) and rank channels\n"
|
||||
<< " by activity to locate where the ADC signal actually arrives.\n"
|
||||
<< "\n"
|
||||
<< "Defaults:\n"
|
||||
<< " sample_clock_hz:50000\n"
|
||||
<< " capture_ms:120\n"
|
||||
<< " range:5 -> +/-5 V\n"
|
||||
<< " top:8\n"
|
||||
<< " exclude_phy:2,3,18,19 -> excludes X3,Y3,X4,Y4 from current diff setup\n"
|
||||
<< "\n"
|
||||
<< "Single-ended physical channel mapping:\n"
|
||||
<< " 0..15 -> X1..X16\n"
|
||||
<< " 16..31 -> Y1..Y16\n"
|
||||
<< "\n"
|
||||
<< "Scoring:\n"
|
||||
<< " ranking priority: stddev first, then peak-to-peak (p2p)\n"
|
||||
<< "\n"
|
||||
<< "Example:\n"
|
||||
<< " " << exe_name
|
||||
<< " sample_clock_hz:50000 capture_ms:120 range:5 exclude_phy:2,3,18,19 top:8\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 (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, "sample_clock_hz:")) {
|
||||
cfg.sample_clock_hz = parse_double(arg.substr(16), "sample_clock_hz");
|
||||
continue;
|
||||
}
|
||||
if (starts_with(arg, "capture_ms:")) {
|
||||
cfg.capture_ms = parse_double(arg.substr(11), "capture_ms");
|
||||
continue;
|
||||
}
|
||||
if (starts_with(arg, "range:")) {
|
||||
cfg.range = parse_range(arg.substr(6));
|
||||
continue;
|
||||
}
|
||||
if (starts_with(arg, "exclude_phy:")) {
|
||||
cfg.exclude_phy = parse_exclude_phy(arg.substr(12));
|
||||
continue;
|
||||
}
|
||||
if (starts_with(arg, "top:")) {
|
||||
cfg.top = parse_u32(arg.substr(4), "top");
|
||||
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;
|
||||
}
|
||||
fail("Unknown argument: " + arg);
|
||||
}
|
||||
|
||||
if (cfg.sample_clock_hz <= 0.0) {
|
||||
fail("sample_clock_hz must be > 0");
|
||||
}
|
||||
if (cfg.capture_ms <= 0.0) {
|
||||
fail("capture_ms must be > 0");
|
||||
}
|
||||
if (cfg.top == 0U) {
|
||||
fail("top must be > 0");
|
||||
}
|
||||
if (cfg.recv_block_words == 0U) {
|
||||
fail("recv_block must be > 0");
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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_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_SetLChannelCount) SetLChannelCount = nullptr;
|
||||
decltype(&X502_SetLChannel) SetLChannel = nullptr;
|
||||
decltype(&X502_SetAdcFreq) SetAdcFreq = nullptr;
|
||||
decltype(&X502_Configure) Configure = nullptr;
|
||||
decltype(&X502_StreamsEnable) StreamsEnable = nullptr;
|
||||
decltype(&X502_StreamsStart) StreamsStart = 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");
|
||||
GetDevInfo2 = load_symbol<decltype(GetDevInfo2)>(x502_module, "X502_GetDevInfo2");
|
||||
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");
|
||||
SetLChannelCount = load_symbol<decltype(SetLChannelCount)>(x502_module, "X502_SetLChannelCount");
|
||||
SetLChannel = load_symbol<decltype(SetLChannel)>(x502_module, "X502_SetLChannel");
|
||||
SetAdcFreq = load_symbol<decltype(SetAdcFreq)>(x502_module, "X502_SetAdcFreq");
|
||||
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");
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
uint64_t tick_count_ms() {
|
||||
#ifdef _WIN32
|
||||
return static_cast<uint64_t>(GetTickCount64());
|
||||
#else
|
||||
using namespace std::chrono;
|
||||
return static_cast<uint64_t>(
|
||||
duration_cast<milliseconds>(steady_clock::now().time_since_epoch()).count());
|
||||
#endif
|
||||
}
|
||||
|
||||
struct RunningStats {
|
||||
std::size_t count = 0;
|
||||
double mean = 0.0;
|
||||
double m2 = 0.0;
|
||||
double sum_sq = 0.0;
|
||||
double min = std::numeric_limits<double>::infinity();
|
||||
double max = -std::numeric_limits<double>::infinity();
|
||||
|
||||
void add(double value) {
|
||||
++count;
|
||||
const double delta = value - mean;
|
||||
mean += delta / static_cast<double>(count);
|
||||
const double delta2 = value - mean;
|
||||
m2 += delta * delta2;
|
||||
sum_sq += value * value;
|
||||
if (value < min) {
|
||||
min = value;
|
||||
}
|
||||
if (value > max) {
|
||||
max = value;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct ChannelResult {
|
||||
uint32_t phy_ch = 0;
|
||||
std::string pin_name;
|
||||
std::size_t sample_count = 0;
|
||||
double score = -1.0;
|
||||
double stddev = 0.0;
|
||||
double peak_to_peak = 0.0;
|
||||
double rms = 0.0;
|
||||
double mean = 0.0;
|
||||
double min = 0.0;
|
||||
double max = 0.0;
|
||||
};
|
||||
|
||||
void configure_base_capture(const Api& api, DeviceHandle& device, const Config& cfg) {
|
||||
expect_ok(api, api.SetMode(device.hnd, X502_MODE_FPGA), "Set FPGA mode");
|
||||
api.StreamsStop(device.hnd);
|
||||
device.streams_started = false;
|
||||
api.StreamsDisable(device.hnd, X502_STREAM_ALL_IN | X502_STREAM_ALL_OUT);
|
||||
|
||||
expect_ok(api, api.SetSyncMode(device.hnd, X502_SYNC_INTERNAL), "Set sync mode");
|
||||
expect_ok(api, api.SetSyncStartMode(device.hnd, X502_SYNC_INTERNAL), "Set sync start mode");
|
||||
expect_ok(api, api.SetRefFreq(device.hnd, X502_REF_FREQ_2000KHZ), "Set internal reference frequency");
|
||||
|
||||
double adc_freq = cfg.sample_clock_hz;
|
||||
double frame_freq = 0.0;
|
||||
expect_ok(api, api.SetAdcFreq(device.hnd, &adc_freq, &frame_freq), "Set ADC frequency");
|
||||
}
|
||||
|
||||
ChannelResult scan_phy_channel(const Api& api, DeviceHandle& device, const Config& cfg, uint32_t phy_ch) {
|
||||
ChannelResult result;
|
||||
result.phy_ch = phy_ch;
|
||||
result.pin_name = phy_channel_name(phy_ch);
|
||||
|
||||
configure_base_capture(api, device, cfg);
|
||||
expect_ok(api, api.SetLChannelCount(device.hnd, 1), "Set logical channel count");
|
||||
expect_ok(api,
|
||||
api.SetLChannel(device.hnd, 0, phy_ch, X502_LCH_MODE_COMM, cfg.range, 1),
|
||||
"Set logical channel");
|
||||
expect_ok(api, api.Configure(device.hnd, 0), "Configure device");
|
||||
expect_ok(api, api.StreamsEnable(device.hnd, X502_STREAM_ADC), "Enable ADC stream");
|
||||
expect_ok(api, api.StreamsStart(device.hnd), "Start streams");
|
||||
device.streams_started = true;
|
||||
|
||||
std::vector<uint32_t> raw(cfg.recv_block_words);
|
||||
std::vector<double> adc(cfg.recv_block_words);
|
||||
RunningStats stats;
|
||||
|
||||
const uint64_t capture_until = tick_count_ms() + static_cast<uint64_t>(std::llround(cfg.capture_ms));
|
||||
while (tick_count_ms() < capture_until) {
|
||||
const int32_t recvd = api.Recv(device.hnd, raw.data(), cfg.recv_block_words, cfg.recv_timeout_ms);
|
||||
if (recvd < 0) {
|
||||
fail("X502_Recv failed while scanning " + result.pin_name + ": " + x502_error(api, recvd));
|
||||
}
|
||||
if (recvd == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
uint32_t adc_count = static_cast<uint32_t>(adc.size());
|
||||
expect_ok(api,
|
||||
api.ProcessData(device.hnd,
|
||||
raw.data(),
|
||||
static_cast<uint32_t>(recvd),
|
||||
X502_PROC_FLAGS_VOLT,
|
||||
adc.data(),
|
||||
&adc_count,
|
||||
nullptr,
|
||||
nullptr),
|
||||
"ProcessData");
|
||||
for (uint32_t i = 0; i < adc_count; ++i) {
|
||||
stats.add(adc[i]);
|
||||
}
|
||||
}
|
||||
|
||||
api.StreamsStop(device.hnd);
|
||||
device.streams_started = false;
|
||||
api.StreamsDisable(device.hnd, X502_STREAM_ALL_IN | X502_STREAM_ALL_OUT);
|
||||
|
||||
result.sample_count = stats.count;
|
||||
if (stats.count == 0U) {
|
||||
return result;
|
||||
}
|
||||
|
||||
result.mean = stats.mean;
|
||||
result.min = stats.min;
|
||||
result.max = stats.max;
|
||||
result.peak_to_peak = stats.max - stats.min;
|
||||
result.rms = std::sqrt(stats.sum_sq / static_cast<double>(stats.count));
|
||||
const double variance =
|
||||
(stats.count > 1U) ? (stats.m2 / static_cast<double>(stats.count - 1U)) : 0.0;
|
||||
result.stddev = std::sqrt(std::max(0.0, variance));
|
||||
|
||||
// Stddev is the dominant rank metric, p2p is a secondary tie-breaker.
|
||||
result.score = result.stddev * 1000000.0 + result.peak_to_peak;
|
||||
return result;
|
||||
}
|
||||
|
||||
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 >> 8) << "."
|
||||
<< static_cast<unsigned>(info.fpga_ver & 0xFF) << "\n";
|
||||
}
|
||||
|
||||
void print_ranked_table(const std::vector<ChannelResult>& sorted) {
|
||||
std::cout << "\nRanked channels (sorted by stddev, then p2p):\n";
|
||||
std::cout << std::left
|
||||
<< std::setw(5) << "Rank"
|
||||
<< std::setw(6) << "phy"
|
||||
<< std::setw(6) << "pin"
|
||||
<< std::setw(12) << "samples"
|
||||
<< std::setw(14) << "score"
|
||||
<< std::setw(14) << "stddev[V]"
|
||||
<< std::setw(14) << "p2p[V]"
|
||||
<< std::setw(14) << "rms[V]"
|
||||
<< std::setw(14) << "mean[V]"
|
||||
<< std::setw(14) << "min[V]"
|
||||
<< std::setw(14) << "max[V]" << "\n";
|
||||
|
||||
std::cout << std::fixed << std::setprecision(6);
|
||||
std::size_t rank = 1;
|
||||
for (const auto& item : sorted) {
|
||||
std::cout << std::left
|
||||
<< std::setw(5) << rank
|
||||
<< std::setw(6) << item.phy_ch
|
||||
<< std::setw(6) << item.pin_name
|
||||
<< std::setw(12) << item.sample_count
|
||||
<< std::setw(14) << item.score
|
||||
<< std::setw(14) << item.stddev
|
||||
<< std::setw(14) << item.peak_to_peak
|
||||
<< std::setw(14) << item.rms
|
||||
<< std::setw(14) << item.mean
|
||||
<< std::setw(14) << item.min
|
||||
<< std::setw(14) << item.max
|
||||
<< "\n";
|
||||
++rank;
|
||||
}
|
||||
}
|
||||
|
||||
int run(const Config& cfg) {
|
||||
Api api;
|
||||
DeviceHandle device(api);
|
||||
|
||||
int32_t err = X502_ERR_OK;
|
||||
if (cfg.ip_addr.has_value()) {
|
||||
err = api.OpenByIpAddr(device.hnd, *cfg.ip_addr, 0, 5000);
|
||||
} else {
|
||||
err = api.OpenUsb(device.hnd, cfg.serial.empty() ? nullptr : cfg.serial.c_str());
|
||||
}
|
||||
expect_ok(api, err, "Open device");
|
||||
device.opened = true;
|
||||
|
||||
t_x502_info info{};
|
||||
err = api.GetDevInfo2(device.hnd, &info, sizeof(info));
|
||||
expect_ok(api, err, "Get device info");
|
||||
print_device_info(info);
|
||||
|
||||
std::array<bool, X502_ADC_COMM_CH_CNT> excluded{};
|
||||
for (uint32_t phy : cfg.exclude_phy) {
|
||||
if (phy >= X502_ADC_COMM_CH_CNT) {
|
||||
fail("exclude_phy has out-of-range channel: " + std::to_string(phy));
|
||||
}
|
||||
excluded[phy] = true;
|
||||
}
|
||||
|
||||
std::vector<uint32_t> scan_channels;
|
||||
scan_channels.reserve(X502_ADC_COMM_CH_CNT);
|
||||
for (uint32_t phy = 0; phy < X502_ADC_COMM_CH_CNT; ++phy) {
|
||||
if (!excluded[phy]) {
|
||||
scan_channels.push_back(phy);
|
||||
}
|
||||
}
|
||||
if (scan_channels.empty()) {
|
||||
fail("No channels left to scan after exclude_phy filtering");
|
||||
}
|
||||
|
||||
std::cout << "Probe settings:\n"
|
||||
<< " source: "
|
||||
<< (cfg.ip_addr ? ("ip:" + ipv4_to_string(*cfg.ip_addr))
|
||||
: (cfg.serial.empty() ? std::string("usb:auto") : ("serial:" + cfg.serial)))
|
||||
<< "\n"
|
||||
<< " mode: comm (single-ended), channel_count=1\n"
|
||||
<< " sample_clock_hz: " << cfg.sample_clock_hz << "\n"
|
||||
<< " capture_ms per channel: " << cfg.capture_ms << "\n"
|
||||
<< " ADC range: +/-" << range_to_volts(cfg.range) << " V\n"
|
||||
<< " top: " << cfg.top << "\n"
|
||||
<< " excluded channels: ";
|
||||
if (cfg.exclude_phy.empty()) {
|
||||
std::cout << "none";
|
||||
} else {
|
||||
for (std::size_t i = 0; i < cfg.exclude_phy.size(); ++i) {
|
||||
if (i != 0U) {
|
||||
std::cout << ",";
|
||||
}
|
||||
std::cout << cfg.exclude_phy[i] << "(" << phy_channel_name(cfg.exclude_phy[i]) << ")";
|
||||
}
|
||||
}
|
||||
std::cout << "\n";
|
||||
|
||||
std::vector<ChannelResult> results;
|
||||
results.reserve(scan_channels.size());
|
||||
for (std::size_t i = 0; i < scan_channels.size(); ++i) {
|
||||
const uint32_t phy = scan_channels[i];
|
||||
std::cout << "Scanning [" << (i + 1U) << "/" << scan_channels.size() << "] "
|
||||
<< "phy=" << phy << " (" << phy_channel_name(phy) << ")...\n";
|
||||
results.push_back(scan_phy_channel(api, device, cfg, phy));
|
||||
}
|
||||
|
||||
api.StreamsStop(device.hnd);
|
||||
device.streams_started = false;
|
||||
api.StreamsDisable(device.hnd, X502_STREAM_ALL_IN | X502_STREAM_ALL_OUT);
|
||||
|
||||
std::sort(results.begin(), results.end(), [](const ChannelResult& a, const ChannelResult& b) {
|
||||
const bool a_valid = a.sample_count != 0U;
|
||||
const bool b_valid = b.sample_count != 0U;
|
||||
if (a_valid != b_valid) {
|
||||
return a_valid;
|
||||
}
|
||||
if (a.stddev != b.stddev) {
|
||||
return a.stddev > b.stddev;
|
||||
}
|
||||
if (a.peak_to_peak != b.peak_to_peak) {
|
||||
return a.peak_to_peak > b.peak_to_peak;
|
||||
}
|
||||
return a.phy_ch < b.phy_ch;
|
||||
});
|
||||
|
||||
print_ranked_table(results);
|
||||
|
||||
const std::size_t top_count = std::min<std::size_t>(cfg.top, results.size());
|
||||
std::cout << "\nTop " << top_count << " candidate(s):\n";
|
||||
for (std::size_t i = 0; i < top_count; ++i) {
|
||||
const auto& item = results[i];
|
||||
std::cout << " #" << (i + 1U)
|
||||
<< " phy=" << item.phy_ch
|
||||
<< " pin=" << item.pin_name
|
||||
<< " stddev=" << std::fixed << std::setprecision(6) << item.stddev << " V"
|
||||
<< " p2p=" << item.peak_to_peak << " V"
|
||||
<< " score=" << item.score
|
||||
<< " samples=" << item.sample_count
|
||||
<< "\n";
|
||||
}
|
||||
|
||||
if (!results.empty() && (results.front().sample_count == 0U)) {
|
||||
std::cout << "\nWarning: no ADC samples were decoded. Check device connection and sample clock setup.\n";
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user