Compare commits

...

4 Commits

Author SHA1 Message Date
awe
3899e99f80 test tty writer 2026-04-09 17:32:34 +03:00
awe
cb95b5ff48 Merge branch 'main' of ssh://git.radiophotonics.ru:2222/awe/kamil_adc 2026-04-09 17:30:23 +03:00
awe
9ad2b1a140 Ignore capture binary 2026-04-09 17:20:32 +03:00
awe
a696b73116 Ignore generated artifacts 2026-04-09 17:20:02 +03:00
3 changed files with 197 additions and 24 deletions

View File

@ -381,7 +381,7 @@ void print_help(const char* exe_name) {
<< " [internal_ref_hz:2000000]\n"
<< " [di1:zero|trace|ignore]\n"
<< " [duration_ms:100] [packet_limit:0] [csv:capture.csv] [svg:capture.svg]\n"
<< " [live_html:live_plot.html] [live_json:live_plot.json] [tty:/dev/ttyADC_data]\n"
<< " [live_html:live_plot.html] [live_json:live_plot.json] [tty:/tmp/ttyADC_data]\n"
<< " [recv_block:32768] [stats_period_ms:1000] [live_update_period_ms:1000] [svg_history_packets:50] [start_wait_ms:10000]\n"
<< " [buffer_words:8388608] [step_words:32768]\n"
<< " [pullup_syn1] [pullup_syn2] [pulldown_conv_in] [pulldown_start_in]\n"
@ -412,7 +412,7 @@ void print_help(const char* exe_name) {
<< " buffer_words:8388608 -> input stream buffer size in 32-bit words\n"
<< " step_words:32768 -> input stream transfer step in 32-bit words\n"
<< " live_html/live_json -> live graph files updated as packets arrive\n"
<< " tty:/dev/ttyADC_data -> write binary step frames to a Linux/POSIX tty path\n"
<< " tty:/tmp/ttyADC_data -> write binary step frames to a Linux/POSIX tty or PTY link path\n"
<< " If sample_clock_hz is omitted together with clock:internal, the maximum ADC speed is used\n"
<< "\n"
<< "Differential physical channel mapping:\n"
@ -747,6 +747,19 @@ Fn load_symbol(ModuleHandle module, const char* name) {
#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;
@ -755,6 +768,7 @@ struct Api {
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;
@ -804,7 +818,11 @@ struct Api {
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");
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");
@ -859,6 +877,8 @@ void expect_ok(const Api& api, int32_t err, const std::string& what) {
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 kStreamInputAdcFlag = 0x80000000U;
constexpr uint32_t kStreamInputCalibratedAdcFlag = 0x40000000U;
using TickMs = uint64_t;
@ -1012,6 +1032,30 @@ int16_t pack_raw_code_to_int16(double avg_raw_code) {
return static_cast<int16_t>(clamped);
}
bool try_extract_raw_adc_code(uint32_t word, double& raw_code) {
if ((word & kStreamInputAdcFlag) == 0U) {
return false;
}
int32_t value = 0;
if ((word & kStreamInputCalibratedAdcFlag) != 0U) {
const uint32_t payload = word & 0x00FFFFFFU;
value = static_cast<int32_t>(payload);
if ((payload & 0x00800000U) != 0U) {
value |= static_cast<int32_t>(0xFF000000U);
}
} else {
const uint32_t payload = word & 0x0000FFFFU;
value = static_cast<int32_t>(payload);
if ((payload & 0x00008000U) != 0U) {
value |= static_cast<int32_t>(0xFFFF0000U);
}
}
raw_code = static_cast<double>(value);
return true;
}
struct ConsoleCtrlGuard {
bool installed = false;
@ -1113,7 +1157,11 @@ int run(const Config& cfg) {
device.opened = true;
t_x502_info info{};
err = api.GetDevInfo2(device.hnd, &info, sizeof(info));
if (api.GetDevInfo2 != nullptr) {
err = api.GetDevInfo2(device.hnd, &info, sizeof(info));
} else {
err = api.GetDevInfo(device.hnd, &info);
}
expect_ok(api, err, "Get device info");
print_device_info(info);
@ -1356,7 +1404,9 @@ int run(const Config& cfg) {
return;
}
if (tty_step.next_step_index >= 0xFFFFU) {
fail("TTY protocol step index overflow within packet");
std::cerr << "Warning: TTY protocol step index overflow, forcing tty packet restart\n";
tty_step.reset_packet();
tty_writer->emit_packet_start();
}
const double ch1_avg = tty_step.sum_ch1 / static_cast<double>(tty_step.count_ch1);
@ -1531,18 +1581,18 @@ int run(const Config& cfg) {
uint32_t raw_adc_count = 0;
if (tty_writer) {
raw_adc_count = static_cast<uint32_t>(adc_raw_buffer.size());
const int32_t raw_process_err = api.ProcessData(device.hnd,
raw.data(),
static_cast<uint32_t>(recvd),
0,
adc_raw_buffer.data(),
&raw_adc_count,
nullptr,
nullptr);
expect_ok(api, raw_process_err, "Process raw ADC data");
for (std::size_t i = 0; i < recvd; ++i) {
double raw_code = 0.0;
if (!try_extract_raw_adc_code(raw[i], raw_code)) {
continue;
}
if (raw_adc_count >= adc_raw_buffer.size()) {
fail("Raw ADC parsing overflowed the temporary buffer");
}
adc_raw_buffer[raw_adc_count++] = raw_code;
}
if (raw_adc_count != adc_count) {
fail("Raw ADC processing returned a different sample count than voltage processing");
fail("Raw ADC parsing returned a different sample count than voltage processing");
}
}
@ -1624,7 +1674,14 @@ int run(const Config& cfg) {
start_packet();
}
if (!packet_active && start_edge) {
if (packet_active && start_edge) {
finish_packet(PacketCloseReason::ExternalStopEdge);
if ((cfg.packet_limit != 0U) && (total_completed_packets >= cfg.packet_limit)) {
stop_loop_requested = true;
continue;
}
start_packet();
} else if (!packet_active && start_edge) {
start_packet();
}

View File

@ -38,11 +38,17 @@ void TtyProtocolWriter::close_fd() noexcept {}
#else
#include <array>
#include <cerrno>
#include <cstring>
#include <fcntl.h>
#include <limits.h>
#include <optional>
#include <pty.h>
#include <sstream>
#include <sys/stat.h>
#include <sys/types.h>
#include <termios.h>
#include <unistd.h>
namespace {
@ -53,31 +59,113 @@ std::string io_error(const std::string& action, const std::string& path) {
return out.str();
}
} // namespace
TtyProtocolWriter::TtyProtocolWriter(std::string path) : path_(std::move(path)) {
fd_ = ::open(path_.c_str(), O_WRONLY | O_NOCTTY);
if (fd_ < 0) {
throw std::runtime_error(io_error("Cannot open tty output", path_));
void set_fd_raw(int fd) {
struct termios tio {};
if (::tcgetattr(fd, &tio) != 0) {
throw std::runtime_error(io_error("Cannot read tty attributes for", std::to_string(fd)));
}
::cfmakeraw(&tio);
if (::tcsetattr(fd, TCSANOW, &tio) != 0) {
throw std::runtime_error(io_error("Cannot apply raw tty attributes to", std::to_string(fd)));
}
}
bool is_character_device_path(const std::string& path) {
struct stat st {};
if (::stat(path.c_str(), &st) != 0) {
if (errno == ENOENT) {
return false;
}
throw std::runtime_error(io_error("Cannot stat tty output", path));
}
return S_ISCHR(st.st_mode);
}
std::optional<std::string> read_link_target(const std::string& path) {
std::array<char, PATH_MAX> buf {};
const ssize_t len = ::readlink(path.c_str(), buf.data(), buf.size() - 1U);
if (len < 0) {
if (errno == EINVAL || errno == ENOENT) {
return std::nullopt;
}
throw std::runtime_error(io_error("Cannot read symlink", path));
}
buf[static_cast<std::size_t>(len)] = '\0';
return std::string(buf.data());
}
} // namespace
TtyProtocolWriter::TtyProtocolWriter(std::string path) : path_(std::move(path)) {
if (is_character_device_path(path_)) {
fd_ = ::open(path_.c_str(), O_WRONLY | O_NOCTTY);
if (fd_ < 0) {
throw std::runtime_error(io_error("Cannot open tty output", path_));
}
return;
}
std::array<char, PATH_MAX> slave_name {};
if (::openpty(&fd_, &slave_fd_, slave_name.data(), nullptr, nullptr) != 0) {
throw std::runtime_error(io_error("Cannot create PTY bridge for", path_));
}
try {
slave_path_ = slave_name.data();
set_fd_raw(slave_fd_);
struct stat st {};
if (::lstat(path_.c_str(), &st) == 0) {
if (!S_ISLNK(st.st_mode) && !S_ISREG(st.st_mode)) {
throw std::runtime_error("Refusing to replace non-link path '" + path_ + "'");
}
if (::unlink(path_.c_str()) != 0) {
throw std::runtime_error(io_error("Cannot remove existing tty link", path_));
}
} else if (errno != ENOENT) {
throw std::runtime_error(io_error("Cannot inspect tty link path", path_));
}
if (::symlink(slave_path_.c_str(), path_.c_str()) != 0) {
throw std::runtime_error(io_error("Cannot create tty symlink", path_));
}
} catch (...) {
close_slave_fd();
close_fd();
throw;
}
owns_link_ = true;
}
TtyProtocolWriter::~TtyProtocolWriter() {
remove_owned_link();
close_slave_fd();
close_fd();
}
TtyProtocolWriter::TtyProtocolWriter(TtyProtocolWriter&& other) noexcept
: path_(std::move(other.path_)),
fd_(other.fd_) {
fd_(other.fd_),
slave_fd_(other.slave_fd_),
slave_path_(std::move(other.slave_path_)),
owns_link_(other.owns_link_) {
other.fd_ = -1;
other.slave_fd_ = -1;
other.owns_link_ = false;
}
TtyProtocolWriter& TtyProtocolWriter::operator=(TtyProtocolWriter&& other) noexcept {
if (this != &other) {
remove_owned_link();
close_slave_fd();
close_fd();
path_ = std::move(other.path_);
fd_ = other.fd_;
slave_fd_ = other.slave_fd_;
slave_path_ = std::move(other.slave_path_);
owns_link_ = other.owns_link_;
other.fd_ = -1;
other.slave_fd_ = -1;
other.owns_link_ = false;
}
return *this;
}
@ -125,4 +213,27 @@ void TtyProtocolWriter::close_fd() noexcept {
}
}
void TtyProtocolWriter::close_slave_fd() noexcept {
if (slave_fd_ >= 0) {
::close(slave_fd_);
slave_fd_ = -1;
}
}
void TtyProtocolWriter::remove_owned_link() noexcept {
if (!owns_link_ || path_.empty()) {
return;
}
try {
const auto target = read_link_target(path_);
if (target && (*target == slave_path_)) {
::unlink(path_.c_str());
}
} catch (...) {
}
owns_link_ = false;
}
#endif

View File

@ -21,9 +21,14 @@ public:
private:
void write_frame(uint16_t word0, uint16_t word1, uint16_t word2, uint16_t word3) const;
void close_fd() noexcept;
void close_slave_fd() noexcept;
void remove_owned_link() noexcept;
std::string path_;
#ifndef _WIN32
int fd_ = -1;
int slave_fd_ = -1;
std::string slave_path_;
bool owns_link_ = false;
#endif
};